Add support for the LIKE ANY and ILIKE ANY pattern-matching condition (#1456)

This commit is contained in:
Yoav Cohen 2024-10-04 22:03:38 +02:00 committed by GitHub
parent 32a126b27c
commit e849f7f143
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 36 additions and 4 deletions

View file

@ -613,6 +613,9 @@ pub enum Expr {
/// `[NOT] LIKE <pattern> [ESCAPE <escape_character>]` /// `[NOT] LIKE <pattern> [ESCAPE <escape_character>]`
Like { Like {
negated: bool, 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<Expr>, expr: Box<Expr>,
pattern: Box<Expr>, pattern: Box<Expr>,
escape_char: Option<String>, escape_char: Option<String>,
@ -620,6 +623,9 @@ pub enum Expr {
/// `ILIKE` (case-insensitive `LIKE`) /// `ILIKE` (case-insensitive `LIKE`)
ILike { ILike {
negated: bool, 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<Expr>, expr: Box<Expr>,
pattern: Box<Expr>, pattern: Box<Expr>,
escape_char: Option<String>, escape_char: Option<String>,
@ -1242,20 +1248,23 @@ impl fmt::Display for Expr {
expr, expr,
pattern, pattern,
escape_char, escape_char,
any,
} => match escape_char { } => match escape_char {
Some(ch) => write!( Some(ch) => write!(
f, f,
"{} {}LIKE {} ESCAPE '{}'", "{} {}LIKE {}{} ESCAPE '{}'",
expr, expr,
if *negated { "NOT " } else { "" }, if *negated { "NOT " } else { "" },
if *any { "ANY " } else { "" },
pattern, pattern,
ch ch
), ),
_ => write!( _ => write!(
f, f,
"{} {}LIKE {}", "{} {}LIKE {}{}",
expr, expr,
if *negated { "NOT " } else { "" }, if *negated { "NOT " } else { "" },
if *any { "ANY " } else { "" },
pattern pattern
), ),
}, },
@ -1264,20 +1273,23 @@ impl fmt::Display for Expr {
expr, expr,
pattern, pattern,
escape_char, escape_char,
any,
} => match escape_char { } => match escape_char {
Some(ch) => write!( Some(ch) => write!(
f, f,
"{} {}ILIKE {} ESCAPE '{}'", "{} {}ILIKE {}{} ESCAPE '{}'",
expr, expr,
if *negated { "NOT " } else { "" }, if *negated { "NOT " } else { "" },
if *any { "ANY" } else { "" },
pattern, pattern,
ch ch
), ),
_ => write!( _ => write!(
f, f,
"{} {}ILIKE {}", "{} {}ILIKE {}{}",
expr, expr,
if *negated { "NOT " } else { "" }, if *negated { "NOT " } else { "" },
if *any { "ANY " } else { "" },
pattern pattern
), ),
}, },

View file

@ -2749,6 +2749,7 @@ impl<'a> Parser<'a> {
} else if self.parse_keyword(Keyword::LIKE) { } else if self.parse_keyword(Keyword::LIKE) {
Ok(Expr::Like { Ok(Expr::Like {
negated, negated,
any: self.parse_keyword(Keyword::ANY),
expr: Box::new(expr), expr: Box::new(expr),
pattern: Box::new( pattern: Box::new(
self.parse_subexpr(self.dialect.prec_value(Precedence::Like))?, self.parse_subexpr(self.dialect.prec_value(Precedence::Like))?,
@ -2758,6 +2759,7 @@ impl<'a> Parser<'a> {
} else if self.parse_keyword(Keyword::ILIKE) { } else if self.parse_keyword(Keyword::ILIKE) {
Ok(Expr::ILike { Ok(Expr::ILike {
negated, negated,
any: self.parse_keyword(Keyword::ANY),
expr: Box::new(expr), expr: Box::new(expr),
pattern: Box::new( pattern: Box::new(
self.parse_subexpr(self.dialect.prec_value(Precedence::Like))?, self.parse_subexpr(self.dialect.prec_value(Precedence::Like))?,

View file

@ -1550,6 +1550,7 @@ fn parse_not_precedence() {
negated: true, negated: true,
pattern: Box::new(Expr::Value(Value::SingleQuotedString("b".into()))), pattern: Box::new(Expr::Value(Value::SingleQuotedString("b".into()))),
escape_char: None, escape_char: None,
any: false,
}), }),
}, },
); );
@ -1580,6 +1581,7 @@ fn parse_null_like() {
SelectItem::ExprWithAlias { SelectItem::ExprWithAlias {
expr: Expr::Like { expr: Expr::Like {
expr: Box::new(Expr::Identifier(Ident::new("column1"))), expr: Box::new(Expr::Identifier(Ident::new("column1"))),
any: false,
negated: false, negated: false,
pattern: Box::new(Expr::Value(Value::Null)), pattern: Box::new(Expr::Value(Value::Null)),
escape_char: None, escape_char: None,
@ -1595,6 +1597,7 @@ fn parse_null_like() {
SelectItem::ExprWithAlias { SelectItem::ExprWithAlias {
expr: Expr::Like { expr: Expr::Like {
expr: Box::new(Expr::Value(Value::Null)), expr: Box::new(Expr::Value(Value::Null)),
any: false,
negated: false, negated: false,
pattern: Box::new(Expr::Identifier(Ident::new("column1"))), pattern: Box::new(Expr::Identifier(Ident::new("column1"))),
escape_char: None, escape_char: None,
@ -1622,6 +1625,7 @@ fn parse_ilike() {
negated, negated,
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None, escape_char: None,
any: false,
}, },
select.selection.unwrap() select.selection.unwrap()
); );
@ -1638,6 +1642,7 @@ fn parse_ilike() {
negated, negated,
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: Some('^'.to_string()), escape_char: Some('^'.to_string()),
any: false,
}, },
select.selection.unwrap() select.selection.unwrap()
); );
@ -1655,6 +1660,7 @@ fn parse_ilike() {
negated, negated,
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None, escape_char: None,
any: false,
})), })),
select.selection.unwrap() select.selection.unwrap()
); );
@ -1677,6 +1683,7 @@ fn parse_like() {
negated, negated,
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None, escape_char: None,
any: false,
}, },
select.selection.unwrap() select.selection.unwrap()
); );
@ -1693,6 +1700,7 @@ fn parse_like() {
negated, negated,
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: Some('^'.to_string()), escape_char: Some('^'.to_string()),
any: false,
}, },
select.selection.unwrap() select.selection.unwrap()
); );
@ -1710,6 +1718,7 @@ fn parse_like() {
negated, negated,
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None, escape_char: None,
any: false,
})), })),
select.selection.unwrap() select.selection.unwrap()
); );
@ -10130,6 +10139,7 @@ fn test_selective_aggregation() {
expr: Box::new(Expr::Identifier(Ident::new("name"))), expr: Box::new(Expr::Identifier(Ident::new("name"))),
pattern: Box::new(Expr::Value(Value::SingleQuotedString("a%".to_owned()))), pattern: Box::new(Expr::Value(Value::SingleQuotedString("a%".to_owned()))),
escape_char: None, escape_char: None,
any: false,
})), })),
null_treatment: None, null_treatment: None,
over: None, over: None,
@ -11291,3 +11301,11 @@ fn test_alter_policy() {
"sql parser error: Expected: (, found: EOF" "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')"#);
}