mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-23 15:34:09 +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 {
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue