diff --git a/datafusion/physical-expr/src/planner.rs b/datafusion/physical-expr/src/planner.rs index 9a3ad2a8d0d9f..7e31dea0acb2d 100644 --- a/datafusion/physical-expr/src/planner.rs +++ b/datafusion/physical-expr/src/planner.rs @@ -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, diff --git a/datafusion/proto/src/lib.rs b/datafusion/proto/src/lib.rs index 7e009a846e698..7c8a760744a64 100644 --- a/datafusion/proto/src/lib.rs +++ b/datafusion/proto/src/lib.rs @@ -929,6 +929,60 @@ mod roundtrip_tests { roundtrip_expr_test(test_expr, ctx); } + #[test] + fn roundtrip_like() { + fn like(negated: bool, escape_char: Option) { + 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) { + 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) { + 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 { diff --git a/datafusion/sql/src/planner.rs b/datafusion/sql/src/planner.rs index d9dbdf1436878..dd44459f5081e 100644 --- a/datafusion/sql/src/planner.rs +++ b/datafusion/sql/src/planner.rs @@ -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, @@ -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 {