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

alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new 244ce5a2a4 Support List type coercion for CASE-WHEN-THEN expression 
(#12490)
244ce5a2a4 is described below

commit 244ce5a2a47bca27b086f184f6aab0b63394c2a1
Author: Jax Liu <[email protected]>
AuthorDate: Sat Sep 21 19:24:38 2024 +0800

    Support List type coercion for CASE-WHEN-THEN expression (#12490)
    
    * support list type coercion
    
    * add planing and sql tests
    
    * clippy
    
    * support to compare nested type for case-when expression
    
    * simplify the macro rules
    
    * fix the FixedSizeList type coercion and add tests
    
    * add test for THEN-ELSE
---
 datafusion/expr-common/src/type_coercion/binary.rs |  73 +++++++++
 datafusion/optimizer/src/analyzer/type_coercion.rs | 180 +++++++++++++++++++++
 datafusion/physical-expr/src/expressions/case.rs   |  10 +-
 datafusion/sqllogictest/test_files/case.slt        |  94 +++++++++++
 4 files changed, 355 insertions(+), 2 deletions(-)

diff --git a/datafusion/expr-common/src/type_coercion/binary.rs 
b/datafusion/expr-common/src/type_coercion/binary.rs
index d1144ed407..c1e96a8fa9 100644
--- a/datafusion/expr-common/src/type_coercion/binary.rs
+++ b/datafusion/expr-common/src/type_coercion/binary.rs
@@ -1021,6 +1021,22 @@ fn list_coercion(lhs_type: &DataType, rhs_type: 
&DataType) -> Option<DataType> {
     use arrow::datatypes::DataType::*;
     match (lhs_type, rhs_type) {
         (List(_), List(_)) => Some(lhs_type.clone()),
+        (LargeList(_), List(_)) => Some(lhs_type.clone()),
+        (List(_), LargeList(_)) => Some(rhs_type.clone()),
+        (LargeList(_), LargeList(_)) => Some(lhs_type.clone()),
+        (List(_), FixedSizeList(_, _)) => Some(lhs_type.clone()),
+        (FixedSizeList(_, _), List(_)) => Some(rhs_type.clone()),
+        // Coerce to the left side FixedSizeList type if the list lengths are 
the same,
+        // otherwise coerce to list with the left type for dynamic length
+        (FixedSizeList(lf, ls), FixedSizeList(_, rs)) => {
+            if ls == rs {
+                Some(lhs_type.clone())
+            } else {
+                Some(List(Arc::clone(lf)))
+            }
+        }
+        (LargeList(_), FixedSizeList(_, _)) => Some(lhs_type.clone()),
+        (FixedSizeList(_, _), LargeList(_)) => Some(rhs_type.clone()),
         _ => None,
     }
 }
@@ -1906,6 +1922,63 @@ mod tests {
             DataType::Timestamp(TimeUnit::Second, 
Some("Europe/Brussels".into()))
         );
 
+        // list
+        let inner_field = Arc::new(Field::new("item", DataType::Int64, true));
+        test_coercion_binary_rule!(
+            DataType::List(Arc::clone(&inner_field)),
+            DataType::List(Arc::clone(&inner_field)),
+            Operator::Eq,
+            DataType::List(Arc::clone(&inner_field))
+        );
+        test_coercion_binary_rule!(
+            DataType::List(Arc::clone(&inner_field)),
+            DataType::LargeList(Arc::clone(&inner_field)),
+            Operator::Eq,
+            DataType::LargeList(Arc::clone(&inner_field))
+        );
+        test_coercion_binary_rule!(
+            DataType::LargeList(Arc::clone(&inner_field)),
+            DataType::List(Arc::clone(&inner_field)),
+            Operator::Eq,
+            DataType::LargeList(Arc::clone(&inner_field))
+        );
+        test_coercion_binary_rule!(
+            DataType::LargeList(Arc::clone(&inner_field)),
+            DataType::LargeList(Arc::clone(&inner_field)),
+            Operator::Eq,
+            DataType::LargeList(Arc::clone(&inner_field))
+        );
+        test_coercion_binary_rule!(
+            DataType::FixedSizeList(Arc::clone(&inner_field), 10),
+            DataType::FixedSizeList(Arc::clone(&inner_field), 10),
+            Operator::Eq,
+            DataType::FixedSizeList(Arc::clone(&inner_field), 10)
+        );
+        test_coercion_binary_rule!(
+            DataType::FixedSizeList(Arc::clone(&inner_field), 10),
+            DataType::LargeList(Arc::clone(&inner_field)),
+            Operator::Eq,
+            DataType::LargeList(Arc::clone(&inner_field))
+        );
+        test_coercion_binary_rule!(
+            DataType::LargeList(Arc::clone(&inner_field)),
+            DataType::FixedSizeList(Arc::clone(&inner_field), 10),
+            Operator::Eq,
+            DataType::LargeList(Arc::clone(&inner_field))
+        );
+        test_coercion_binary_rule!(
+            DataType::List(Arc::clone(&inner_field)),
+            DataType::FixedSizeList(Arc::clone(&inner_field), 10),
+            Operator::Eq,
+            DataType::List(Arc::clone(&inner_field))
+        );
+        test_coercion_binary_rule!(
+            DataType::FixedSizeList(Arc::clone(&inner_field), 10),
+            DataType::List(Arc::clone(&inner_field)),
+            Operator::Eq,
+            DataType::List(Arc::clone(&inner_field))
+        );
+
         // TODO add other data type
         Ok(())
     }
diff --git a/datafusion/optimizer/src/analyzer/type_coercion.rs 
b/datafusion/optimizer/src/analyzer/type_coercion.rs
index 7a8746572c..3e31ac1c63 100644
--- a/datafusion/optimizer/src/analyzer/type_coercion.rs
+++ b/datafusion/optimizer/src/analyzer/type_coercion.rs
@@ -1811,6 +1811,186 @@ mod test {
         Ok(())
     }
 
+    macro_rules! test_case_expression {
+        ($expr:expr, $when_then:expr, $case_when_type:expr, 
$then_else_type:expr, $schema:expr) => {
+            let case = Case {
+                expr: $expr.map(|e| Box::new(col(e))),
+                when_then_expr: $when_then,
+                else_expr: None,
+            };
+
+            let expected =
+                cast_helper(case.clone(), &$case_when_type, &$then_else_type, 
&$schema);
+
+            let actual = coerce_case_expression(case, &$schema)?;
+            assert_eq!(expected, actual);
+        };
+    }
+
+    #[test]
+    fn tes_case_when_list() -> Result<()> {
+        let inner_field = Arc::new(Field::new("item", DataType::Int64, true));
+        let schema = Arc::new(DFSchema::from_unqualified_fields(
+            vec![
+                Field::new(
+                    "large_list",
+                    DataType::LargeList(Arc::clone(&inner_field)),
+                    true,
+                ),
+                Field::new(
+                    "fixed_list",
+                    DataType::FixedSizeList(Arc::clone(&inner_field), 3),
+                    true,
+                ),
+                Field::new("list", DataType::List(inner_field), true),
+            ]
+            .into(),
+            std::collections::HashMap::new(),
+        )?);
+
+        test_case_expression!(
+            Some("list"),
+            vec![(Box::new(col("large_list")), Box::new(lit("1")))],
+            DataType::LargeList(Arc::new(Field::new("item", DataType::Int64, 
true))),
+            DataType::Utf8,
+            schema
+        );
+
+        test_case_expression!(
+            Some("large_list"),
+            vec![(Box::new(col("list")), Box::new(lit("1")))],
+            DataType::LargeList(Arc::new(Field::new("item", DataType::Int64, 
true))),
+            DataType::Utf8,
+            schema
+        );
+
+        test_case_expression!(
+            Some("list"),
+            vec![(Box::new(col("fixed_list")), Box::new(lit("1")))],
+            DataType::List(Arc::new(Field::new("item", DataType::Int64, 
true))),
+            DataType::Utf8,
+            schema
+        );
+
+        test_case_expression!(
+            Some("fixed_list"),
+            vec![(Box::new(col("list")), Box::new(lit("1")))],
+            DataType::List(Arc::new(Field::new("item", DataType::Int64, 
true))),
+            DataType::Utf8,
+            schema
+        );
+
+        test_case_expression!(
+            Some("fixed_list"),
+            vec![(Box::new(col("large_list")), Box::new(lit("1")))],
+            DataType::LargeList(Arc::new(Field::new("item", DataType::Int64, 
true))),
+            DataType::Utf8,
+            schema
+        );
+
+        test_case_expression!(
+            Some("large_list"),
+            vec![(Box::new(col("fixed_list")), Box::new(lit("1")))],
+            DataType::LargeList(Arc::new(Field::new("item", DataType::Int64, 
true))),
+            DataType::Utf8,
+            schema
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_then_else_list() -> Result<()> {
+        let inner_field = Arc::new(Field::new("item", DataType::Int64, true));
+        let schema = Arc::new(DFSchema::from_unqualified_fields(
+            vec![
+                Field::new("boolean", DataType::Boolean, true),
+                Field::new(
+                    "large_list",
+                    DataType::LargeList(Arc::clone(&inner_field)),
+                    true,
+                ),
+                Field::new(
+                    "fixed_list",
+                    DataType::FixedSizeList(Arc::clone(&inner_field), 3),
+                    true,
+                ),
+                Field::new("list", DataType::List(inner_field), true),
+            ]
+            .into(),
+            std::collections::HashMap::new(),
+        )?);
+
+        // large list and list
+        test_case_expression!(
+            None::<String>,
+            vec![
+                (Box::new(col("boolean")), Box::new(col("large_list"))),
+                (Box::new(col("boolean")), Box::new(col("list")))
+            ],
+            DataType::Boolean,
+            DataType::LargeList(Arc::new(Field::new("item", DataType::Int64, 
true))),
+            schema
+        );
+
+        test_case_expression!(
+            None::<String>,
+            vec![
+                (Box::new(col("boolean")), Box::new(col("list"))),
+                (Box::new(col("boolean")), Box::new(col("large_list")))
+            ],
+            DataType::Boolean,
+            DataType::LargeList(Arc::new(Field::new("item", DataType::Int64, 
true))),
+            schema
+        );
+
+        // fixed list and list
+        test_case_expression!(
+            None::<String>,
+            vec![
+                (Box::new(col("boolean")), Box::new(col("fixed_list"))),
+                (Box::new(col("boolean")), Box::new(col("list")))
+            ],
+            DataType::Boolean,
+            DataType::List(Arc::new(Field::new("item", DataType::Int64, 
true))),
+            schema
+        );
+
+        test_case_expression!(
+            None::<String>,
+            vec![
+                (Box::new(col("boolean")), Box::new(col("list"))),
+                (Box::new(col("boolean")), Box::new(col("fixed_list")))
+            ],
+            DataType::Boolean,
+            DataType::List(Arc::new(Field::new("item", DataType::Int64, 
true))),
+            schema
+        );
+
+        // fixed list and large list
+        test_case_expression!(
+            None::<String>,
+            vec![
+                (Box::new(col("boolean")), Box::new(col("fixed_list"))),
+                (Box::new(col("boolean")), Box::new(col("large_list")))
+            ],
+            DataType::Boolean,
+            DataType::LargeList(Arc::new(Field::new("item", DataType::Int64, 
true))),
+            schema
+        );
+
+        test_case_expression!(
+            None::<String>,
+            vec![
+                (Box::new(col("boolean")), Box::new(col("large_list"))),
+                (Box::new(col("boolean")), Box::new(col("fixed_list")))
+            ],
+            DataType::Boolean,
+            DataType::LargeList(Arc::new(Field::new("item", DataType::Int64, 
true))),
+            schema
+        );
+        Ok(())
+    }
+
     #[test]
     fn interval_plus_timestamp() -> Result<()> {
         // SELECT INTERVAL '1' YEAR + '2000-01-01T00:00:00'::timestamp;
diff --git a/datafusion/physical-expr/src/expressions/case.rs 
b/datafusion/physical-expr/src/expressions/case.rs
index 712175c9af..ffb431b200 100644
--- a/datafusion/physical-expr/src/expressions/case.rs
+++ b/datafusion/physical-expr/src/expressions/case.rs
@@ -24,7 +24,6 @@ use crate::physical_expr::down_cast_any_ref;
 use crate::PhysicalExpr;
 
 use arrow::array::*;
-use arrow::compute::kernels::cmp::eq;
 use arrow::compute::kernels::zip::zip;
 use arrow::compute::{and, and_not, is_null, not, nullif, or, 
prep_null_mask_filter};
 use arrow::datatypes::{DataType, Schema};
@@ -33,6 +32,7 @@ use datafusion_common::{exec_err, internal_err, 
DataFusionError, Result, ScalarV
 use datafusion_expr::ColumnarValue;
 
 use super::{Column, Literal};
+use datafusion_physical_expr_common::datum::compare_with_eq;
 use itertools::Itertools;
 
 type WhenThen = (Arc<dyn PhysicalExpr>, Arc<dyn PhysicalExpr>);
@@ -204,7 +204,13 @@ impl CaseExpr {
                 .evaluate_selection(batch, &remainder)?;
             let when_value = when_value.into_array(batch.num_rows())?;
             // build boolean array representing which rows match the "when" 
value
-            let when_match = eq(&when_value, &base_value)?;
+            let when_match = compare_with_eq(
+                &when_value,
+                &base_value,
+                // The types of case and when expressions will be coerced to 
match.
+                // We only need to check if the base_value is nested.
+                base_value.data_type().is_nested(),
+            )?;
             // Treat nulls as false
             let when_match = match when_match.null_count() {
                 0 => Cow::Borrowed(&when_match),
diff --git a/datafusion/sqllogictest/test_files/case.slt 
b/datafusion/sqllogictest/test_files/case.slt
index 70063b88fb..3c967eed21 100644
--- a/datafusion/sqllogictest/test_files/case.slt
+++ b/datafusion/sqllogictest/test_files/case.slt
@@ -108,3 +108,97 @@ SELECT CASE WHEN false THEN 1 ELSE 0 END FROM foo
 0
 0
 0
+
+# test then value type coercion
+# List(Utf8) will be casted to List(Int64)
+query ?
+SELECT CASE 1 WHEN 1 THEN ['1', '2', '3'] WHEN 2 THEN [1, 2, 3] ELSE null END;
+----
+[1, 2, 3]
+
+query ?
+SELECT CASE 1 WHEN 1 THEN [[1,2], [2,4]] WHEN 2 THEN [['1','2'], ['2','4']] 
ELSE null END;
+----
+[[1, 2], [2, 4]]
+
+query ?
+SELECT CASE 1 WHEN 1 THEN [1,2,3] WHEN 2 THEN arrow_cast([1,2,3], 
'LargeList(Int64)') WHEN 3 THEN arrow_cast([1,2,3], 'FixedSizeList(3, Int32)') 
ELSE null END;
+----
+[1, 2, 3]
+
+query ?
+SELECT CASE 1 WHEN 1 THEN [[1,2], [2,4]] WHEN 2 THEN arrow_cast([[1,2], 
[2,4]], 'LargeList(LargeList(Int64))') WHEN 3 THEN arrow_cast([[1,2], [2,4]], 
'FixedSizeList(2, FixedSizeList(2, Int32))') ELSE null END;
+----
+[[1, 2], [2, 4]]
+
+query ?
+SELECT CASE 1 WHEN 1 THEN [1,2,3] WHEN 2 THEN arrow_cast(['1','2','3'], 
'LargeList(Utf8)') WHEN 3 THEN arrow_cast(['1','2','3'], 'FixedSizeList(3, 
Utf8)') ELSE null END;
+----
+[1, 2, 3]
+
+query ?
+SELECT CASE 1 WHEN 1 THEN [[1,2], [2,4]] WHEN 2 THEN arrow_cast([['1','2'], 
['2','4']], 'LargeList(LargeList(Utf8))') WHEN 3 THEN arrow_cast([['1','2'], 
['2','4']], 'FixedSizeList(2, FixedSizeList(2, Utf8))') ELSE null END;
+----
+[[1, 2], [2, 4]]
+
+query ?
+SELECT CASE 1 WHEN 1 THEN arrow_cast([1,2,3], 'LargeList(Int64)') WHEN 2 THEN 
arrow_cast(['1','2','3'], 'LargeList(Utf8)') ELSE null END;
+----
+[1, 2, 3]
+
+query ?
+SELECT CASE 1 WHEN 1 THEN arrow_cast([1, 2], 'FixedSizeList(2, Int64)') WHEN 2 
THEN arrow_cast(['1', '2', '3'], 'FixedSizeList(3, Utf8)') ELSE null END;
+----
+[1, 2]
+
+query error DataFusion error: type_coercion
+SELECT CASE 1 WHEN 1 THEN [1,2,3] WHEN 2 THEN 'test' ELSE null END;
+
+# test case when type coercion
+query I
+SELECT CASE [1,2,3] WHEN arrow_cast([1,2,3], 'LargeList(Int64)') THEN 1 ELSE 0 
END;
+----
+1
+
+query I
+SELECT CASE [1,2,3] WHEN arrow_cast(['1','2','3'], 'LargeList(Int64)') THEN 1 
ELSE 0 END;
+----
+1
+
+query I
+SELECT CASE arrow_cast([1,2,3], 'LargeList(Int64)') WHEN [1,2,3] THEN 1 ELSE 0 
END;
+----
+1
+
+query I
+SELECT CASE [[1,2],[2,4]] WHEN arrow_cast([[1,2],[2,4]], 
'LargeList(LargeList(Int64))') THEN 1 ELSE 0 END;
+----
+1
+
+query I
+SELECT CASE arrow_cast([1,2,3], 'FixedSizeList(3, Int64)') WHEN [1,2,3] THEN 1 
ELSE 0 END;
+----
+1
+
+query error DataFusion error: type_coercion
+SELECT CASE [1,2,3] WHEN 'test' THEN 1 ELSE 0 END;
+
+query I
+SELECT CASE arrow_cast([1,2], 'FixedSizeList(2, Int64)') WHEN 
arrow_cast([1,2,3], 'FixedSizeList(3, Int64)') THEN 1 ELSE 0 END;
+----
+0
+
+query I
+SELECT CASE arrow_cast([1,2], 'FixedSizeList(2, Int64)') WHEN 
arrow_cast(['1','2','3'], 'FixedSizeList(3, Utf8)') THEN 1 ELSE 0 END;
+----
+0
+
+query I
+SELECT CASE arrow_cast(['1','2'], 'FixedSizeList(2, Utf8)') WHEN 
arrow_cast([1,2,3], 'FixedSizeList(3, Int64)') THEN 1 ELSE 0 END;
+----
+0
+
+query I
+SELECT CASE arrow_cast([1,2,3], 'FixedSizeList(3, Int64)') WHEN 
arrow_cast([1,2,3], 'FixedSizeList(3, Int64)') THEN 1 ELSE 0 END;
+----
+1


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to