This is an automated email from the ASF dual-hosted git repository.

liukun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-datafusion.git


The following commit(s) were added to refs/heads/master by this push:
     new 11abfb928 type coercion: support is/is_not_`bool`/like/unknown expr 
(#3510)
11abfb928 is described below

commit 11abfb928b23a670292c05406ad52fb76d295ee3
Author: Kun Liu <[email protected]>
AuthorDate: Wed Sep 21 09:43:25 2022 +0800

    type coercion: support is/is_not_`bool`/like/unknown expr (#3510)
    
    * type coercion: support the boolen op, is_true, is_not_true, is_false, 
is_not_false
    
    * Apply suggestions from code review
    
    Co-authored-by: Daniël Heres <[email protected]>
    
    * suport like,unknown for type coercion
    
    * Update datafusion/optimizer/src/type_coercion.rs
    
    Co-authored-by: Andrew Lamb <[email protected]>
    
    Co-authored-by: Daniël Heres <[email protected]>
    Co-authored-by: Andrew Lamb <[email protected]>
---
 datafusion/expr/src/expr.rs               |  30 ++++
 datafusion/expr/src/expr_fn.rs            |  30 ++++
 datafusion/optimizer/src/type_coercion.rs | 281 +++++++++++++++++++++++++++++-
 3 files changed, 336 insertions(+), 5 deletions(-)

diff --git a/datafusion/expr/src/expr.rs b/datafusion/expr/src/expr.rs
index 8b90fb9e4..8cdf53c26 100644
--- a/datafusion/expr/src/expr.rs
+++ b/datafusion/expr/src/expr.rs
@@ -497,6 +497,36 @@ impl Expr {
         }
     }
 
+    /// Return `IsTrue(Box(self))`
+    pub fn is_true(self) -> Expr {
+        Expr::IsTrue(Box::new(self))
+    }
+
+    /// Return `IsNotTrue(Box(self))`
+    pub fn is_not_true(self) -> Expr {
+        Expr::IsNotTrue(Box::new(self))
+    }
+
+    /// Return `IsFalse(Box(self))`
+    pub fn is_false(self) -> Expr {
+        Expr::IsFalse(Box::new(self))
+    }
+
+    /// Return `IsNotFalse(Box(self))`
+    pub fn is_not_false(self) -> Expr {
+        Expr::IsNotFalse(Box::new(self))
+    }
+
+    /// Return `IsUnknown(Box(self))`
+    pub fn is_unknown(self) -> Expr {
+        Expr::IsUnknown(Box::new(self))
+    }
+
+    /// Return `IsNotUnknown(Box(self))`
+    pub fn is_not_unknown(self) -> Expr {
+        Expr::IsNotUnknown(Box::new(self))
+    }
+
     pub fn try_into_col(&self) -> Result<Column> {
         match self {
             Expr::Column(it) => Ok(it.clone()),
diff --git a/datafusion/expr/src/expr_fn.rs b/datafusion/expr/src/expr_fn.rs
index 8d89f6f41..7b11b6304 100644
--- a/datafusion/expr/src/expr_fn.rs
+++ b/datafusion/expr/src/expr_fn.rs
@@ -282,6 +282,36 @@ pub fn is_null(expr: Expr) -> Expr {
     Expr::IsNull(Box::new(expr))
 }
 
+/// Create is true expression
+pub fn is_true(expr: Expr) -> Expr {
+    Expr::IsTrue(Box::new(expr))
+}
+
+/// Create is not true expression
+pub fn is_not_true(expr: Expr) -> Expr {
+    Expr::IsNotTrue(Box::new(expr))
+}
+
+/// Create is false expression
+pub fn is_false(expr: Expr) -> Expr {
+    Expr::IsFalse(Box::new(expr))
+}
+
+/// Create is not false expression
+pub fn is_not_false(expr: Expr) -> Expr {
+    Expr::IsNotFalse(Box::new(expr))
+}
+
+/// Create is unknown expression
+pub fn is_unknown(expr: Expr) -> Expr {
+    Expr::IsUnknown(Box::new(expr))
+}
+
+/// Create is not unknown expression
+pub fn is_not_unknown(expr: Expr) -> Expr {
+    Expr::IsNotUnknown(Box::new(expr))
+}
+
 /// Create an convenience function representing a unary scalar function
 macro_rules! unary_scalar_expr {
     ($ENUM:ident, $FUNC:ident, $DOC:expr) => {
diff --git a/datafusion/optimizer/src/type_coercion.rs 
b/datafusion/optimizer/src/type_coercion.rs
index 9d8e97f36..7be327713 100644
--- a/datafusion/optimizer/src/type_coercion.rs
+++ b/datafusion/optimizer/src/type_coercion.rs
@@ -25,7 +25,10 @@ use datafusion_expr::binary_rule::{coerce_types, 
comparison_coercion};
 use datafusion_expr::expr_rewriter::{ExprRewritable, ExprRewriter, 
RewriteRecursion};
 use datafusion_expr::type_coercion::data_types;
 use datafusion_expr::utils::from_plan;
-use datafusion_expr::{Expr, LogicalPlan};
+use datafusion_expr::{
+    is_false, is_not_false, is_not_true, is_not_unknown, is_true, is_unknown, 
Expr,
+    LogicalPlan, Operator,
+};
 use datafusion_expr::{ExprSchemable, Signature};
 use datafusion_physical_expr::execution_props::ExecutionProps;
 use std::sync::Arc;
@@ -98,6 +101,81 @@ impl ExprRewriter for TypeCoercionRewriter<'_> {
 
     fn mutate(&mut self, expr: Expr) -> Result<Expr> {
         match expr {
+            Expr::IsTrue(expr) => {
+                let expr = is_true(get_casted_expr_for_bool_op(&expr, 
&self.schema)?);
+                expr.rewrite(&mut self.const_evaluator)
+            }
+            Expr::IsNotTrue(expr) => {
+                let expr = is_not_true(get_casted_expr_for_bool_op(&expr, 
&self.schema)?);
+                expr.rewrite(&mut self.const_evaluator)
+            }
+            Expr::IsFalse(expr) => {
+                let expr = is_false(get_casted_expr_for_bool_op(&expr, 
&self.schema)?);
+                expr.rewrite(&mut self.const_evaluator)
+            }
+            Expr::IsNotFalse(expr) => {
+                let expr =
+                    is_not_false(get_casted_expr_for_bool_op(&expr, 
&self.schema)?);
+                expr.rewrite(&mut self.const_evaluator)
+            }
+            Expr::Like {
+                negated,
+                expr,
+                pattern,
+                escape_char,
+            } => {
+                let left_type = expr.get_type(&self.schema)?;
+                let right_type = pattern.get_type(&self.schema)?;
+                let coerced_type =
+                    coerce_types(&left_type, &Operator::Like, &right_type)?;
+                let expr = Box::new(expr.cast_to(&coerced_type, 
&self.schema)?);
+                let pattern = Box::new(pattern.cast_to(&coerced_type, 
&self.schema)?);
+                let expr = Expr::Like {
+                    negated,
+                    expr,
+                    pattern,
+                    escape_char,
+                };
+                expr.rewrite(&mut self.const_evaluator)
+            }
+            Expr::ILike {
+                negated,
+                expr,
+                pattern,
+                escape_char,
+            } => {
+                let left_type = expr.get_type(&self.schema)?;
+                let right_type = pattern.get_type(&self.schema)?;
+                let coerced_type =
+                    coerce_types(&left_type, &Operator::Like, &right_type)?;
+                let expr = Box::new(expr.cast_to(&coerced_type, 
&self.schema)?);
+                let pattern = Box::new(pattern.cast_to(&coerced_type, 
&self.schema)?);
+                let expr = Expr::ILike {
+                    negated,
+                    expr,
+                    pattern,
+                    escape_char,
+                };
+                expr.rewrite(&mut self.const_evaluator)
+            }
+            Expr::IsUnknown(expr) => {
+                // will convert the 
binary(expr,IsNotDistinctFrom,lit(Boolean(None));
+                let left_type = expr.get_type(&self.schema)?;
+                let right_type = DataType::Boolean;
+                let coerced_type =
+                    coerce_types(&left_type, &Operator::IsNotDistinctFrom, 
&right_type)?;
+                let expr = is_unknown(expr.cast_to(&coerced_type, 
&self.schema)?);
+                expr.rewrite(&mut self.const_evaluator)
+            }
+            Expr::IsNotUnknown(expr) => {
+                // will convert the 
binary(expr,IsDistinctFrom,lit(Boolean(None));
+                let left_type = expr.get_type(&self.schema)?;
+                let right_type = DataType::Boolean;
+                let coerced_type =
+                    coerce_types(&left_type, &Operator::IsDistinctFrom, 
&right_type)?;
+                let expr = is_not_unknown(expr.cast_to(&coerced_type, 
&self.schema)?);
+                expr.rewrite(&mut self.const_evaluator)
+            }
             Expr::BinaryExpr {
                 ref left,
                 op,
@@ -136,18 +214,34 @@ impl ExprRewriter for TypeCoercionRewriter<'_> {
             } => {
                 let expr_type = expr.get_type(&self.schema)?;
                 let low_type = low.get_type(&self.schema)?;
-                let coerced_type = comparison_coercion(&expr_type, &low_type)
+                let low_coerced_type = comparison_coercion(&expr_type, 
&low_type)
                     .ok_or_else(|| {
                         DataFusionError::Internal(format!(
                             "Failed to coerce types {} and {} in BETWEEN 
expression",
                             expr_type, low_type
                         ))
                     })?;
+                let high_type = high.get_type(&self.schema)?;
+                let high_coerced_type = comparison_coercion(&expr_type, 
&low_type)
+                    .ok_or_else(|| {
+                        DataFusionError::Internal(format!(
+                            "Failed to coerce types {} and {} in BETWEEN 
expression",
+                            expr_type, high_type
+                        ))
+                    })?;
+                let coercion_type =
+                    comparison_coercion(&low_coerced_type, &high_coerced_type)
+                        .ok_or_else(|| {
+                            DataFusionError::Internal(format!(
+                                "Failed to coerce types {} and {} in BETWEEN 
expression",
+                                expr_type, high_type
+                            ))
+                        })?;
                 let expr = Expr::Between {
-                    expr: Box::new(expr.cast_to(&coerced_type, &self.schema)?),
+                    expr: Box::new(expr.cast_to(&coercion_type, 
&self.schema)?),
                     negated,
-                    low: Box::new(low.cast_to(&coerced_type, &self.schema)?),
-                    high: Box::new(high.cast_to(&coerced_type, &self.schema)?),
+                    low: Box::new(low.cast_to(&coercion_type, &self.schema)?),
+                    high: Box::new(high.cast_to(&coercion_type, 
&self.schema)?),
                 };
                 expr.rewrite(&mut self.const_evaluator)
             }
@@ -203,6 +297,15 @@ impl ExprRewriter for TypeCoercionRewriter<'_> {
     }
 }
 
+// Support the `IsTrue` `IsNotTrue` `IsFalse` `IsNotFalse` type coercion.
+// The above op will be rewrite to the binary op when creating the physical op.
+fn get_casted_expr_for_bool_op(expr: &Expr, schema: &DFSchemaRef) -> 
Result<Expr> {
+    let left_type = expr.get_type(schema)?;
+    let right_type = DataType::Boolean;
+    let coerced_type = coerce_types(&left_type, &Operator::IsDistinctFrom, 
&right_type)?;
+    expr.clone().cast_to(&coerced_type, schema)
+}
+
 /// Attempts to coerce the types of `list_types` to be comparable with the
 /// `expr_type`.
 /// Returns the common data type for `expr_type` and `list_types`
@@ -440,10 +543,178 @@ mod test {
         Ok(())
     }
 
+    #[test]
+    fn is_bool_for_type_coercion() -> Result<()> {
+        // is true
+        let expr = col("a").is_true();
+        let empty = empty_with_type(DataType::Boolean);
+        let plan = LogicalPlan::Projection(Projection::try_new(
+            vec![expr.clone()],
+            empty,
+            None,
+        )?);
+        let rule = TypeCoercion::new();
+        let mut config = OptimizerConfig::default();
+        let plan = rule.optimize(&plan, &mut config).unwrap();
+        assert_eq!(
+            "Projection: #a IS TRUE\n  EmptyRelation",
+            &format!("{:?}", plan)
+        );
+        let empty = empty_with_type(DataType::Int64);
+        let plan = LogicalPlan::Projection(Projection::try_new(vec![expr], 
empty, None)?);
+        let plan = rule.optimize(&plan, &mut config);
+        assert!(plan.is_err());
+        assert!(plan.unwrap_err().to_string().contains("'Int64 IS DISTINCT 
FROM Boolean' can't be evaluated because there isn't a common type to coerce 
the types to"));
+
+        // is not true
+        let expr = col("a").is_not_true();
+        let empty = empty_with_type(DataType::Boolean);
+        let plan = LogicalPlan::Projection(Projection::try_new(vec![expr], 
empty, None)?);
+        let plan = rule.optimize(&plan, &mut config).unwrap();
+        assert_eq!(
+            "Projection: #a IS NOT TRUE\n  EmptyRelation",
+            &format!("{:?}", plan)
+        );
+
+        // is false
+        let expr = col("a").is_false();
+        let empty = empty_with_type(DataType::Boolean);
+        let plan = LogicalPlan::Projection(Projection::try_new(vec![expr], 
empty, None)?);
+        let plan = rule.optimize(&plan, &mut config).unwrap();
+        assert_eq!(
+            "Projection: #a IS FALSE\n  EmptyRelation",
+            &format!("{:?}", plan)
+        );
+
+        // is not false
+        let expr = col("a").is_not_false();
+        let empty = empty_with_type(DataType::Boolean);
+        let plan = LogicalPlan::Projection(Projection::try_new(vec![expr], 
empty, None)?);
+        let plan = rule.optimize(&plan, &mut config).unwrap();
+        assert_eq!(
+            "Projection: #a IS NOT FALSE\n  EmptyRelation",
+            &format!("{:?}", plan)
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn like_for_type_coercion() -> Result<()> {
+        // like : utf8 like "abc"
+        let expr = Box::new(col("a"));
+        let pattern = 
Box::new(lit(ScalarValue::Utf8(Some("abc".to_string()))));
+        let like_expr = Expr::Like {
+            negated: false,
+            expr,
+            pattern,
+            escape_char: None,
+        };
+        let empty = empty_with_type(DataType::Utf8);
+        let plan =
+            LogicalPlan::Projection(Projection::try_new(vec![like_expr], 
empty, None)?);
+        let rule = TypeCoercion::new();
+        let mut config = OptimizerConfig::default();
+        let plan = rule.optimize(&plan, &mut config).unwrap();
+        assert_eq!(
+            "Projection: #a LIKE Utf8(\"abc\")\n  EmptyRelation",
+            &format!("{:?}", plan)
+        );
+
+        let expr = Box::new(col("a"));
+        let pattern = Box::new(lit(ScalarValue::Null));
+        let like_expr = Expr::Like {
+            negated: false,
+            expr,
+            pattern,
+            escape_char: None,
+        };
+        let empty = empty_with_type(DataType::Utf8);
+        let plan =
+            LogicalPlan::Projection(Projection::try_new(vec![like_expr], 
empty, None)?);
+        let rule = TypeCoercion::new();
+        let mut config = OptimizerConfig::default();
+        let plan = rule.optimize(&plan, &mut config).unwrap();
+        assert_eq!(
+            "Projection: #a LIKE Utf8(NULL)\n  EmptyRelation",
+            &format!("{:?}", plan)
+        );
+
+        let expr = Box::new(col("a"));
+        let pattern = 
Box::new(lit(ScalarValue::Utf8(Some("abc".to_string()))));
+        let like_expr = Expr::Like {
+            negated: false,
+            expr,
+            pattern,
+            escape_char: None,
+        };
+        let empty = empty_with_type(DataType::Int64);
+        let plan =
+            LogicalPlan::Projection(Projection::try_new(vec![like_expr], 
empty, None)?);
+        let rule = TypeCoercion::new();
+        let mut config = OptimizerConfig::default();
+        let plan = rule.optimize(&plan, &mut config);
+        assert!(plan.is_err());
+        assert!(plan.unwrap_err().to_string().contains("'Int64 LIKE Utf8' 
can't be evaluated because there isn't a common type to coerce the types to"));
+        Ok(())
+    }
+
+    #[test]
+    fn unknown_for_type_coercion() -> Result<()> {
+        // unknown
+        let expr = col("a").is_unknown();
+        let empty = empty_with_type(DataType::Boolean);
+        let plan = LogicalPlan::Projection(Projection::try_new(
+            vec![expr.clone()],
+            empty,
+            None,
+        )?);
+        let rule = TypeCoercion::new();
+        let mut config = OptimizerConfig::default();
+        let plan = rule.optimize(&plan, &mut config).unwrap();
+        assert_eq!(
+            "Projection: #a IS UNKNOWN\n  EmptyRelation",
+            &format!("{:?}", plan)
+        );
+
+        let empty = empty_with_type(DataType::Utf8);
+        let plan = LogicalPlan::Projection(Projection::try_new(vec![expr], 
empty, None)?);
+        let rule = TypeCoercion::new();
+        let mut config = OptimizerConfig::default();
+        let plan = rule.optimize(&plan, &mut config);
+        assert!(plan.is_err());
+        assert!(plan.unwrap_err().to_string().contains("'Utf8 IS NOT DISTINCT 
FROM Boolean' can't be evaluated because there isn't a common type to coerce 
the types to"));
+
+        // is not unknown
+        let expr = col("a").is_not_unknown();
+        let empty = empty_with_type(DataType::Boolean);
+        let plan = LogicalPlan::Projection(Projection::try_new(vec![expr], 
empty, None)?);
+        let rule = TypeCoercion::new();
+        let mut config = OptimizerConfig::default();
+        let plan = rule.optimize(&plan, &mut config).unwrap();
+        assert_eq!(
+            "Projection: #a IS NOT UNKNOWN\n  EmptyRelation",
+            &format!("{:?}", plan)
+        );
+        Ok(())
+    }
+
     fn empty() -> Arc<LogicalPlan> {
         Arc::new(LogicalPlan::EmptyRelation(EmptyRelation {
             produce_one_row: false,
             schema: Arc::new(DFSchema::empty()),
         }))
     }
+
+    fn empty_with_type(data_type: DataType) -> Arc<LogicalPlan> {
+        Arc::new(LogicalPlan::EmptyRelation(EmptyRelation {
+            produce_one_row: false,
+            schema: Arc::new(
+                DFSchema::new_with_metadata(
+                    vec![DFField::new(None, "a", data_type, true)],
+                    std::collections::HashMap::new(),
+                )
+                .unwrap(),
+            ),
+        }))
+    }
 }

Reply via email to