sunchao commented on code in PR #6458:
URL: https://github.com/apache/datafusion-comet/pull/6458#discussion_r4154017963


##########
native/spark-expr/src/conditional_funcs/case_when.rs:
##########
@@ -63,34 +64,66 @@ pub fn create_case_when(
     else {
         return Ok(Arc::new(CaseWhenExpr::try_new(when_then, else_expr)?));
     };
-    // The branches share a Spark type, so any difference is in the Arrow 
representation. For a
-    // timestamp that is the timezone label, and the cast only relabels it, 
but Comet's cast still
-    // needs a timezone. Every `TimestampType` value in a native plan is 
labelled UTC.
-    let cast_options = SparkCastOptions::new(EvalMode::Legacy, "UTC", false);
-    // A branch that already has the common type is not wrapped in a cast, 
which would do nothing
-    // but hide what the branch is from the evaluation.
-    let coerce = |expr: Arc<dyn PhysicalExpr>, data_type: &DataType| -> 
Arc<dyn PhysicalExpr> {
-        if data_type == &coerce_type {
-            expr
-        } else {
-            Arc::new(Cast::new(
-                expr,
-                coerce_type.clone(),
-                cast_options.clone(),
-                None,
-                None,
-            ))
-        }
-    };
     let when_then = when_then
         .into_iter()
         .zip(&then_types)
-        .map(|((when, then), then_type)| (when, coerce(then, then_type)))
+        .map(|((when, then), then_type)| (when, coerce_branch(then, then_type, 
&coerce_type)))
         .collect();
-    let else_expr = else_expr.map(|e| coerce(e, &else_type));
+    let else_expr = else_expr.map(|e| coerce_branch(e, &else_type, 
&coerce_type));
     Ok(Arc::new(CaseWhenExpr::try_new(when_then, else_expr)?))
 }
 
+/// Creates an `IF`, casting a branch whose type differs from the other's to 
their common type.
+///
+/// Spark adds no cast when the branches differ only in whether a nested field 
can be NULL, or in
+/// the case of a struct field name. But [`IfExpr`] reports the THEN branch's 
type, and returns the
+/// ELSE branch's array unchanged when no row of a batch takes the THEN branch.
+pub fn create_if_expr(
+    if_expr: Arc<dyn PhysicalExpr>,
+    true_expr: Arc<dyn PhysicalExpr>,
+    false_expr: Arc<dyn PhysicalExpr>,
+    input_schema: &Schema,
+) -> Result<Arc<dyn PhysicalExpr>> {
+    let true_type = true_expr.data_type(input_schema)?;
+    let false_type = false_expr.data_type(input_schema)?;
+    // The coercion that `get_coerce_type_for_case_expression` folds over the 
branches of a CASE
+    // WHEN, starting from the ELSE branch. Here the THEN branch goes first, 
so the common type
+    // takes its struct field names, as Spark's `If.dataType` does.
+    let Some(common_type) = type_union_coercion(&true_type, &false_type) else {

Review Comment:
   [P2] Merge struct fields positionally before using DataFusion coercion. With 
default case-insensitive resolution, nullable `i INT` and `d DOUBLE`, Spark 
accepts `IF(b, named_struct('x', i, 'X', CAST(5.5 AS DOUBLE)), 
named_struct('X', 0, 'x', d))` and returns `STRUCT<x:INT,X:DOUBLE>`. Because 
the branches differ in nullability, `type_union_coercion` reaches DataFusion's 
name-based struct merge and pairs the first integer field with the second 
double field. The new casts consequently change `x` to DOUBLE even on an 
all-THEN batch that worked before. With native `to_json` enabled, the result 
changes from `{"x":7,"X":5.5}` to `{"x":7.0,"X":5.5}`. Use positional recursive 
struct reconciliation while retaining THEN names and combining nullability.
   
   Evidence: An exact-head Rust reproduction constructed the branches with 
`CreateNamedStruct` over b=[true,true], i=[7,NULL], d=[9.5,NULL]. The base 
planner path, `IfExpr::new`, returned Int32 values [7,NULL] and JSON 
{"x":7,"X":5.5}. `create_if_expr` returned Float64 values [7.0,NULL] and JSON 
{"x":7.0,"X":5.5}. Spark 3.5.9 independently returned the integer schema and 
original JSON. The case-distinct names pass Scala's duplicate-name check. 
Native JSON observability requires 
`spark.comet.expression.StructsToJson.allowIncompatible=true`. Reproduction 
source is retained at `/tmp/review6458-repro.rs`, with output in 
`/tmp/review6458-repro.log` and Spark results in 
`/tmp/review6458-spark-reference.log`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to