diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a11fa63f4..57cd401d3 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 a4445e04d..329194dfe 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 2677c193c..53bc55909 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -1549,6 +1549,7 @@ fn parse_not_precedence() { negated: true, pattern: Box::new(Expr::Value(Value::SingleQuotedString("b".into()))), escape_char: None, + any: false, }), }, ); @@ -1579,6 +1580,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, @@ -1594,6 +1596,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, @@ -1621,6 +1624,7 @@ fn parse_ilike() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: None, + any: false, }, select.selection.unwrap() ); @@ -1637,6 +1641,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() ); @@ -1654,6 +1659,7 @@ fn parse_ilike() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: None, + any: false, })), select.selection.unwrap() ); @@ -1676,6 +1682,7 @@ fn parse_like() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: None, + any: false, }, select.selection.unwrap() ); @@ -1692,6 +1699,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() ); @@ -1709,6 +1717,7 @@ fn parse_like() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: None, + any: false, })), select.selection.unwrap() ); @@ -10098,6 +10107,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, @@ -11259,3 +11269,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')"#); +}