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

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


The following commit(s) were added to refs/heads/main by this push:
     new 47f395c737 fix: use per-expression eval mode for decimal promotion 
(#5171)
47f395c737 is described below

commit 47f395c737295810573141fd94fc48f8c0a29f63
Author: Peter Lee <[email protected]>
AuthorDate: Sat Aug 1 22:41:45 2026 +0800

    fix: use per-expression eval mode for decimal promotion (#5171)
---
 spark/pom.xml                                      |  1 +
 .../org/apache/comet/serde/QueryPlanSerde.scala    |  2 +-
 .../apache/spark/sql/comet/DecimalPrecision.scala  | 16 +++---
 .../comet/CometDecimalArithmeticViewSuite.scala    | 60 +++++++++++++++++++++-
 4 files changed, 70 insertions(+), 9 deletions(-)

diff --git a/spark/pom.xml b/spark/pom.xml
index 33e6d102c1..c3c57c5f57 100644
--- a/spark/pom.xml
+++ b/spark/pom.xml
@@ -536,6 +536,7 @@ under the License.
             <configuration>
               <sources>
                 <source>src/test/${shims.majorVerSrc}</source>
+                <source>src/test/${shims.minorPlusVerSrc}</source>
                 <source>src/test/${shims.minorVerSrc}</source>
               </sources>
             </configuration>
diff --git a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala 
b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
index 5df16eab67..4ae8144648 100644
--- a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
@@ -785,7 +785,7 @@ object QueryPlanSerde extends Logging with CometExprShim 
with CometTypeShim {
       inputs: Seq[Attribute],
       binding: Boolean = true): Option[Expr] = {
 
-    val newExpr = DecimalPrecision.promote(expr, !SQLConf.get.ansiEnabled)
+    val newExpr = DecimalPrecision.promote(expr)
     exprToProtoInternal(newExpr, inputs, binding)
   }
 
diff --git 
a/spark/src/main/scala/org/apache/spark/sql/comet/DecimalPrecision.scala 
b/spark/src/main/scala/org/apache/spark/sql/comet/DecimalPrecision.scala
index 6309a4304a..2d5247df7e 100644
--- a/spark/src/main/scala/org/apache/spark/sql/comet/DecimalPrecision.scala
+++ b/spark/src/main/scala/org/apache/spark/sql/comet/DecimalPrecision.scala
@@ -33,9 +33,13 @@ import org.apache.spark.sql.types.DecimalType
  * (SPARK-53968) it preserves the per-expression `allowDecimalPrecisionLoss` 
captured at view
  * creation time. Recomputing from the live `SQLConf` would re-label a stored 
DEC(38, 17) result
  * as DEC(38, 18) (or vice versa) and shift values by 10x (issue #4124).
+ *
+ * The `CheckOverflow` behavior also follows each expression's captured 
`evalMode`, since TRY
+ * arithmetic can be nested inside ANSI arithmetic (or vice versa) 
independently of the live
+ * session ANSI setting.
  */
 object DecimalPrecision {
-  def promote(expr: Expression, nullOnOverflow: Boolean): Expression = {
+  def promote(expr: Expression): Expression = {
     expr.transformUp {
       // This means the binary expression is already optimized with the rule 
in Spark. This can
       // happen if the Spark version is < 3.4
@@ -43,23 +47,23 @@ object DecimalPrecision {
 
       case add @ Add(DecimalExpression(_, _), DecimalExpression(_, _), _)
           if add.dataType.isInstanceOf[DecimalType] =>
-        CheckOverflow(add, add.dataType.asInstanceOf[DecimalType], 
nullOnOverflow)
+        CheckOverflow(add, add.dataType.asInstanceOf[DecimalType], 
add.evalMode != EvalMode.ANSI)
 
       case sub @ Subtract(DecimalExpression(_, _), DecimalExpression(_, _), _)
           if sub.dataType.isInstanceOf[DecimalType] =>
-        CheckOverflow(sub, sub.dataType.asInstanceOf[DecimalType], 
nullOnOverflow)
+        CheckOverflow(sub, sub.dataType.asInstanceOf[DecimalType], 
sub.evalMode != EvalMode.ANSI)
 
       case mul @ Multiply(DecimalExpression(_, _), DecimalExpression(_, _), _)
           if mul.dataType.isInstanceOf[DecimalType] =>
-        CheckOverflow(mul, mul.dataType.asInstanceOf[DecimalType], 
nullOnOverflow)
+        CheckOverflow(mul, mul.dataType.asInstanceOf[DecimalType], 
mul.evalMode != EvalMode.ANSI)
 
       case div @ Divide(DecimalExpression(_, _), DecimalExpression(_, _), _)
           if div.dataType.isInstanceOf[DecimalType] =>
-        CheckOverflow(div, div.dataType.asInstanceOf[DecimalType], 
nullOnOverflow)
+        CheckOverflow(div, div.dataType.asInstanceOf[DecimalType], 
div.evalMode != EvalMode.ANSI)
 
       case rem @ Remainder(DecimalExpression(_, _), DecimalExpression(_, _), _)
           if rem.dataType.isInstanceOf[DecimalType] =>
-        CheckOverflow(rem, rem.dataType.asInstanceOf[DecimalType], 
nullOnOverflow)
+        CheckOverflow(rem, rem.dataType.asInstanceOf[DecimalType], 
rem.evalMode != EvalMode.ANSI)
 
       case e => e
     }
diff --git 
a/spark/src/test/spark-4.1/org/apache/spark/sql/comet/CometDecimalArithmeticViewSuite.scala
 
b/spark/src/test/spark-4.1+/org/apache/spark/sql/comet/CometDecimalArithmeticViewSuite.scala
similarity index 54%
rename from 
spark/src/test/spark-4.1/org/apache/spark/sql/comet/CometDecimalArithmeticViewSuite.scala
rename to 
spark/src/test/spark-4.1+/org/apache/spark/sql/comet/CometDecimalArithmeticViewSuite.scala
index 0a16f8e55c..37189714ad 100644
--- 
a/spark/src/test/spark-4.1/org/apache/spark/sql/comet/CometDecimalArithmeticViewSuite.scala
+++ 
b/spark/src/test/spark-4.1+/org/apache/spark/sql/comet/CometDecimalArithmeticViewSuite.scala
@@ -20,10 +20,12 @@
 package org.apache.spark.sql.comet
 
 import org.apache.spark.sql.CometTestBase
-import org.apache.spark.sql.catalyst.expressions.{Add, AttributeReference, 
CheckOverflow, EvalMode, NumericEvalContext}
+import org.apache.spark.sql.catalyst.expressions.{Add, AttributeReference, 
BinaryArithmetic, CheckOverflow, Divide, EvalMode, Expression, Multiply, 
NumericEvalContext, Remainder, Subtract}
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.types.DecimalType
 
+import org.apache.comet.serde.{ExprOuterClass, QueryPlanSerde}
+
 class CometDecimalArithmeticViewSuite extends CometTestBase {
 
   // Spark 4.1+ (SPARK-53968) stores 
`spark.sql.decimalOperations.allowPrecisionLoss` per
@@ -50,7 +52,7 @@ class CometDecimalArithmeticViewSuite extends CometTestBase {
     Seq((true, storedFalse), (false, storedTrue)).foreach { case (currentConf, 
add) =>
       withSQLConf(SQLConf.DECIMAL_OPERATIONS_ALLOW_PREC_LOSS.key -> 
currentConf.toString) {
         val promoted = org.apache.spark.sql.comet.DecimalPrecision
-          .promote(add, nullOnOverflow = true)
+          .promote(add)
         promoted match {
           case CheckOverflow(_, dt, _) =>
             assert(
@@ -63,4 +65,58 @@ class CometDecimalArithmeticViewSuite extends CometTestBase {
       }
     }
   }
+
+  test("issue #5075: DecimalPrecision.promote honours per-expression eval 
mode") {
+    val left = AttributeReference("a", DecimalType(10, 0))()
+    val right = AttributeReference("b", DecimalType(10, 0))()
+    val third = AttributeReference("c", DecimalType(10, 0))()
+
+    val operations: Seq[(
+        String,
+        (Expression, Expression, NumericEvalContext) => BinaryArithmetic,
+        ExprOuterClass.Expr => ExprOuterClass.MathExpr)] = Seq(
+      ("add", Add.apply, _.getAdd),
+      ("subtract", Subtract.apply, _.getSubtract),
+      ("multiply", Multiply.apply, _.getMultiply),
+      ("divide", Divide.apply, _.getDivide),
+      ("remainder", Remainder.apply, _.getRemainder))
+    val tryContext = NumericEvalContext(EvalMode.TRY, 
allowDecimalPrecisionLoss = true)
+    val ansiContext = NumericEvalContext(EvalMode.ANSI, 
allowDecimalPrecisionLoss = true)
+    val expressionTrees = operations.map { case (name, operation, getMathExpr) 
=>
+      (name, operation(operation(left, right, tryContext), third, 
ansiContext), getMathExpr)
+    }
+
+    Seq(false, true).foreach { sessionAnsiEnabled =>
+      withSQLConf(SQLConf.ANSI_ENABLED.key -> sessionAnsiEnabled.toString) {
+        expressionTrees.foreach { case (name, expression, getMathExpr) =>
+          val proto = QueryPlanSerde.exprToProto(expression, Seq(left, right, 
third)).get
+          assert(proto.hasCheckOverflow, s"$name under session 
ANSI=$sessionAnsiEnabled")
+          val ansiOverflow = proto.getCheckOverflow
+          assert(ansiOverflow.getFailOnError, s"$name under session 
ANSI=$sessionAnsiEnabled")
+
+          // Decimal division already adds its own CheckOverflow inside the 
one added by
+          // DecimalPrecision, so peel that wrapper before inspecting the 
Divide proto.
+          val mathExprProto =
+            if (name == "divide") {
+              assert(
+                ansiOverflow.getChild.hasCheckOverflow,
+                s"$name under session ANSI=$sessionAnsiEnabled")
+              val divideOverflow = ansiOverflow.getChild.getCheckOverflow
+              assert(
+                divideOverflow.getFailOnError,
+                s"$name under session ANSI=$sessionAnsiEnabled")
+              divideOverflow.getChild
+            } else {
+              ansiOverflow.getChild
+            }
+
+          val tryExprProto = getMathExpr(mathExprProto).getLeft
+          assert(tryExprProto.hasCheckOverflow, s"$name under session 
ANSI=$sessionAnsiEnabled")
+          assert(
+            !tryExprProto.getCheckOverflow.getFailOnError,
+            s"$name under session ANSI=$sessionAnsiEnabled")
+        }
+      }
+    }
+  }
 }


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

Reply via email to