Skip to content

Commit f07063f

Browse files
authored
Support for SIMILAR TO syntax, change Like and ILike to Expr 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
1 parent 18881f8 commit f07063f

File tree

4 files changed

+237
-57
lines changed

4 files changed

+237
-57
lines changed

src/ast/mod.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,27 @@ pub enum Expr {
268268
op: BinaryOperator,
269269
right: Box<Expr>,
270270
},
271+
/// LIKE
272+
Like {
273+
negated: bool,
274+
expr: Box<Expr>,
275+
pattern: Box<Value>,
276+
escape_char: Option<char>,
277+
},
278+
/// ILIKE (case-insensitive LIKE)
279+
ILike {
280+
negated: bool,
281+
expr: Box<Expr>,
282+
pattern: Box<Value>,
283+
escape_char: Option<char>,
284+
},
285+
/// SIMILAR TO regex
286+
SimilarTo {
287+
negated: bool,
288+
expr: Box<Expr>,
289+
pattern: Box<Value>,
290+
escape_char: Option<char>,
291+
},
271292
/// Any operation e.g. `1 ANY (1)` or `foo > ANY(bar)`, It will be wrapped in the right side of BinaryExpr
272293
AnyOp(Box<Expr>),
273294
/// ALL operation e.g. `1 ALL (1)` or `foo > ALL(bar)`, It will be wrapped in the right side of BinaryExpr
@@ -438,6 +459,72 @@ impl fmt::Display for Expr {
438459
high
439460
),
440461
Expr::BinaryOp { left, op, right } => write!(f, "{} {} {}", left, op, right),
462+
Expr::Like {
463+
negated,
464+
expr,
465+
pattern,
466+
escape_char,
467+
} => match escape_char {
468+
Some(ch) => write!(
469+
f,
470+
"{} {}LIKE {} ESCAPE '{}'",
471+
expr,
472+
if *negated { "NOT " } else { "" },
473+
pattern,
474+
ch
475+
),
476+
_ => write!(
477+
f,
478+
"{} {}LIKE {}",
479+
expr,
480+
if *negated { "NOT " } else { "" },
481+
pattern
482+
),
483+
},
484+
Expr::ILike {
485+
negated,
486+
expr,
487+
pattern,
488+
escape_char,
489+
} => match escape_char {
490+
Some(ch) => write!(
491+
f,
492+
"{} {}ILIKE {} ESCAPE '{}'",
493+
expr,
494+
if *negated { "NOT " } else { "" },
495+
pattern,
496+
ch
497+
),
498+
_ => write!(
499+
f,
500+
"{} {}ILIKE {}",
501+
expr,
502+
if *negated { "NOT " } else { "" },
503+
pattern
504+
),
505+
},
506+
Expr::SimilarTo {
507+
negated,
508+
expr,
509+
pattern,
510+
escape_char,
511+
} => match escape_char {
512+
Some(ch) => write!(
513+
f,
514+
"{} {}SIMILAR TO {} ESCAPE '{}'",
515+
expr,
516+
if *negated { "NOT " } else { "" },
517+
pattern,
518+
ch
519+
),
520+
_ => write!(
521+
f,
522+
"{} {}SIMILAR TO {}",
523+
expr,
524+
if *negated { "NOT " } else { "" },
525+
pattern
526+
),
527+
},
441528
Expr::AnyOp(expr) => write!(f, "ANY({})", expr),
442529
Expr::AllOp(expr) => write!(f, "ALL({})", expr),
443530
Expr::UnaryOp { op, expr } => {

src/ast/operator.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,6 @@ pub enum BinaryOperator {
7676
And,
7777
Or,
7878
Xor,
79-
Like,
80-
NotLike,
81-
ILike,
82-
NotILike,
8379
BitwiseOr,
8480
BitwiseAnd,
8581
BitwiseXor,
@@ -116,10 +112,6 @@ impl fmt::Display for BinaryOperator {
116112
BinaryOperator::And => f.write_str("AND"),
117113
BinaryOperator::Or => f.write_str("OR"),
118114
BinaryOperator::Xor => f.write_str("XOR"),
119-
BinaryOperator::Like => f.write_str("LIKE"),
120-
BinaryOperator::NotLike => f.write_str("NOT LIKE"),
121-
BinaryOperator::ILike => f.write_str("ILIKE"),
122-
BinaryOperator::NotILike => f.write_str("NOT ILIKE"),
123115
BinaryOperator::BitwiseOr => f.write_str("|"),
124116
BinaryOperator::BitwiseAnd => f.write_str("&"),
125117
BinaryOperator::BitwiseXor => f.write_str("^"),

src/parser.rs

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,17 +1178,6 @@ impl<'a> Parser<'a> {
11781178
Token::Word(w) => match w.keyword {
11791179
Keyword::AND => Some(BinaryOperator::And),
11801180
Keyword::OR => Some(BinaryOperator::Or),
1181-
Keyword::LIKE => Some(BinaryOperator::Like),
1182-
Keyword::ILIKE => Some(BinaryOperator::ILike),
1183-
Keyword::NOT => {
1184-
if self.parse_keyword(Keyword::LIKE) {
1185-
Some(BinaryOperator::NotLike)
1186-
} else if self.parse_keyword(Keyword::ILIKE) {
1187-
Some(BinaryOperator::NotILike)
1188-
} else {
1189-
None
1190-
}
1191-
}
11921181
Keyword::XOR => Some(BinaryOperator::Xor),
11931182
Keyword::OPERATOR if dialect_of!(self is PostgreSqlDialect | GenericDialect) => {
11941183
self.expect_token(&Token::LParen)?;
@@ -1282,13 +1271,39 @@ impl<'a> Parser<'a> {
12821271
self.expected("Expected Token::Word after AT", tok)
12831272
}
12841273
}
1285-
Keyword::NOT | Keyword::IN | Keyword::BETWEEN => {
1274+
Keyword::NOT
1275+
| Keyword::IN
1276+
| Keyword::BETWEEN
1277+
| Keyword::LIKE
1278+
| Keyword::ILIKE
1279+
| Keyword::SIMILAR => {
12861280
self.prev_token();
12871281
let negated = self.parse_keyword(Keyword::NOT);
12881282
if self.parse_keyword(Keyword::IN) {
12891283
self.parse_in(expr, negated)
12901284
} else if self.parse_keyword(Keyword::BETWEEN) {
12911285
self.parse_between(expr, negated)
1286+
} else if self.parse_keyword(Keyword::LIKE) {
1287+
Ok(Expr::Like {
1288+
negated,
1289+
expr: Box::new(expr),
1290+
pattern: Box::new(self.parse_value()?),
1291+
escape_char: self.parse_escape_char()?,
1292+
})
1293+
} else if self.parse_keyword(Keyword::ILIKE) {
1294+
Ok(Expr::ILike {
1295+
negated,
1296+
expr: Box::new(expr),
1297+
pattern: Box::new(self.parse_value()?),
1298+
escape_char: self.parse_escape_char()?,
1299+
})
1300+
} else if self.parse_keywords(&[Keyword::SIMILAR, Keyword::TO]) {
1301+
Ok(Expr::SimilarTo {
1302+
negated,
1303+
expr: Box::new(expr),
1304+
pattern: Box::new(self.parse_value()?),
1305+
escape_char: self.parse_escape_char()?,
1306+
})
12921307
} else {
12931308
self.expected("IN or BETWEEN after NOT", self.peek_token())
12941309
}
@@ -1333,6 +1348,15 @@ impl<'a> Parser<'a> {
13331348
}
13341349
}
13351350

1351+
/// parse the ESCAPE CHAR portion of LIKE, ILIKE, and SIMILAR TO
1352+
pub fn parse_escape_char(&mut self) -> Result<Option<char>, ParserError> {
1353+
if self.parse_keyword(Keyword::ESCAPE) {
1354+
Ok(Some(self.parse_literal_char()?))
1355+
} else {
1356+
Ok(None)
1357+
}
1358+
}
1359+
13361360
pub fn parse_array_index(&mut self, expr: Expr) -> Result<Expr, ParserError> {
13371361
let index = self.parse_expr()?;
13381362
self.expect_token(&Token::RBracket)?;
@@ -1463,6 +1487,7 @@ impl<'a> Parser<'a> {
14631487
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
14641488
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
14651489
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
1490+
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
14661491
_ => Ok(0),
14671492
},
14681493
Token::Word(w) if w.keyword == Keyword::IS => Ok(17),
@@ -1471,6 +1496,7 @@ impl<'a> Parser<'a> {
14711496
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
14721497
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
14731498
Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(Self::BETWEEN_PREC),
1499+
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
14741500
Token::Eq
14751501
| Token::Lt
14761502
| Token::LtEq

0 commit comments

Comments
 (0)