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


##########
spark/src/main/scala/org/apache/comet/serde/arrays.scala:
##########
@@ -352,7 +350,9 @@ object CometArrayJoin
     if (hasNonDefaultStringCollation(expr.array.dataType)) {
       Incompatible(Some(collationReason))
     } else {
-      Incompatible(Some(incompatReason))
+      // Null handling matched Spark once the nullReplacement guard in 
convert() landed (#3178);
+      // collation is the only remaining deviation.
+      Compatible()

Review Comment:
   [P2] Keep delimiter evaluation behind the null-array guard
   
   Making this `Compatible` changes the default behavior of `array_join(arr, 
element_at(delims, idx))` over supported Parquet input columns. For a row with 
`arr = NULL`, `delims = [',']`, and `idx = 0` (`arr`/`delims` are 
`ARRAY<STRING>` and `idx` is `INT`), Spark's generated code returns NULL 
without evaluating the delimiter. The native `ScalarFunctionExpr` evaluates 
every argument first, so `ListExtract` throws for index zero before 
`array_to_string` can inspect the null array, even with ANSI disabled. The 
previous default JVM dispatcher preserved Spark's guard, and the native error 
propagates rather than falling back. Please preserve that guard on this newly 
enabled path. This is source reasoning for the inspected Spark 3.5/4.0 paths; I 
have not run it.



##########
spark/src/main/scala/org/apache/comet/serde/arrays.scala:
##########
@@ -374,7 +374,28 @@ object CometArrayJoin
           delimiterExprProto,
           nullReplacementExprProto)
 
-        arrayJoinScalarExpr
+        // Spark's ArrayJoin returns null as soon as nullReplacement evaluates 
to null, whether
+        // or not the array actually contains any nulls. DataFusion's 
array_to_string instead
+        // reads a null null_string as "omit null elements", which is what the 
two-argument form
+        // means, so wrap the call in an explicit null guard (#3178). A 
non-nullable replacement
+        // -- the common literal case -- cannot trigger this and is left 
unwrapped.
+        if (!nullReplacementExpr.nullable) {
+          arrayJoinScalarExpr
+        } else {
+          for {
+            joined <- arrayJoinScalarExpr
+            replacementIsNull <- exprToProto(IsNull(nullReplacementExpr), 
inputs, binding)

Review Comment:
   [P2] Evaluate a nullable replacement once per input row
   
   The replacement is serialized both as the function argument and again under 
`IsNull`. Consider `IF(monotonically_increasing_id() % 2L = 0L, CAST(NULL AS 
STRING), 'X')` with a sole input array column containing four rows of `['a', 
NULL, 'b']` in one native batch. The two native replacement trees have separate 
counters: the guard sees all four rows, while the function's copy sees only the 
two rows that passed the guard. That second copy produces one NULL replacement, 
which `array_to_string` treats as omission, yielding `'a,b'`. Spark's single 
evaluation can produce only NULL or `'a,X,b'`. The mismatch survives either 
starting parity, including counters advanced during planning. Please compute 
the replacement once and reuse its value for both the guard and join. This is 
source reasoning for the inspected Spark 3.5/4.0 paths, not an executed 
reproduction.



-- 
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