Parse LIKE patterns as Expr not Value (#579)

This commit is contained in:
Andy Grove 2022-08-18 10:02:54 -06:00 committed by GitHub
parent fc71719719
commit eb7f1b005e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 28 deletions

View file

@ -280,21 +280,21 @@ pub enum Expr {
Like { Like {
negated: bool, negated: bool,
expr: Box<Expr>, expr: Box<Expr>,
pattern: Box<Value>, pattern: Box<Expr>,
escape_char: Option<char>, escape_char: Option<char>,
}, },
/// ILIKE (case-insensitive LIKE) /// ILIKE (case-insensitive LIKE)
ILike { ILike {
negated: bool, negated: bool,
expr: Box<Expr>, expr: Box<Expr>,
pattern: Box<Value>, pattern: Box<Expr>,
escape_char: Option<char>, escape_char: Option<char>,
}, },
/// SIMILAR TO regex /// SIMILAR TO regex
SimilarTo { SimilarTo {
negated: bool, negated: bool,
expr: Box<Expr>, expr: Box<Expr>,
pattern: Box<Value>, pattern: Box<Expr>,
escape_char: Option<char>, escape_char: Option<char>,
}, },
/// Any operation e.g. `1 ANY (1)` or `foo > ANY(bar)`, It will be wrapped in the right side of BinaryExpr /// Any operation e.g. `1 ANY (1)` or `foo > ANY(bar)`, It will be wrapped in the right side of BinaryExpr

View file

@ -1317,21 +1317,21 @@ impl<'a> Parser<'a> {
Ok(Expr::Like { Ok(Expr::Like {
negated, negated,
expr: Box::new(expr), expr: Box::new(expr),
pattern: Box::new(self.parse_value()?), pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
escape_char: self.parse_escape_char()?, escape_char: self.parse_escape_char()?,
}) })
} else if self.parse_keyword(Keyword::ILIKE) { } else if self.parse_keyword(Keyword::ILIKE) {
Ok(Expr::ILike { Ok(Expr::ILike {
negated, negated,
expr: Box::new(expr), expr: Box::new(expr),
pattern: Box::new(self.parse_value()?), pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
escape_char: self.parse_escape_char()?, escape_char: self.parse_escape_char()?,
}) })
} else if self.parse_keywords(&[Keyword::SIMILAR, Keyword::TO]) { } else if self.parse_keywords(&[Keyword::SIMILAR, Keyword::TO]) {
Ok(Expr::SimilarTo { Ok(Expr::SimilarTo {
negated, negated,
expr: Box::new(expr), expr: Box::new(expr),
pattern: Box::new(self.parse_value()?), pattern: Box::new(self.parse_subexpr(Self::LIKE_PREC)?),
escape_char: self.parse_escape_char()?, escape_char: self.parse_escape_char()?,
}) })
} else { } else {
@ -1478,10 +1478,16 @@ impl<'a> Parser<'a> {
}) })
} }
const UNARY_NOT_PREC: u8 = 15; // use https://www.postgresql.org/docs/7.0/operators.htm#AEN2026 as a reference
const BETWEEN_PREC: u8 = 20;
const PLUS_MINUS_PREC: u8 = 30; const PLUS_MINUS_PREC: u8 = 30;
const XOR_PREC: u8 = 24;
const TIME_ZONE_PREC: u8 = 20; const TIME_ZONE_PREC: u8 = 20;
const BETWEEN_PREC: u8 = 20;
const LIKE_PREC: u8 = 19;
const IS_PREC: u8 = 17;
const UNARY_NOT_PREC: u8 = 15;
const AND_PREC: u8 = 10;
const OR_PREC: u8 = 5;
/// Get the precedence of the next token /// Get the precedence of the next token
pub fn get_next_precedence(&self) -> Result<u8, ParserError> { pub fn get_next_precedence(&self) -> Result<u8, ParserError> {
@ -1492,9 +1498,9 @@ impl<'a> Parser<'a> {
let token_2 = self.peek_nth_token(2); let token_2 = self.peek_nth_token(2);
debug!("0: {token_0} 1: {token_1} 2: {token_2}"); debug!("0: {token_0} 1: {token_1} 2: {token_2}");
match token { match token {
Token::Word(w) if w.keyword == Keyword::OR => Ok(5), Token::Word(w) if w.keyword == Keyword::OR => Ok(Self::OR_PREC),
Token::Word(w) if w.keyword == Keyword::AND => Ok(10), Token::Word(w) if w.keyword == Keyword::AND => Ok(Self::AND_PREC),
Token::Word(w) if w.keyword == Keyword::XOR => Ok(24), Token::Word(w) if w.keyword == Keyword::XOR => Ok(Self::XOR_PREC),
Token::Word(w) if w.keyword == Keyword::AT => { Token::Word(w) if w.keyword == Keyword::AT => {
match (self.peek_nth_token(1), self.peek_nth_token(2)) { match (self.peek_nth_token(1), self.peek_nth_token(2)) {
@ -1515,18 +1521,18 @@ impl<'a> Parser<'a> {
// precedence. // precedence.
Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC), Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC),
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC), Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC), Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::LIKE_PREC),
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC), Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::LIKE_PREC),
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC), Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::LIKE_PREC),
_ => Ok(0), _ => Ok(0),
}, },
Token::Word(w) if w.keyword == Keyword::IS => Ok(17), Token::Word(w) if w.keyword == Keyword::IS => Ok(Self::IS_PREC),
Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC), Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC),
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC), Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC), Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::LIKE_PREC),
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC), Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::LIKE_PREC),
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::LIKE_PREC),
Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(Self::BETWEEN_PREC), Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(Self::BETWEEN_PREC),
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
Token::Eq Token::Eq
| Token::Lt | Token::Lt
| Token::LtEq | Token::LtEq

View file

@ -862,7 +862,7 @@ fn parse_not_precedence() {
expr: Box::new(Expr::Like { expr: Box::new(Expr::Like {
expr: Box::new(Expr::Value(Value::SingleQuotedString("a".into()))), expr: Box::new(Expr::Value(Value::SingleQuotedString("a".into()))),
negated: true, negated: true,
pattern: Box::new(Value::SingleQuotedString("b".into())), pattern: Box::new(Expr::Value(Value::SingleQuotedString("b".into()))),
escape_char: None escape_char: None
}), }),
}, },
@ -895,7 +895,7 @@ fn parse_like() {
Expr::Like { Expr::Like {
expr: Box::new(Expr::Identifier(Ident::new("name"))), expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated, negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None escape_char: None
}, },
select.selection.unwrap() select.selection.unwrap()
@ -911,7 +911,7 @@ fn parse_like() {
Expr::Like { Expr::Like {
expr: Box::new(Expr::Identifier(Ident::new("name"))), expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated, negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: Some('\\') escape_char: Some('\\')
}, },
select.selection.unwrap() select.selection.unwrap()
@ -928,7 +928,7 @@ fn parse_like() {
Expr::IsNull(Box::new(Expr::Like { Expr::IsNull(Box::new(Expr::Like {
expr: Box::new(Expr::Identifier(Ident::new("name"))), expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated, negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None escape_char: None
})), })),
select.selection.unwrap() select.selection.unwrap()
@ -938,6 +938,45 @@ fn parse_like() {
chk(true); chk(true);
} }
#[test]
fn parse_null_like() {
let sql = "SELECT \
column1 LIKE NULL AS col_null, \
NULL LIKE column1 AS null_col \
FROM customers";
let select = verified_only_select(sql);
assert_eq!(
SelectItem::ExprWithAlias {
expr: Expr::Like {
expr: Box::new(Expr::Identifier(Ident::new("column1"))),
negated: false,
pattern: Box::new(Expr::Value(Value::Null)),
escape_char: None
},
alias: Ident {
value: "col_null".to_owned(),
quote_style: None
}
},
select.projection[0]
);
assert_eq!(
SelectItem::ExprWithAlias {
expr: Expr::Like {
expr: Box::new(Expr::Value(Value::Null)),
negated: false,
pattern: Box::new(Expr::Identifier(Ident::new("column1"))),
escape_char: None
},
alias: Ident {
value: "null_col".to_owned(),
quote_style: None
}
},
select.projection[1]
);
}
#[test] #[test]
fn parse_ilike() { fn parse_ilike() {
fn chk(negated: bool) { fn chk(negated: bool) {
@ -950,7 +989,7 @@ fn parse_ilike() {
Expr::ILike { Expr::ILike {
expr: Box::new(Expr::Identifier(Ident::new("name"))), expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated, negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None escape_char: None
}, },
select.selection.unwrap() select.selection.unwrap()
@ -966,7 +1005,7 @@ fn parse_ilike() {
Expr::ILike { Expr::ILike {
expr: Box::new(Expr::Identifier(Ident::new("name"))), expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated, negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: Some('^') escape_char: Some('^')
}, },
select.selection.unwrap() select.selection.unwrap()
@ -983,7 +1022,7 @@ fn parse_ilike() {
Expr::IsNull(Box::new(Expr::ILike { Expr::IsNull(Box::new(Expr::ILike {
expr: Box::new(Expr::Identifier(Ident::new("name"))), expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated, negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None escape_char: None
})), })),
select.selection.unwrap() select.selection.unwrap()
@ -1005,7 +1044,7 @@ fn parse_similar_to() {
Expr::SimilarTo { Expr::SimilarTo {
expr: Box::new(Expr::Identifier(Ident::new("name"))), expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated, negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None escape_char: None
}, },
select.selection.unwrap() select.selection.unwrap()
@ -1021,7 +1060,7 @@ fn parse_similar_to() {
Expr::SimilarTo { Expr::SimilarTo {
expr: Box::new(Expr::Identifier(Ident::new("name"))), expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated, negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: Some('\\') escape_char: Some('\\')
}, },
select.selection.unwrap() select.selection.unwrap()
@ -1037,7 +1076,7 @@ fn parse_similar_to() {
Expr::IsNull(Box::new(Expr::SimilarTo { Expr::IsNull(Box::new(Expr::SimilarTo {
expr: Box::new(Expr::Identifier(Ident::new("name"))), expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated, negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())), pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: Some('\\') escape_char: Some('\\')
})), })),
select.selection.unwrap() select.selection.unwrap()