mirror of
				https://github.com/apache/datafusion-sqlparser-rs.git
				synced 2025-10-29 22:37:17 +00:00 
			
		
		
		
	Add support for quantified comparison predicates (ALL/ANY/SOME) (#1459)
This commit is contained in:
		
							parent
							
								
									0a941b87dc
								
							
						
					
					
						commit
						a4fa9e08b7
					
				
					 3 changed files with 64 additions and 22 deletions
				
			
		|  | @ -646,12 +646,16 @@ pub enum Expr { | ||||||
|         regexp: bool, |         regexp: bool, | ||||||
|     }, |     }, | ||||||
|     /// `ANY` operation e.g. `foo > ANY(bar)`, comparison operator is one of `[=, >, <, =>, =<, !=]`
 |     /// `ANY` operation e.g. `foo > ANY(bar)`, comparison operator is one of `[=, >, <, =>, =<, !=]`
 | ||||||
|  |     /// <https://docs.snowflake.com/en/sql-reference/operators-subquery#all-any>
 | ||||||
|     AnyOp { |     AnyOp { | ||||||
|         left: Box<Expr>, |         left: Box<Expr>, | ||||||
|         compare_op: BinaryOperator, |         compare_op: BinaryOperator, | ||||||
|         right: Box<Expr>, |         right: Box<Expr>, | ||||||
|  |         // ANY and SOME are synonymous: https://docs.cloudera.com/cdw-runtime/cloud/using-hiveql/topics/hive_comparison_predicates.html
 | ||||||
|  |         is_some: bool, | ||||||
|     }, |     }, | ||||||
|     /// `ALL` operation e.g. `foo > ALL(bar)`, comparison operator is one of `[=, >, <, =>, =<, !=]`
 |     /// `ALL` operation e.g. `foo > ALL(bar)`, comparison operator is one of `[=, >, <, =>, =<, !=]`
 | ||||||
|  |     /// <https://docs.snowflake.com/en/sql-reference/operators-subquery#all-any>
 | ||||||
|     AllOp { |     AllOp { | ||||||
|         left: Box<Expr>, |         left: Box<Expr>, | ||||||
|         compare_op: BinaryOperator, |         compare_op: BinaryOperator, | ||||||
|  | @ -1332,12 +1336,30 @@ impl fmt::Display for Expr { | ||||||
|                 left, |                 left, | ||||||
|                 compare_op, |                 compare_op, | ||||||
|                 right, |                 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 { |             Expr::AllOp { | ||||||
|                 left, |                 left, | ||||||
|                 compare_op, |                 compare_op, | ||||||
|                 right, |                 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 } => { |             Expr::UnaryOp { op, expr } => { | ||||||
|                 if op == &UnaryOperator::PGPostfixFactorial { |                 if op == &UnaryOperator::PGPostfixFactorial { | ||||||
|                     write!(f, "{expr}{op}") |                     write!(f, "{expr}{op}") | ||||||
|  |  | ||||||
|  | @ -1302,13 +1302,9 @@ impl<'a> Parser<'a> { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn try_parse_expr_sub_query(&mut self) -> Result<Option<Expr>, ParserError> { |     fn try_parse_expr_sub_query(&mut self) -> Result<Option<Expr>, ParserError> { | ||||||
|         if self |         if !self.peek_sub_query() { | ||||||
|             .parse_one_of_keywords(&[Keyword::SELECT, Keyword::WITH]) |  | ||||||
|             .is_none() |  | ||||||
|         { |  | ||||||
|             return Ok(None); |             return Ok(None); | ||||||
|         } |         } | ||||||
|         self.prev_token(); |  | ||||||
| 
 | 
 | ||||||
|         Ok(Some(Expr::Subquery(self.parse_boxed_query()?))) |         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
 |         // Snowflake permits a subquery to be passed as an argument without
 | ||||||
|         // an enclosing set of parens if it's the only argument.
 |         // an enclosing set of parens if it's the only argument.
 | ||||||
|         if dialect_of!(self is SnowflakeDialect) |         if dialect_of!(self is SnowflakeDialect) && self.peek_sub_query() { | ||||||
|             && self |  | ||||||
|                 .parse_one_of_keywords(&[Keyword::WITH, Keyword::SELECT]) |  | ||||||
|                 .is_some() |  | ||||||
|         { |  | ||||||
|             self.prev_token(); |  | ||||||
|             let subquery = self.parse_boxed_query()?; |             let subquery = self.parse_boxed_query()?; | ||||||
|             self.expect_token(&Token::RParen)?; |             self.expect_token(&Token::RParen)?; | ||||||
|             return Ok(Expr::Function(Function { |             return Ok(Expr::Function(Function { | ||||||
|  | @ -2639,10 +2630,21 @@ impl<'a> Parser<'a> { | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         if let Some(op) = regular_binary_operator { |         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)?; |                 self.expect_token(&Token::LParen)?; | ||||||
|                 let right = self.parse_subexpr(precedence)?; |                 let right = if self.peek_sub_query() { | ||||||
|                 self.expect_token(&Token::RParen)?; |                     // 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)? | ||||||
|  |                 } else { | ||||||
|  |                     // Non-subquery expression
 | ||||||
|  |                     let right = self.parse_subexpr(precedence)?; | ||||||
|  |                     self.expect_token(&Token::RParen)?; | ||||||
|  |                     right | ||||||
|  |                 }; | ||||||
| 
 | 
 | ||||||
|                 if !matches!( |                 if !matches!( | ||||||
|                     op, |                     op, | ||||||
|  | @ -2667,10 +2669,11 @@ impl<'a> Parser<'a> { | ||||||
|                         compare_op: op, |                         compare_op: op, | ||||||
|                         right: Box::new(right), |                         right: Box::new(right), | ||||||
|                     }, |                     }, | ||||||
|                     Keyword::ANY => Expr::AnyOp { |                     Keyword::ANY | Keyword::SOME => Expr::AnyOp { | ||||||
|                         left: Box::new(expr), |                         left: Box::new(expr), | ||||||
|                         compare_op: op, |                         compare_op: op, | ||||||
|                         right: Box::new(right), |                         right: Box::new(right), | ||||||
|  |                         is_some: keyword == Keyword::SOME, | ||||||
|                     }, |                     }, | ||||||
|                     _ => unreachable!(), |                     _ => unreachable!(), | ||||||
|                 }) |                 }) | ||||||
|  | @ -10507,11 +10510,7 @@ impl<'a> Parser<'a> { | ||||||
|                 vec![] |                 vec![] | ||||||
|             }; |             }; | ||||||
|             PivotValueSource::Any(order_by) |             PivotValueSource::Any(order_by) | ||||||
|         } else if self |         } else if self.peek_sub_query() { | ||||||
|             .parse_one_of_keywords(&[Keyword::SELECT, Keyword::WITH]) |  | ||||||
|             .is_some() |  | ||||||
|         { |  | ||||||
|             self.prev_token(); |  | ||||||
|             PivotValueSource::Subquery(self.parse_query()?) |             PivotValueSource::Subquery(self.parse_query()?) | ||||||
|         } else { |         } else { | ||||||
|             PivotValueSource::List(self.parse_comma_separated(Self::parse_expr_with_alias)?) |             PivotValueSource::List(self.parse_comma_separated(Self::parse_expr_with_alias)?) | ||||||
|  | @ -12177,6 +12176,18 @@ impl<'a> Parser<'a> { | ||||||
|     pub fn into_tokens(self) -> Vec<TokenWithLocation> { |     pub fn into_tokens(self) -> Vec<TokenWithLocation> { | ||||||
|         self.tokens |         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 { | impl Word { | ||||||
|  |  | ||||||
|  | @ -1907,6 +1907,7 @@ fn parse_binary_any() { | ||||||
|             left: Box::new(Expr::Identifier(Ident::new("a"))), |             left: Box::new(Expr::Identifier(Ident::new("a"))), | ||||||
|             compare_op: BinaryOperator::Eq, |             compare_op: BinaryOperator::Eq, | ||||||
|             right: Box::new(Expr::Identifier(Ident::new("b"))), |             right: Box::new(Expr::Identifier(Ident::new("b"))), | ||||||
|  |             is_some: false, | ||||||
|         }), |         }), | ||||||
|         select.projection[0] |         select.projection[0] | ||||||
|     ); |     ); | ||||||
|  | @ -11395,3 +11396,11 @@ fn test_select_where_with_like_or_ilike_any() { | ||||||
|     verified_stmt(r#"SELECT * FROM x WHERE a ILIKE ANY ('%Jo%oe%', 'T%e')"#); |     verified_stmt(r#"SELECT * FROM x WHERE a ILIKE ANY ('%Jo%oe%', 'T%e')"#); | ||||||
|     verified_stmt(r#"SELECT * FROM x WHERE a LIKE ANY ('%Jo%oe%', 'T%e')"#); |     verified_stmt(r#"SELECT * FROM x WHERE a LIKE ANY ('%Jo%oe%', 'T%e')"#); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn test_any_some_all_comparison() { | ||||||
|  |     verified_stmt("SELECT c1 FROM tbl WHERE c1 = ANY(SELECT c2 FROM tbl)"); | ||||||
|  |     verified_stmt("SELECT c1 FROM tbl WHERE c1 >= ALL(SELECT c2 FROM tbl)"); | ||||||
|  |     verified_stmt("SELECT c1 FROM tbl WHERE c1 <> SOME(SELECT c2 FROM tbl)"); | ||||||
|  |     verified_stmt("SELECT 1 = ANY(WITH x AS (SELECT 1) SELECT * FROM x)"); | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Yoav Cohen
						Yoav Cohen