Skip to content

Commit

Permalink
Support for SIMILAR TO syntax, change Like and ILike to Expr
Browse files Browse the repository at this point in the history
…variants, allow escape char for like/ilike (apache#569)

* Remove [not]like,[not]ilike from binary operator list

* Add like, ilike and similar as an expr variant. Also adds support for escape char to like/ilike

* Add parsing logic for similar to, update parsing logic for like/ilike

* Add tests for similar to, update tests for like/ilike

* Fix linter warnings

* remove additional copyright license from files

* Add more coverage w/wo escape char for like,ilike,similar to
  • Loading branch information
ayushdg authored Aug 11, 2022
1 parent 18881f8 commit f07063f
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 57 deletions.
87 changes: 87 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,27 @@ pub enum Expr {
op: BinaryOperator,
right: Box<Expr>,
},
/// LIKE
Like {
negated: bool,
expr: Box<Expr>,
pattern: Box<Value>,
escape_char: Option<char>,
},
/// ILIKE (case-insensitive LIKE)
ILike {
negated: bool,
expr: Box<Expr>,
pattern: Box<Value>,
escape_char: Option<char>,
},
/// SIMILAR TO regex
SimilarTo {
negated: bool,
expr: Box<Expr>,
pattern: Box<Value>,
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
AnyOp(Box<Expr>),
/// ALL operation e.g. `1 ALL (1)` or `foo > ALL(bar)`, It will be wrapped in the right side of BinaryExpr
Expand Down Expand Up @@ -438,6 +459,72 @@ impl fmt::Display for Expr {
high
),
Expr::BinaryOp { left, op, right } => write!(f, "{} {} {}", left, op, right),
Expr::Like {
negated,
expr,
pattern,
escape_char,
} => match escape_char {
Some(ch) => write!(
f,
"{} {}LIKE {} ESCAPE '{}'",
expr,
if *negated { "NOT " } else { "" },
pattern,
ch
),
_ => write!(
f,
"{} {}LIKE {}",
expr,
if *negated { "NOT " } else { "" },
pattern
),
},
Expr::ILike {
negated,
expr,
pattern,
escape_char,
} => match escape_char {
Some(ch) => write!(
f,
"{} {}ILIKE {} ESCAPE '{}'",
expr,
if *negated { "NOT " } else { "" },
pattern,
ch
),
_ => write!(
f,
"{} {}ILIKE {}",
expr,
if *negated { "NOT " } else { "" },
pattern
),
},
Expr::SimilarTo {
negated,
expr,
pattern,
escape_char,
} => match escape_char {
Some(ch) => write!(
f,
"{} {}SIMILAR TO {} ESCAPE '{}'",
expr,
if *negated { "NOT " } else { "" },
pattern,
ch
),
_ => write!(
f,
"{} {}SIMILAR TO {}",
expr,
if *negated { "NOT " } else { "" },
pattern
),
},
Expr::AnyOp(expr) => write!(f, "ANY({})", expr),
Expr::AllOp(expr) => write!(f, "ALL({})", expr),
Expr::UnaryOp { op, expr } => {
Expand Down
8 changes: 0 additions & 8 deletions src/ast/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,6 @@ pub enum BinaryOperator {
And,
Or,
Xor,
Like,
NotLike,
ILike,
NotILike,
BitwiseOr,
BitwiseAnd,
BitwiseXor,
Expand Down Expand Up @@ -116,10 +112,6 @@ impl fmt::Display for BinaryOperator {
BinaryOperator::And => f.write_str("AND"),
BinaryOperator::Or => f.write_str("OR"),
BinaryOperator::Xor => f.write_str("XOR"),
BinaryOperator::Like => f.write_str("LIKE"),
BinaryOperator::NotLike => f.write_str("NOT LIKE"),
BinaryOperator::ILike => f.write_str("ILIKE"),
BinaryOperator::NotILike => f.write_str("NOT ILIKE"),
BinaryOperator::BitwiseOr => f.write_str("|"),
BinaryOperator::BitwiseAnd => f.write_str("&"),
BinaryOperator::BitwiseXor => f.write_str("^"),
Expand Down
50 changes: 38 additions & 12 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1178,17 +1178,6 @@ impl<'a> Parser<'a> {
Token::Word(w) => match w.keyword {
Keyword::AND => Some(BinaryOperator::And),
Keyword::OR => Some(BinaryOperator::Or),
Keyword::LIKE => Some(BinaryOperator::Like),
Keyword::ILIKE => Some(BinaryOperator::ILike),
Keyword::NOT => {
if self.parse_keyword(Keyword::LIKE) {
Some(BinaryOperator::NotLike)
} else if self.parse_keyword(Keyword::ILIKE) {
Some(BinaryOperator::NotILike)
} else {
None
}
}
Keyword::XOR => Some(BinaryOperator::Xor),
Keyword::OPERATOR if dialect_of!(self is PostgreSqlDialect | GenericDialect) => {
self.expect_token(&Token::LParen)?;
Expand Down Expand Up @@ -1282,13 +1271,39 @@ impl<'a> Parser<'a> {
self.expected("Expected Token::Word after AT", tok)
}
}
Keyword::NOT | Keyword::IN | Keyword::BETWEEN => {
Keyword::NOT
| Keyword::IN
| Keyword::BETWEEN
| Keyword::LIKE
| Keyword::ILIKE
| Keyword::SIMILAR => {
self.prev_token();
let negated = self.parse_keyword(Keyword::NOT);
if self.parse_keyword(Keyword::IN) {
self.parse_in(expr, negated)
} else if self.parse_keyword(Keyword::BETWEEN) {
self.parse_between(expr, negated)
} else if self.parse_keyword(Keyword::LIKE) {
Ok(Expr::Like {
negated,
expr: Box::new(expr),
pattern: Box::new(self.parse_value()?),
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()?),
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()?),
escape_char: self.parse_escape_char()?,
})
} else {
self.expected("IN or BETWEEN after NOT", self.peek_token())
}
Expand Down Expand Up @@ -1333,6 +1348,15 @@ impl<'a> Parser<'a> {
}
}

/// parse the ESCAPE CHAR portion of LIKE, ILIKE, and SIMILAR TO
pub fn parse_escape_char(&mut self) -> Result<Option<char>, ParserError> {
if self.parse_keyword(Keyword::ESCAPE) {
Ok(Some(self.parse_literal_char()?))
} else {
Ok(None)
}
}

pub fn parse_array_index(&mut self, expr: Expr) -> Result<Expr, ParserError> {
let index = self.parse_expr()?;
self.expect_token(&Token::RBracket)?;
Expand Down Expand Up @@ -1463,6 +1487,7 @@ impl<'a> Parser<'a> {
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),
_ => Ok(0),
},
Token::Word(w) if w.keyword == Keyword::IS => Ok(17),
Expand All @@ -1471,6 +1496,7 @@ impl<'a> Parser<'a> {
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::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
Loading

0 comments on commit f07063f

Please sign in to comment.