mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-22 23:14:07 +00:00
Parse LIKE patterns as Expr not Value (#579)
This commit is contained in:
parent
fc71719719
commit
eb7f1b005e
3 changed files with 73 additions and 28 deletions
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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> {
|
||||
|
@ -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)) {
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}),
|
||||
},
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue