Skip to content

Commit a4fa9e0

Browse files
authored
Add support for quantified comparison predicates (ALL/ANY/SOME) (#1459)
1 parent 0a941b8 commit a4fa9e0

File tree

3 files changed

+64
-22
lines changed

3 files changed

+64
-22
lines changed

src/ast/mod.rs

+24-2
Original file line numberDiff line numberDiff line change
@@ -646,12 +646,16 @@ pub enum Expr {
646646
regexp: bool,
647647
},
648648
/// `ANY` operation e.g. `foo > ANY(bar)`, comparison operator is one of `[=, >, <, =>, =<, !=]`
649+
/// <https://docs.snowflake.com/en/sql-reference/operators-subquery#all-any>
649650
AnyOp {
650651
left: Box<Expr>,
651652
compare_op: BinaryOperator,
652653
right: Box<Expr>,
654+
// ANY and SOME are synonymous: https://docs.cloudera.com/cdw-runtime/cloud/using-hiveql/topics/hive_comparison_predicates.html
655+
is_some: bool,
653656
},
654657
/// `ALL` operation e.g. `foo > ALL(bar)`, comparison operator is one of `[=, >, <, =>, =<, !=]`
658+
/// <https://docs.snowflake.com/en/sql-reference/operators-subquery#all-any>
655659
AllOp {
656660
left: Box<Expr>,
657661
compare_op: BinaryOperator,
@@ -1332,12 +1336,30 @@ impl fmt::Display for Expr {
13321336
left,
13331337
compare_op,
13341338
right,
1335-
} => write!(f, "{left} {compare_op} ANY({right})"),
1339+
is_some,
1340+
} => {
1341+
let add_parens = !matches!(right.as_ref(), Expr::Subquery(_));
1342+
write!(
1343+
f,
1344+
"{left} {compare_op} {}{}{right}{}",
1345+
if *is_some { "SOME" } else { "ANY" },
1346+
if add_parens { "(" } else { "" },
1347+
if add_parens { ")" } else { "" },
1348+
)
1349+
}
13361350
Expr::AllOp {
13371351
left,
13381352
compare_op,
13391353
right,
1340-
} => write!(f, "{left} {compare_op} ALL({right})"),
1354+
} => {
1355+
let add_parens = !matches!(right.as_ref(), Expr::Subquery(_));
1356+
write!(
1357+
f,
1358+
"{left} {compare_op} ALL{}{right}{}",
1359+
if add_parens { "(" } else { "" },
1360+
if add_parens { ")" } else { "" },
1361+
)
1362+
}
13411363
Expr::UnaryOp { op, expr } => {
13421364
if op == &UnaryOperator::PGPostfixFactorial {
13431365
write!(f, "{expr}{op}")

src/parser/mod.rs

+31-20
Original file line numberDiff line numberDiff line change
@@ -1302,13 +1302,9 @@ impl<'a> Parser<'a> {
13021302
}
13031303

13041304
fn try_parse_expr_sub_query(&mut self) -> Result<Option<Expr>, ParserError> {
1305-
if self
1306-
.parse_one_of_keywords(&[Keyword::SELECT, Keyword::WITH])
1307-
.is_none()
1308-
{
1305+
if !self.peek_sub_query() {
13091306
return Ok(None);
13101307
}
1311-
self.prev_token();
13121308

13131309
Ok(Some(Expr::Subquery(self.parse_boxed_query()?)))
13141310
}
@@ -1334,12 +1330,7 @@ impl<'a> Parser<'a> {
13341330

13351331
// Snowflake permits a subquery to be passed as an argument without
13361332
// an enclosing set of parens if it's the only argument.
1337-
if dialect_of!(self is SnowflakeDialect)
1338-
&& self
1339-
.parse_one_of_keywords(&[Keyword::WITH, Keyword::SELECT])
1340-
.is_some()
1341-
{
1342-
self.prev_token();
1333+
if dialect_of!(self is SnowflakeDialect) && self.peek_sub_query() {
13431334
let subquery = self.parse_boxed_query()?;
13441335
self.expect_token(&Token::RParen)?;
13451336
return Ok(Expr::Function(Function {
@@ -2639,10 +2630,21 @@ impl<'a> Parser<'a> {
26392630
};
26402631

26412632
if let Some(op) = regular_binary_operator {
2642-
if let Some(keyword) = self.parse_one_of_keywords(&[Keyword::ANY, Keyword::ALL]) {
2633+
if let Some(keyword) =
2634+
self.parse_one_of_keywords(&[Keyword::ANY, Keyword::ALL, Keyword::SOME])
2635+
{
26432636
self.expect_token(&Token::LParen)?;
2644-
let right = self.parse_subexpr(precedence)?;
2645-
self.expect_token(&Token::RParen)?;
2637+
let right = if self.peek_sub_query() {
2638+
// We have a subquery ahead (SELECT\WITH ...) need to rewind and
2639+
// use the parenthesis for parsing the subquery as an expression.
2640+
self.prev_token(); // LParen
2641+
self.parse_subexpr(precedence)?
2642+
} else {
2643+
// Non-subquery expression
2644+
let right = self.parse_subexpr(precedence)?;
2645+
self.expect_token(&Token::RParen)?;
2646+
right
2647+
};
26462648

26472649
if !matches!(
26482650
op,
@@ -2667,10 +2669,11 @@ impl<'a> Parser<'a> {
26672669
compare_op: op,
26682670
right: Box::new(right),
26692671
},
2670-
Keyword::ANY => Expr::AnyOp {
2672+
Keyword::ANY | Keyword::SOME => Expr::AnyOp {
26712673
left: Box::new(expr),
26722674
compare_op: op,
26732675
right: Box::new(right),
2676+
is_some: keyword == Keyword::SOME,
26742677
},
26752678
_ => unreachable!(),
26762679
})
@@ -10507,11 +10510,7 @@ impl<'a> Parser<'a> {
1050710510
vec![]
1050810511
};
1050910512
PivotValueSource::Any(order_by)
10510-
} else if self
10511-
.parse_one_of_keywords(&[Keyword::SELECT, Keyword::WITH])
10512-
.is_some()
10513-
{
10514-
self.prev_token();
10513+
} else if self.peek_sub_query() {
1051510514
PivotValueSource::Subquery(self.parse_query()?)
1051610515
} else {
1051710516
PivotValueSource::List(self.parse_comma_separated(Self::parse_expr_with_alias)?)
@@ -12177,6 +12176,18 @@ impl<'a> Parser<'a> {
1217712176
pub fn into_tokens(self) -> Vec<TokenWithLocation> {
1217812177
self.tokens
1217912178
}
12179+
12180+
/// Returns true if the next keyword indicates a sub query, i.e. SELECT or WITH
12181+
fn peek_sub_query(&mut self) -> bool {
12182+
if self
12183+
.parse_one_of_keywords(&[Keyword::SELECT, Keyword::WITH])
12184+
.is_some()
12185+
{
12186+
self.prev_token();
12187+
return true;
12188+
}
12189+
false
12190+
}
1218012191
}
1218112192

1218212193
impl Word {

tests/sqlparser_common.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1907,6 +1907,7 @@ fn parse_binary_any() {
19071907
left: Box::new(Expr::Identifier(Ident::new("a"))),
19081908
compare_op: BinaryOperator::Eq,
19091909
right: Box::new(Expr::Identifier(Ident::new("b"))),
1910+
is_some: false,
19101911
}),
19111912
select.projection[0]
19121913
);
@@ -11395,3 +11396,11 @@ fn test_select_where_with_like_or_ilike_any() {
1139511396
verified_stmt(r#"SELECT * FROM x WHERE a ILIKE ANY ('%Jo%oe%', 'T%e')"#);
1139611397
verified_stmt(r#"SELECT * FROM x WHERE a LIKE ANY ('%Jo%oe%', 'T%e')"#);
1139711398
}
11399+
11400+
#[test]
11401+
fn test_any_some_all_comparison() {
11402+
verified_stmt("SELECT c1 FROM tbl WHERE c1 = ANY(SELECT c2 FROM tbl)");
11403+
verified_stmt("SELECT c1 FROM tbl WHERE c1 >= ALL(SELECT c2 FROM tbl)");
11404+
verified_stmt("SELECT c1 FROM tbl WHERE c1 <> SOME(SELECT c2 FROM tbl)");
11405+
verified_stmt("SELECT 1 = ANY(WITH x AS (SELECT 1) SELECT * FROM x)");
11406+
}

0 commit comments

Comments
 (0)