-
Notifications
You must be signed in to change notification settings - Fork 605
Add support for quantified comparison predicates (ALL/ANY/SOME) #1459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
cad0742
76dbe35
3860cb5
e4def24
704fa97
2219f8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -646,12 +646,16 @@ pub enum Expr { | |
regexp: bool, | ||
}, | ||
/// `ANY` operation e.g. `foo > ANY(bar)`, comparison operator is one of `[=, >, <, =>, =<, !=]` | ||
/// https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#_8_9_quantified_comparison_predicate | ||
AnyOp { | ||
left: Box<Expr>, | ||
compare_op: BinaryOperator, | ||
right: Box<Expr>, | ||
// ANY and SOME are synonymous | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any one of the supported dialects supporting the SOME variant? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah cool! Can we include the link in the comment here as well? It could help later on in case someone comes across this and wonders the same |
||
is_some: bool, | ||
}, | ||
/// `ALL` operation e.g. `foo > ALL(bar)`, comparison operator is one of `[=, >, <, =>, =<, !=]` | ||
/// https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#_8_9_quantified_comparison_predicate | ||
AllOp { | ||
left: Box<Expr>, | ||
compare_op: BinaryOperator, | ||
|
@@ -1332,12 +1336,30 @@ impl fmt::Display for Expr { | |
left, | ||
compare_op, | ||
right, | ||
} => write!(f, "{left} {compare_op} ANY({right})"), | ||
is_some, | ||
} => { | ||
let add_parens = !matches!(right.as_ref(), Expr::Subquery(_)); | ||
write!( | ||
f, | ||
"{left} {compare_op} {}{}{right}{}", | ||
if *is_some { "SOME" } else { "ANY" }, | ||
if add_parens { "(" } else { "" }, | ||
if add_parens { ")" } else { "" }, | ||
) | ||
} | ||
Expr::AllOp { | ||
left, | ||
compare_op, | ||
right, | ||
} => write!(f, "{left} {compare_op} ALL({right})"), | ||
} => { | ||
let add_parens = !matches!(right.as_ref(), Expr::Subquery(_)); | ||
write!( | ||
f, | ||
"{left} {compare_op} ALL{}{right}{}", | ||
if add_parens { "(" } else { "" }, | ||
if add_parens { ")" } else { "" }, | ||
) | ||
} | ||
Expr::UnaryOp { op, expr } => { | ||
if op == &UnaryOperator::PGPostfixFactorial { | ||
write!(f, "{expr}{op}") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1302,13 +1302,9 @@ impl<'a> Parser<'a> { | |
} | ||
|
||
fn try_parse_expr_sub_query(&mut self) -> Result<Option<Expr>, ParserError> { | ||
if self | ||
.parse_one_of_keywords(&[Keyword::SELECT, Keyword::WITH]) | ||
.is_none() | ||
{ | ||
if !self.peek_sub_query() { | ||
return Ok(None); | ||
} | ||
self.prev_token(); | ||
|
||
Ok(Some(Expr::Subquery(self.parse_boxed_query()?))) | ||
} | ||
|
@@ -1334,12 +1330,7 @@ impl<'a> Parser<'a> { | |
|
||
// Snowflake permits a subquery to be passed as an argument without | ||
// an enclosing set of parens if it's the only argument. | ||
if dialect_of!(self is SnowflakeDialect) | ||
&& self | ||
.parse_one_of_keywords(&[Keyword::WITH, Keyword::SELECT]) | ||
.is_some() | ||
{ | ||
self.prev_token(); | ||
if dialect_of!(self is SnowflakeDialect) && self.peek_sub_query() { | ||
let subquery = self.parse_boxed_query()?; | ||
self.expect_token(&Token::RParen)?; | ||
return Ok(Expr::Function(Function { | ||
|
@@ -2639,10 +2630,21 @@ impl<'a> Parser<'a> { | |
}; | ||
|
||
if let Some(op) = regular_binary_operator { | ||
if let Some(keyword) = self.parse_one_of_keywords(&[Keyword::ANY, Keyword::ALL]) { | ||
if let Some(keyword) = | ||
self.parse_one_of_keywords(&[Keyword::ANY, Keyword::ALL, Keyword::SOME]) | ||
{ | ||
self.expect_token(&Token::LParen)?; | ||
let right = self.parse_subexpr(precedence)?; | ||
self.expect_token(&Token::RParen)?; | ||
let right = if self.peek_sub_query() { | ||
// We have a subquery ahead (SELECT\WITH ...) need to rewind and | ||
// use the parenthesis for parsing the subquery as an expression. | ||
self.prev_token(); // LParen | ||
self.parse_subexpr(precedence)? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would it make sense to call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to make that work in the new commit, but since we need to rewind the LParen first it got a bit messy There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah right that makes sense! |
||
} else { | ||
// Non-subquery expression | ||
let right = self.parse_subexpr(precedence)?; | ||
self.expect_token(&Token::RParen)?; | ||
right | ||
}; | ||
|
||
if !matches!( | ||
op, | ||
|
@@ -2667,10 +2669,11 @@ impl<'a> Parser<'a> { | |
compare_op: op, | ||
right: Box::new(right), | ||
}, | ||
Keyword::ANY => Expr::AnyOp { | ||
Keyword::ANY | Keyword::SOME => Expr::AnyOp { | ||
left: Box::new(expr), | ||
compare_op: op, | ||
right: Box::new(right), | ||
is_some: keyword == Keyword::SOME, | ||
}, | ||
_ => unreachable!(), | ||
}) | ||
|
@@ -10471,11 +10474,7 @@ impl<'a> Parser<'a> { | |
vec![] | ||
}; | ||
PivotValueSource::Any(order_by) | ||
} else if self | ||
.parse_one_of_keywords(&[Keyword::SELECT, Keyword::WITH]) | ||
.is_some() | ||
{ | ||
self.prev_token(); | ||
} else if self.peek_sub_query() { | ||
PivotValueSource::Subquery(self.parse_query()?) | ||
} else { | ||
PivotValueSource::List(self.parse_comma_separated(Self::parse_expr_with_alias)?) | ||
|
@@ -12141,6 +12140,18 @@ impl<'a> Parser<'a> { | |
pub fn into_tokens(self) -> Vec<TokenWithLocation> { | ||
self.tokens | ||
} | ||
|
||
/// Returns true if the next keyword indicates a sub query, i.e. SELECT or WITH | ||
fn peek_sub_query(&mut self) -> bool { | ||
if self | ||
.parse_one_of_keywords(&[Keyword::SELECT, Keyword::WITH]) | ||
.is_some() | ||
{ | ||
self.prev_token(); | ||
return true; | ||
} | ||
false | ||
} | ||
} | ||
|
||
impl Word { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking we can link to snowflake instead? Since that's a supported dialect that has the subquery functionality. The current link doesnt seem to refer to any specific dialect so it could add confusion later on when trying to figure what features in the parser to maintain