andygrove commented on code in PR #5736:
URL: https://github.com/apache/datafusion-comet/pull/5736#discussion_r3944570485


##########
spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala:
##########
@@ -740,6 +740,8 @@ object QueryPlanSerde extends Logging with CometExprShim 
with CometTypeShim {
     }
   }
 
+  // Aggregate functions bypass exprToProto: their arguments and filters are 
independent roots

Review Comment:
   This reads as though it contradicts itself, since aggregates both bypass 
`exprToProto` and must enter through it. I think the point is that this method 
does not promote the aggregate tree, so the arguments and filters are 
independent roots. Maybe something like: "This method does not promote the 
aggregate tree, so its arguments and filters are independent roots and 
`AggExprSerde` implementations must serialize them through `exprToProto`."
   
   Worth making it a scaladoc block too, so it renders next to the two 
entry-point docs you add below.



##########
spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala:
##########
@@ -913,6 +917,11 @@ object QueryPlanSerde extends Logging with CometExprShim 
with CometTypeShim {
    * Convert a Spark expression to a protocol-buffer representation of a 
native Comet/DataFusion
    * expression.
    *
+   * The caller owns decimal promotion: this method serializes children of an 
already-promoted

Review Comment:
   Worth thinking about what backstops this contract now. Before this change 
the array and bitwise serdes re-promoted their children, so they were safe 
against ever handing unwrapped decimal arithmetic to the native side even if a 
later edit synthesized some. After it, that safety is a scaladoc sentence, and 
the failure mode is quiet. I patched promotion out of `exprToProto` and 
`array_contains(array(c / d), c / d)` on `DECIMAL(38,6)` returns `true` where 
Spark returns `null`, because native division's `i128::MAX` overflow sentinel 
is only converted to NULL by `CheckOverflow`. No error, no fallback, just a 
wrong boolean. Multiply and add at scale 0 do not diverge, so division is the 
case that bites.
   
   Nothing violates the contract today. `CometAtan2` is the only serde that 
synthesizes arithmetic onto the internal path, and `atan2` arguments are always 
double. So this is about the next serde, not about this PR.
   
   Would a testing-only assertion be worth adding? I tried it and there are two 
traps worth passing on. `Utils` is package-private to `org.apache.spark`, so 
the helper has to live somewhere like `DecimalPrecision` rather than in 
`org.apache.comet.serde`. And a plain `assert(promote(expr) == expr)` here 
fires on four existing tests, because `CometCheckOverflow` legitimately passes 
down the bare arithmetic node it just unwrapped. Exempting decimal 
`BinaryArithmetic` silences those but also silences the real thing, which I 
confirmed by injecting a violation.
   
   What did work was keeping the strict assertion and giving 
`CometCheckOverflow` a `private[serde] exprToProtoInternalUnchecked`: clean 
across 80 tests in 5 suites with no trips, and it fires on an injected 
`Add(child, child)` in `CometArrayContains`. Happy for that to be a follow-up 
rather than something this PR carries.



##########
spark/src/test/scala/org/apache/spark/sql/comet/CometDecimalPromotionSuite.scala:
##########
@@ -62,9 +63,7 @@ class CometDecimalPromotionSuite extends CometTestBase {
           DecimalPrecision.promote(promoted) == promoted,
           s"$name promotion is not idempotent: $promoted")
 
-        // This proto-shape check relies on CometArrayContains re-entering 
exprToProto for its
-        // children. If https://github.com/apache/datafusion-comet/issues/5248 
changes that,
-        // re-point it to another recursively serializing serde.
+        // Recursive child serialization must retain exactly one equivalent 
overflow wrapper.

Review Comment:
   The comment being removed here was addressed to exactly this change. It said 
the proto-shape check relies on `CometArrayContains` re-entering `exprToProto` 
for its children, and to re-point it at another recursively serializing serde 
if #5248 changed that. Now that `array_contains` promotes once, the "Duplicate 
outer CheckOverflow" and "Duplicate inner CheckOverflow" assertions in the new 
test cannot trip through that path any more, since reaching them needs the 
double promotion that no longer happens there. `CometSlice` and 
`CometArrayJoin` still double-promote, so pointing the shape check at one of 
those would keep the coverage the comment was guarding.
   
   The other gap is that both tests assert only on proto structure, and the 
property that actually breaks is the value. I wrote a value-level suite while 
reviewing and it seems worth having: 46 cases putting overflowing 
`DECIMAL(38,0)` multiply and overflowing `DECIMAL(38,6)` divide under every 
array and bitwise serde this PR touches, read from Parquet so the scan is 
native, checked with `checkSparkAnswerAndOperator` for ANSI off and 
`checkSparkAnswerMaybeThrows` for ANSI on. It passes on this branch and on its 
parent, and fails two of three tests if promotion is removed. Happy to hand it 
over if useful.
   
   Smaller thing: the `Seq(true, false).foreach { binding => ... }` loop below 
doubles the runtime but no assertion inside it depends on `binding`, so it is 
not really covering the bound and unbound cases the description mentions.



##########
spark/src/main/scala/org/apache/comet/serde/arrays.scala:
##########
@@ -999,12 +999,12 @@ object CometSequence extends 
CometExpressionSerde[Sequence] with CodegenDispatch
       expr: Sequence,
       inputs: Seq[Attribute],
       binding: Boolean): Option[ExprOuterClass.Expr] = {
-    val startExprProto = exprToProto(expr.start, inputs, binding)
-    val stopExprProto = exprToProto(expr.stop, inputs, binding)
+    val startExprProto = exprToProtoInternal(expr.start, inputs, binding)

Review Comment:
   `getSupportLevel` already requires every argument to be a `Literal`, 
`Attribute` or `BoundReference` via `argsAreLiteralsOrRefs`, so `convert` never 
sees a decimal arithmetic child here and promoting a single leaf is free. These 
three lines are fine and consistent, just worth knowing they are not part of 
the measured win. My corpus confirms it: every `sequence` case serialized as 
`jvm_scalar_udf` through the codegen dispatcher rather than `spark_sequence`, 
because the cast-wrapped arguments fail the literals-or-refs test.



##########
spark/src/main/scala/org/apache/comet/serde/arrays.scala:
##########
@@ -498,7 +498,7 @@ object CometSlice extends CometExpressionSerde[Slice] {
       inputs: Seq[Attribute],
       binding: Boolean): Option[ExprOuterClass.Expr] = {
     val elementType = expr.x.dataType.asInstanceOf[ArrayType].elementType
-    val arrayExprProto = exprToProto(expr.x, inputs, binding)
+    val arrayExprProto = exprToProtoInternal(expr.x, inputs, binding)
     val startExprProto = exprToProto(Cast(expr.start, LongType), inputs, 
binding)

Review Comment:
   The new contract says literals and non-arithmetic wrappers can use 
`exprToProtoInternal`, and only synthesized arithmetic needs the public entry 
point. Eight calls in this file still take the public path, and two of them 
look like the case this PR is fixing.
   
   Here, `expr.x` goes through the internal path but `expr.start` and 
`expr.length` get wrapped in synthesized `Cast(..., LongType)` and sent through 
`exprToProto`, which re-promotes everything under `expr.start`. Reachable with 
something like `slice(arr, CAST(a + b AS INT), 2)` on decimal columns. A `Cast` 
is not arithmetic, so by your own rule these belong on the internal path.
   
   `CometArrayJoin` has the same shape in a tighter space: 
`nullReplacementExpr` is serialized internally at line 412, then 
`IsNull(nullReplacementExpr)` publicly at line 423, so the same subexpression 
takes both entry points seven lines apart. The remaining literal calls are at 
lines 84, 424, 700, 816 and 887.
   
   `CometArraysZip` at line 886 already routes its synthesized 
`IsNotNull(...)`/`And` guard through `exprToProtoInternal`, so the pattern is 
right there in the file. Would you sweep these too? The protos come out 
identical either way, so it is consistency and saved traversal rather than a 
correctness fix.



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