diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a11fa63f..57cd401d 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -613,6 +613,9 @@ pub enum Expr { /// `[NOT] LIKE [ESCAPE ]` Like { negated: bool, + // Snowflake supports the ANY keyword to match against a list of patterns + // https://docs.snowflake.com/en/sql-reference/functions/like_any + any: bool, expr: Box, pattern: Box, escape_char: Option, @@ -620,6 +623,9 @@ pub enum Expr { /// `ILIKE` (case-insensitive `LIKE`) ILike { negated: bool, + // Snowflake supports the ANY keyword to match against a list of patterns + // https://docs.snowflake.com/en/sql-reference/functions/like_any + any: bool, expr: Box, pattern: Box, escape_char: Option, @@ -1242,20 +1248,23 @@ impl fmt::Display for Expr { expr, pattern, escape_char, + any, } => match escape_char { Some(ch) => write!( f, - "{} {}LIKE {} ESCAPE '{}'", + "{} {}LIKE {}{} ESCAPE '{}'", expr, if *negated { "NOT " } else { "" }, + if *any { "ANY " } else { "" }, pattern, ch ), _ => write!( f, - "{} {}LIKE {}", + "{} {}LIKE {}{}", expr, if *negated { "NOT " } else { "" }, + if *any { "ANY " } else { "" }, pattern ), }, @@ -1264,20 +1273,23 @@ impl fmt::Display for Expr { expr, pattern, escape_char, + any, } => match escape_char { Some(ch) => write!( f, - "{} {}ILIKE {} ESCAPE '{}'", + "{} {}ILIKE {}{} ESCAPE '{}'", expr, if *negated { "NOT " } else { "" }, + if *any { "ANY" } else { "" }, pattern, ch ), _ => write!( f, - "{} {}ILIKE {}", + "{} {}ILIKE {}{}", expr, if *negated { "NOT " } else { "" }, + if *any { "ANY " } else { "" }, pattern ), }, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 028bae70..88c3bd19 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2749,6 +2749,7 @@ impl<'a> Parser<'a> { } else if self.parse_keyword(Keyword::LIKE) { Ok(Expr::Like { negated, + any: self.parse_keyword(Keyword::ANY), expr: Box::new(expr), pattern: Box::new( self.parse_subexpr(self.dialect.prec_value(Precedence::Like))?, @@ -2758,6 +2759,7 @@ impl<'a> Parser<'a> { } else if self.parse_keyword(Keyword::ILIKE) { Ok(Expr::ILike { negated, + any: self.parse_keyword(Keyword::ANY), expr: Box::new(expr), pattern: Box::new( self.parse_subexpr(self.dialect.prec_value(Precedence::Like))?, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index a06b47a5..5d5a17ca 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -1550,6 +1550,7 @@ fn parse_not_precedence() { negated: true, pattern: Box::new(Expr::Value(Value::SingleQuotedString("b".into()))), escape_char: None, + any: false, }), }, ); @@ -1580,6 +1581,7 @@ fn parse_null_like() { SelectItem::ExprWithAlias { expr: Expr::Like { expr: Box::new(Expr::Identifier(Ident::new("column1"))), + any: false, negated: false, pattern: Box::new(Expr::Value(Value::Null)), escape_char: None, @@ -1595,6 +1597,7 @@ fn parse_null_like() { SelectItem::ExprWithAlias { expr: Expr::Like { expr: Box::new(Expr::Value(Value::Null)), + any: false, negated: false, pattern: Box::new(Expr::Identifier(Ident::new("column1"))), escape_char: None, @@ -1622,6 +1625,7 @@ fn parse_ilike() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: None, + any: false, }, select.selection.unwrap() ); @@ -1638,6 +1642,7 @@ fn parse_ilike() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: Some('^'.to_string()), + any: false, }, select.selection.unwrap() ); @@ -1655,6 +1660,7 @@ fn parse_ilike() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: None, + any: false, })), select.selection.unwrap() ); @@ -1677,6 +1683,7 @@ fn parse_like() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: None, + any: false, }, select.selection.unwrap() ); @@ -1693,6 +1700,7 @@ fn parse_like() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: Some('^'.to_string()), + any: false, }, select.selection.unwrap() ); @@ -1710,6 +1718,7 @@ fn parse_like() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: None, + any: false, })), select.selection.unwrap() ); @@ -10130,6 +10139,7 @@ fn test_selective_aggregation() { expr: Box::new(Expr::Identifier(Ident::new("name"))), pattern: Box::new(Expr::Value(Value::SingleQuotedString("a%".to_owned()))), escape_char: None, + any: false, })), null_treatment: None, over: None, @@ -11291,3 +11301,11 @@ fn test_alter_policy() { "sql parser error: Expected: (, found: EOF" ); } + +#[test] +fn test_select_where_with_like_or_ilike_any() { + verified_stmt(r#"SELECT * FROM x WHERE a ILIKE ANY '%abc%'"#); + verified_stmt(r#"SELECT * FROM x WHERE a LIKE ANY '%abc%'"#); + 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')"#); +}