Skip to content

Commit

Permalink
Add SQL planner support for Like, ILike and SimilarTo, with opt…
Browse files Browse the repository at this point in the history
…ional escape character (#3101)

* Make Like a top-level Expr

* revert some changes

* add type validation

* Revert physical plan changes and reduce scope of the PR

* Revert more changes

* Revert more changes

* clippy

* address feedback

* revert change to test

* revert more changes
  • Loading branch information
andygrove committed Sep 9, 2022
1 parent 73447b5 commit c96f03e
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 21 deletions.
20 changes: 20 additions & 0 deletions datafusion/physical-expr/src/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,26 @@ pub fn create_physical_expr(
}
}
}
Expr::Like {
negated,
expr,
pattern,
escape_char,
} => {
if escape_char.is_some() {
return Err(DataFusionError::Execution(
"LIKE does not support escape_char".to_string(),
));
}
let op = if *negated {
Operator::NotLike
} else {
Operator::Like
};
let bin_expr =
binary_expr(expr.as_ref().clone(), op, pattern.as_ref().clone());
create_physical_expr(&bin_expr, input_dfschema, input_schema, execution_props)
}
Expr::Case {
expr,
when_then_expr,
Expand Down
54 changes: 54 additions & 0 deletions datafusion/proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,60 @@ mod roundtrip_tests {
roundtrip_expr_test(test_expr, ctx);
}

#[test]
fn roundtrip_like() {
fn like(negated: bool, escape_char: Option<char>) {
let test_expr = Expr::Like {
negated,
expr: Box::new(col("col")),
pattern: Box::new(lit("[0-9]+")),
escape_char,
};
let ctx = SessionContext::new();
roundtrip_expr_test(test_expr, ctx);
}
like(true, Some('X'));
like(false, Some('\\'));
like(true, None);
like(false, None);
}

#[test]
fn roundtrip_ilike() {
fn ilike(negated: bool, escape_char: Option<char>) {
let test_expr = Expr::ILike {
negated,
expr: Box::new(col("col")),
pattern: Box::new(lit("[0-9]+")),
escape_char,
};
let ctx = SessionContext::new();
roundtrip_expr_test(test_expr, ctx);
}
ilike(true, Some('X'));
ilike(false, Some('\\'));
ilike(true, None);
ilike(false, None);
}

#[test]
fn roundtrip_similar_to() {
fn similar_to(negated: bool, escape_char: Option<char>) {
let test_expr = Expr::SimilarTo {
negated,
expr: Box::new(col("col")),
pattern: Box::new(lit("[0-9]+")),
escape_char,
};
let ctx = SessionContext::new();
roundtrip_expr_test(test_expr, ctx);
}
similar_to(true, Some('X'));
similar_to(false, Some('\\'));
similar_to(true, None);
similar_to(false, None);
}

#[test]
fn roundtrip_count() {
let test_expr = Expr::AggregateFunction {
Expand Down
64 changes: 43 additions & 21 deletions datafusion/sql/src/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ use datafusion_expr::utils::{
COUNT_STAR_EXPANSION,
};
use datafusion_expr::{
and, col, lit, AggregateFunction, AggregateUDF, Expr, Operator, ScalarUDF,
WindowFrame, WindowFrameUnits,
and, col, lit, AggregateFunction, AggregateUDF, Expr, ExprSchemable, Operator,
ScalarUDF, WindowFrame, WindowFrameUnits,
};
use datafusion_expr::{
window_function::WindowFunction, BuiltinScalarFunction, TableSource,
Expand Down Expand Up @@ -1939,30 +1939,52 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
}

SQLExpr::Like { negated, expr, pattern, escape_char } => {
match escape_char {
Some(_) => {
// to support this we will need to introduce `Expr::Like` instead
// of treating it like a binary expression
Err(DataFusionError::NotImplemented("LIKE with ESCAPE is not yet supported".to_string()))
},
_ => {
Ok(Expr::BinaryExpr {
left: Box::new(self.sql_expr_to_logical_expr(*expr, schema, ctes)?),
op: if negated { Operator::NotLike } else { Operator::Like },
right: Box::new(self.sql_expr_to_logical_expr(*pattern, schema, ctes)?),
})
}
let pattern = self.sql_expr_to_logical_expr(*pattern, schema, ctes)?;
let pattern_type = pattern.get_type(schema)?;
if pattern_type != DataType::Utf8 && pattern_type != DataType::Null {
return Err(DataFusionError::Plan(
"Invalid pattern in LIKE expression".to_string(),
));
}
Ok(Expr::Like {
negated,
expr: Box::new(self.sql_expr_to_logical_expr(*expr, schema, ctes)?),
pattern: Box::new(pattern),
escape_char

})
}

SQLExpr::ILike { .. } => {
// https://github.com/apache/arrow-datafusion/issues/3099
Err(DataFusionError::NotImplemented("ILIKE is not yet supported".to_string()))
SQLExpr::ILike { negated, expr, pattern, escape_char } => {
let pattern = self.sql_expr_to_logical_expr(*pattern, schema, ctes)?;
let pattern_type = pattern.get_type(schema)?;
if pattern_type != DataType::Utf8 && pattern_type != DataType::Null {
return Err(DataFusionError::Plan(
"Invalid pattern in ILIKE expression".to_string(),
));
}
Ok(Expr::ILike {
negated,
expr: Box::new(self.sql_expr_to_logical_expr(*expr, schema, ctes)?),
pattern: Box::new(pattern),
escape_char
})
}

SQLExpr::SimilarTo { .. } => {
// https://github.com/apache/arrow-datafusion/issues/3099
Err(DataFusionError::NotImplemented("SIMILAR TO is not yet supported".to_string()))
SQLExpr::SimilarTo { negated, expr, pattern, escape_char } => {
let pattern = self.sql_expr_to_logical_expr(*pattern, schema, ctes)?;
let pattern_type = pattern.get_type(schema)?;
if pattern_type != DataType::Utf8 && pattern_type != DataType::Null {
return Err(DataFusionError::Plan(
"Invalid pattern in SIMILAR TO expression".to_string(),
));
}
Ok(Expr::SimilarTo {
negated,
expr: Box::new(self.sql_expr_to_logical_expr(*expr, schema, ctes)?),
pattern: Box::new(pattern),
escape_char
})
}

SQLExpr::BinaryOp {
Expand Down

0 comments on commit c96f03e

Please sign in to comment.