Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support ESCAPE on LIKE #135

Merged
merged 2 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/binder/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ impl<'a, T: Transaction> Binder<'a, T> {
negated,
expr,
pattern,
..
} => self.bind_like(*negated, expr, pattern),
escape_char,
} => self.bind_like(*negated, expr, pattern, escape_char),
Expr::IsNull(expr) => self.bind_is_null(expr, false),
Expr::IsNotNull(expr) => self.bind_is_null(expr, true),
Expr::InList {
Expand Down Expand Up @@ -100,13 +100,14 @@ impl<'a, T: Transaction> Binder<'a, T> {
negated: bool,
expr: &Expr,
pattern: &Expr,
escape_char: &Option<char>,
) -> Result<ScalarExpression, DatabaseError> {
let left_expr = Box::new(self.bind_expr(expr)?);
let right_expr = Box::new(self.bind_expr(pattern)?);
let op = if negated {
expression::BinaryOperator::NotLike
expression::BinaryOperator::NotLike(*escape_char)
} else {
expression::BinaryOperator::Like
expression::BinaryOperator::Like(*escape_char)
};
Ok(ScalarExpression::Binary {
op,
Expand Down
21 changes: 17 additions & 4 deletions src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,8 @@ pub enum BinaryOperator {
Spaceship,
Eq,
NotEq,
Like,
NotLike,
Like(Option<char>),
NotLike(Option<char>),

And,
Or,
Expand All @@ -367,6 +367,13 @@ impl fmt::Display for ScalarExpression {

impl fmt::Display for BinaryOperator {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let like_op = |f: &mut Formatter, escape_char: &Option<char>| {
if let Some(escape_char) = escape_char {
write!(f, "(escape: {})", escape_char)?;
}
Ok(())
};

match self {
BinaryOperator::Plus => write!(f, "+"),
BinaryOperator::Minus => write!(f, "-"),
Expand All @@ -384,8 +391,14 @@ impl fmt::Display for BinaryOperator {
BinaryOperator::And => write!(f, "&&"),
BinaryOperator::Or => write!(f, "||"),
BinaryOperator::Xor => write!(f, "^"),
BinaryOperator::Like => write!(f, "like"),
BinaryOperator::NotLike => write!(f, "not like"),
BinaryOperator::Like(escape_char) => {
write!(f, "like")?;
like_op(f, escape_char)
}
BinaryOperator::NotLike(escape_char) => {
write!(f, "not like")?;
like_op(f, escape_char)
}
}
}
}
Expand Down
22 changes: 18 additions & 4 deletions src/expression/value_compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,18 +181,32 @@ pub fn binary_op(
right: &DataValue,
op: &BinaryOperator,
) -> Result<DataValue, DatabaseError> {
if matches!(op, BinaryOperator::Like | BinaryOperator::NotLike) {
if let BinaryOperator::Like(escape_char) | BinaryOperator::NotLike(escape_char) = op {
let value_option = unpack_utf8(left.clone().cast(&LogicalType::Varchar(None))?);
let pattern_option = unpack_utf8(right.clone().cast(&LogicalType::Varchar(None))?);

let mut is_match = if let (Some(value), Some(pattern)) = (value_option, pattern_option) {
let regex_pattern = pattern.replace('%', ".*").replace('_', ".");
let mut regex_pattern = String::new();
let mut chars = pattern.chars().peekable();
while let Some(c) = chars.next() {
if matches!(escape_char.map(|escape_c| escape_c == c), Some(true)) {
if let Some(next_char) = chars.next() {
regex_pattern.push(next_char);
}
} else if c == '%' {
regex_pattern.push_str(".*");
} else if c == '_' {
regex_pattern.push('.');
} else {
regex_pattern.push(c);
}
}

Regex::new(&regex_pattern).unwrap().is_match(&value)
} else {
unreachable!("The left and right values calculated by Like cannot be Null values.")
return Ok(DataValue::Boolean(None));
};
if op == &BinaryOperator::NotLike {
if matches!(op, BinaryOperator::NotLike(_)) {
is_match = !is_match;
}
return Ok(DataValue::Boolean(Some(is_match)));
Expand Down
76 changes: 54 additions & 22 deletions tests/slt/filter.slt
Original file line number Diff line number Diff line change
Expand Up @@ -71,104 +71,136 @@ statement ok
create table t1(id int primary key, v1 varchar)

statement ok
insert into t1 values (0, 'KipSQL'), (1, 'KipDB'), (2, 'KipBlog'), (3, 'Cool!');
insert into t1 values (0, 'KipSQL'), (1, 'KipDB'), (2, 'KipBlog'), (3, 'Cool!'), (4, 'F%ck')

query II
query IT
select * from t1 where v1 like 'Kip%'
----
0 KipSQL
1 KipDB
2 KipBlog

query II
query IT
select * from t1 where v1 not like 'Kip%'
----
3 Cool!
4 F%ck

query II
query IT
select * from t1 where v1 like 'F@%ck' escape '@'
----
4 F%ck

query IT
select * from t1 where v1 like 'KipD_'
----
1 KipDB

query II
query IT
select * from t1 where v1 like 'KipS_L'
----
0 KipSQL

query II
query IT
select * from t1 where v1 like 'K%L'
----
0 KipSQL

query II
query IT
select * from t1 where v1 like null
----

query IT
select * from t1 where null like 'K%L'
----

query IT
select * from t1 where null like null
----

query IT
select * from t1 where v1 not like null
----

query IT
select * from t1 where null not like 'K%L'
----

query IT
select * from t1 where null not like null
----

query IT
select * from t1 where id in (1, 2)
----
1 KipDB
2 KipBlog

query II
query IT
select * from t1 where id not in (1, 2)
----
0 KipSQL
3 Cool!
4 F%ck

query II
query IT
select * from t1 where id in (1, null)
----

query II
query IT
select * from t1 where null in (1, 2)
----

query II
query IT
select * from t1 where null in (1, null)
----

query II
query IT
select * from t1 where id not in (1, null)
----

query II
query IT
select * from t1 where null not in (1, 2)
----

query II
query IT
select * from t1 where null not in (1, null)
----

query II
query IT
select * from t1 where id between 1 and 2
----
1 KipDB
2 KipBlog

query II
query IT
select * from t1 where id not between 1 and 2
----
0 KipSQL
3 Cool!
4 F%ck

query II
query IT
select * from t1 where id between 1 and null
----

query II
query IT
select * from t1 where null between 1 and 2
----

query II
query IT
select * from t1 where null between 1 and null
----

query II
query IT
select * from t1 where id not between 1 and null
----

query II
query IT
select * from t1 where null not between 1 and 2
----

query II
query IT
select * from t1 where null not between 1 and null
----

Expand Down
16 changes: 8 additions & 8 deletions tests/slt/sql_2016/E061_05.slt
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# E061-05: LIKE predicate: ESCAPE clause

# TODO: support `ESCAPE` on `LIKE`
statement ok
CREATE TABLE TABLE_E061_05_01_01 ( ID INT PRIMARY KEY, A VARCHAR ( 255 ) );

# statement ok
# CREATE TABLE TABLE_E061_05_01_01 ( ID INT PRIMARY KEY, A VARCHAR ( 255 ) );
query T
SELECT A FROM TABLE_E061_05_01_01 WHERE A LIKE 'foo' ESCAPE 'f'

# SELECT A FROM TABLE_E061_05_01_01 WHERE A LIKE 'foo' ESCAPE 'f'
statement ok
CREATE TABLE TABLE_E061_05_01_02 ( ID INT PRIMARY KEY, A VARCHAR ( 255 ) );

# statement ok
# CREATE TABLE TABLE_E061_05_01_02 ( ID INT PRIMARY KEY, A VARCHAR ( 255 ) );

# SELECT A FROM TABLE_E061_05_01_02 WHERE A NOT LIKE 'foo' ESCAPE 'f'
query T
SELECT A FROM TABLE_E061_05_01_02 WHERE A NOT LIKE 'foo' ESCAPE 'f'
Loading