Skip to content

Commit 53716f1

Browse files
ayushdgMazterQyou
authored andcommitted
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 883cc6a commit 53716f1

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
@@ -232,6 +232,27 @@ pub enum Expr {
232232
op: BinaryOperator,
233233
right: Box<Expr>,
234234
},
235+
/// LIKE
236+
Like {
237+
negated: bool,
238+
expr: Box<Expr>,
239+
pattern: Box<Value>,
240+
escape_char: Option<char>,
241+
},
242+
/// ILIKE (case-insensitive LIKE)
243+
ILike {
244+
negated: bool,
245+
expr: Box<Expr>,
246+
pattern: Box<Value>,
247+
escape_char: Option<char>,
248+
},
249+
/// SIMILAR TO regex
250+
SimilarTo {
251+
negated: bool,
252+
expr: Box<Expr>,
253+
pattern: Box<Value>,
254+
escape_char: Option<char>,
255+
},
235256
/// Any operation e.g. `1 ANY (1)` or `foo > ANY(bar)`, It will be wrapped in the right side of BinaryExpr
236257
AnyOp(Box<Expr>),
237258
/// ALL operation e.g. `1 ALL (1)` or `foo > ALL(bar)`, It will be wrapped in the right side of BinaryExpr
@@ -411,6 +432,72 @@ impl fmt::Display for Expr {
411432
high
412433
),
413434
Expr::BinaryOp { left, op, right } => write!(f, "{} {} {}", left, op, right),
435+
Expr::Like {
436+
negated,
437+
expr,
438+
pattern,
439+
escape_char,
440+
} => match escape_char {
441+
Some(ch) => write!(
442+
f,
443+
"{} {}LIKE {} ESCAPE '{}'",
444+
expr,
445+
if *negated { "NOT " } else { "" },
446+
pattern,
447+
ch
448+
),
449+
_ => write!(
450+
f,
451+
"{} {}LIKE {}",
452+
expr,
453+
if *negated { "NOT " } else { "" },
454+
pattern
455+
),
456+
},
457+
Expr::ILike {
458+
negated,
459+
expr,
460+
pattern,
461+
escape_char,
462+
} => match escape_char {
463+
Some(ch) => write!(
464+
f,
465+
"{} {}ILIKE {} ESCAPE '{}'",
466+
expr,
467+
if *negated { "NOT " } else { "" },
468+
pattern,
469+
ch
470+
),
471+
_ => write!(
472+
f,
473+
"{} {}ILIKE {}",
474+
expr,
475+
if *negated { "NOT " } else { "" },
476+
pattern
477+
),
478+
},
479+
Expr::SimilarTo {
480+
negated,
481+
expr,
482+
pattern,
483+
escape_char,
484+
} => match escape_char {
485+
Some(ch) => write!(
486+
f,
487+
"{} {}SIMILAR TO {} ESCAPE '{}'",
488+
expr,
489+
if *negated { "NOT " } else { "" },
490+
pattern,
491+
ch
492+
),
493+
_ => write!(
494+
f,
495+
"{} {}SIMILAR TO {}",
496+
expr,
497+
if *negated { "NOT " } else { "" },
498+
pattern
499+
),
500+
},
414501
Expr::AnyOp(expr) => write!(f, "ANY({})", expr),
415502
Expr::AllOp(expr) => write!(f, "ALL({})", expr),
416503
Expr::UnaryOp { op, expr } => {

src/ast/operator.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,6 @@ pub enum BinaryOperator {
7272
And,
7373
Or,
7474
Xor,
75-
Like,
76-
NotLike,
77-
ILike,
78-
NotILike,
7975
BitwiseOr,
8076
BitwiseAnd,
8177
BitwiseXor,
@@ -107,10 +103,6 @@ impl fmt::Display for BinaryOperator {
107103
BinaryOperator::And => "AND",
108104
BinaryOperator::Or => "OR",
109105
BinaryOperator::Xor => "XOR",
110-
BinaryOperator::Like => "LIKE",
111-
BinaryOperator::NotLike => "NOT LIKE",
112-
BinaryOperator::ILike => "ILIKE",
113-
BinaryOperator::NotILike => "NOT ILIKE",
114106
BinaryOperator::BitwiseOr => "|",
115107
BinaryOperator::BitwiseAnd => "&",
116108
BinaryOperator::BitwiseXor => "^",

src/parser.rs

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,17 +1190,6 @@ impl<'a> Parser<'a> {
11901190
Token::Word(w) => match w.keyword {
11911191
Keyword::AND => Some(BinaryOperator::And),
11921192
Keyword::OR => Some(BinaryOperator::Or),
1193-
Keyword::LIKE => Some(BinaryOperator::Like),
1194-
Keyword::ILIKE => Some(BinaryOperator::ILike),
1195-
Keyword::NOT => {
1196-
if self.parse_keyword(Keyword::LIKE) {
1197-
Some(BinaryOperator::NotLike)
1198-
} else if self.parse_keyword(Keyword::ILIKE) {
1199-
Some(BinaryOperator::NotILike)
1200-
} else {
1201-
None
1202-
}
1203-
}
12041193
Keyword::XOR => Some(BinaryOperator::Xor),
12051194
_ => None,
12061195
},
@@ -1286,13 +1275,39 @@ impl<'a> Parser<'a> {
12861275
self.expected("Expected Token::Word after AT", tok)
12871276
}
12881277
}
1289-
Keyword::NOT | Keyword::IN | Keyword::BETWEEN => {
1278+
Keyword::NOT
1279+
| Keyword::IN
1280+
| Keyword::BETWEEN
1281+
| Keyword::LIKE
1282+
| Keyword::ILIKE
1283+
| Keyword::SIMILAR => {
12901284
self.prev_token();
12911285
let negated = self.parse_keyword(Keyword::NOT);
12921286
if self.parse_keyword(Keyword::IN) {
12931287
self.parse_in(expr, negated)
12941288
} else if self.parse_keyword(Keyword::BETWEEN) {
12951289
self.parse_between(expr, negated)
1290+
} else if self.parse_keyword(Keyword::LIKE) {
1291+
Ok(Expr::Like {
1292+
negated,
1293+
expr: Box::new(expr),
1294+
pattern: Box::new(self.parse_value()?),
1295+
escape_char: self.parse_escape_char()?,
1296+
})
1297+
} else if self.parse_keyword(Keyword::ILIKE) {
1298+
Ok(Expr::ILike {
1299+
negated,
1300+
expr: Box::new(expr),
1301+
pattern: Box::new(self.parse_value()?),
1302+
escape_char: self.parse_escape_char()?,
1303+
})
1304+
} else if self.parse_keywords(&[Keyword::SIMILAR, Keyword::TO]) {
1305+
Ok(Expr::SimilarTo {
1306+
negated,
1307+
expr: Box::new(expr),
1308+
pattern: Box::new(self.parse_value()?),
1309+
escape_char: self.parse_escape_char()?,
1310+
})
12961311
} else {
12971312
self.expected("IN or BETWEEN after NOT", self.peek_token())
12981313
}
@@ -1320,6 +1335,15 @@ impl<'a> Parser<'a> {
13201335
}
13211336
}
13221337

1338+
/// parse the ESCAPE CHAR portion of LIKE, ILIKE, and SIMILAR TO
1339+
pub fn parse_escape_char(&mut self) -> Result<Option<char>, ParserError> {
1340+
if self.parse_keyword(Keyword::ESCAPE) {
1341+
Ok(Some(self.parse_literal_char()?))
1342+
} else {
1343+
Ok(None)
1344+
}
1345+
}
1346+
13231347
pub fn parse_array_index(&mut self, expr: Expr) -> Result<Expr, ParserError> {
13241348
let index = self.parse_expr()?;
13251349
self.expect_token(&Token::RBracket)?;
@@ -1450,13 +1474,15 @@ impl<'a> Parser<'a> {
14501474
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
14511475
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
14521476
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
1477+
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
14531478
_ => Ok(0),
14541479
},
14551480
Token::Word(w) if w.keyword == Keyword::IS => Ok(17),
14561481
Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC),
14571482
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
14581483
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
14591484
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
1485+
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(Self::BETWEEN_PREC),
14601486
Token::Eq
14611487
| Token::Lt
14621488
| Token::LtEq

0 commit comments

Comments
 (0)