Skip to content

Commit

Permalink
Parse LIKE patterns as Expr not Value (#579)
Browse files Browse the repository at this point in the history
  • Loading branch information
andygrove committed Aug 18, 2022
1 parent fc71719 commit eb7f1b0
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 28 deletions.
6 changes: 3 additions & 3 deletions src/ast/mod.rs
Expand Up @@ -280,21 +280,21 @@ pub enum Expr {
Like {
negated: bool,
expr: Box<Expr>,
pattern: Box<Value>,
pattern: Box<Expr>,
escape_char: Option<char>,
},
/// ILIKE (case-insensitive LIKE)
ILike {
negated: bool,
expr: Box<Expr>,
pattern: Box<Value>,
pattern: Box<Expr>,
escape_char: Option<char>,
},
/// SIMILAR TO regex
SimilarTo {
negated: bool,
expr: Box<Expr>,
pattern: Box<Value>,
pattern: Box<Expr>,
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
Expand Down
36 changes: 21 additions & 15 deletions src/parser.rs
Expand Up @@ -1317,21 +1317,21 @@ impl<'a> Parser<'a> {
Ok(Expr::Like {
negated,
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()?,
})
} else if self.parse_keyword(Keyword::ILIKE) {
Ok(Expr::ILike {
negated,
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()?,
})
} else if self.parse_keywords(&[Keyword::SIMILAR, Keyword::TO]) {
Ok(Expr::SimilarTo {
negated,
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()?,
})
} else {
Expand Down Expand Up @@ -1478,10 +1478,16 @@ impl<'a> Parser<'a> {
})
}

const UNARY_NOT_PREC: u8 = 15;
const BETWEEN_PREC: u8 = 20;
// use https://www.postgresql.org/docs/7.0/operators.htm#AEN2026 as a reference
const PLUS_MINUS_PREC: u8 = 30;
const XOR_PREC: u8 = 24;
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
pub fn get_next_precedence(&self) -> Result<u8, ParserError> {
Expand All @@ -1492,9 +1498,9 @@ impl<'a> Parser<'a> {
let token_2 = self.peek_nth_token(2);
debug!("0: {token_0} 1: {token_1} 2: {token_2}");
match token {
Token::Word(w) if w.keyword == Keyword::OR => Ok(5),
Token::Word(w) if w.keyword == Keyword::AND => Ok(10),
Token::Word(w) if w.keyword == Keyword::XOR => Ok(24),
Token::Word(w) if w.keyword == Keyword::OR => Ok(Self::OR_PREC),
Token::Word(w) if w.keyword == Keyword::AND => Ok(Self::AND_PREC),
Token::Word(w) if w.keyword == Keyword::XOR => Ok(Self::XOR_PREC),

Token::Word(w) if w.keyword == Keyword::AT => {
match (self.peek_nth_token(1), self.peek_nth_token(2)) {
Expand All @@ -1515,18 +1521,18 @@ impl<'a> Parser<'a> {
// precedence.
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::LIKE => Ok(Self::BETWEEN_PREC),
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
Token::Word(w) if w.keyword == Keyword::SIMILAR => 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::LIKE_PREC),
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::LIKE_PREC),
_ => 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::BETWEEN => Ok(Self::BETWEEN_PREC),
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
Token::Word(w) if w.keyword == Keyword::ILIKE => 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::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::SIMILAR => Ok(Self::BETWEEN_PREC),
Token::Eq
| Token::Lt
| Token::LtEq
Expand Down
59 changes: 49 additions & 10 deletions tests/sqlparser_common.rs
Expand Up @@ -862,7 +862,7 @@ fn parse_not_precedence() {
expr: Box::new(Expr::Like {
expr: Box::new(Expr::Value(Value::SingleQuotedString("a".into()))),
negated: true,
pattern: Box::new(Value::SingleQuotedString("b".into())),
pattern: Box::new(Expr::Value(Value::SingleQuotedString("b".into()))),
escape_char: None
}),
},
Expand Down Expand Up @@ -895,7 +895,7 @@ fn parse_like() {
Expr::Like {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None
},
select.selection.unwrap()
Expand All @@ -911,7 +911,7 @@ fn parse_like() {
Expr::Like {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: Some('\\')
},
select.selection.unwrap()
Expand All @@ -928,7 +928,7 @@ fn parse_like() {
Expr::IsNull(Box::new(Expr::Like {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None
})),
select.selection.unwrap()
Expand All @@ -938,6 +938,45 @@ fn parse_like() {
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]
fn parse_ilike() {
fn chk(negated: bool) {
Expand All @@ -950,7 +989,7 @@ fn parse_ilike() {
Expr::ILike {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None
},
select.selection.unwrap()
Expand All @@ -966,7 +1005,7 @@ fn parse_ilike() {
Expr::ILike {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: Some('^')
},
select.selection.unwrap()
Expand All @@ -983,7 +1022,7 @@ fn parse_ilike() {
Expr::IsNull(Box::new(Expr::ILike {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None
})),
select.selection.unwrap()
Expand All @@ -1005,7 +1044,7 @@ fn parse_similar_to() {
Expr::SimilarTo {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: None
},
select.selection.unwrap()
Expand All @@ -1021,7 +1060,7 @@ fn parse_similar_to() {
Expr::SimilarTo {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: Some('\\')
},
select.selection.unwrap()
Expand All @@ -1037,7 +1076,7 @@ fn parse_similar_to() {
Expr::IsNull(Box::new(Expr::SimilarTo {
expr: Box::new(Expr::Identifier(Ident::new("name"))),
negated,
pattern: Box::new(Value::SingleQuotedString("%a".to_string())),
pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))),
escape_char: Some('\\')
})),
select.selection.unwrap()
Expand Down

0 comments on commit eb7f1b0

Please sign in to comment.