Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse LIKE patterns as Expr not Value #579

Merged
merged 1 commit into from Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/ast/mod.rs
Expand Up @@ -272,21 +272,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 @@ -1309,21 +1309,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 @@ -1470,10 +1470,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 @@ -1484,9 +1490,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 @@ -1507,18 +1513,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