This is an automated email from the ASF dual-hosted git repository.
ulysses-you pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git
The following commit(s) were added to refs/heads/main by this push:
new 74796290ac [GLUTEN-12260][VL] Fix CheckOverflowTransformer using wrong
child type for cast decision in Spark-33 (#12261)
74796290ac is described below
commit 74796290acb9d100a3bf7499de78399164c25316
Author: Shilong Duan <[email protected]>
AuthorDate: Tue Jun 23 10:11:00 2026 +0800
[GLUTEN-12260][VL] Fix CheckOverflowTransformer using wrong child type for
cast decision in Spark-33 (#12261)
Fix: https://github.com/apache/gluten/issues/12260
## What changes are proposed in this pull request?
`CheckOverflowTransformer` reads `original.child.dataType` to decide
whether to insert a cast. For `BinaryArithmetic`, Spark's `.dataType` returns
`left.dataType` rather than the arithmetic result type. After child
transformers apply rescale optimizations, the actual output type may differ
from the Spark-declared type, and the cast is wrongly skipped.
The resulting substrait plan has decimal types that mismatch function
signatures. Velox's SimpleFunction validation rejects it, and
`ColumnarPartialProjectRule` falls the entire Project back to JVM. Result is
correct (via fallback) but native acceleration is lost.
### Reproducer
```sql
CREATE TABLE t1 (val BIGINT) USING parquet;
CREATE TABLE t2 (val BIGINT) USING parquet;
INSERT INTO t1 VALUES (200);
INSERT INTO t2 VALUES (100), (100), (100), (100), (100);
SELECT
a.val,
(a.val - COALESCE(SUM(b.val), 0) / 5.0)
/ (COALESCE(SUM(b.val), 0) / 5.0) AS growth_rate
FROM t1 a CROSS JOIN t2 b
GROUP BY a.val;
```
### Root cause:
https://github.com/apache/gluten/blob/fc90a7933afaf5518ad20a60a5d79d482cea5ef1/gluten-substrait/src/main/scala/org/apache/gluten/expression/UnaryExpressionTransformer.scala#L90
this read the Spark expression's declared type instead of the transformer's
actual output type.
### Fix
```diff
- original.child.dataType,
+ child.dataType,
```
## How was this patch tested?
**Before fix** — Project falls back to JVM:
```
== Final Plan ==
* Project (17) ← JVM, codegen id=3
+- VeloxColumnarToRow (16) ← extra C2R
conversion
+- ^ RegularHashAggregateExecTransformer (14)
+- ^ VeloxBroadcastNestedLoopJoinExecTransformer (13)
:- ^ InputIteratorTransformer (7)
: +- BroadcastQueryStage (5)
: +- ColumnarBroadcastExchange (4)
: +- RowToVeloxColumnar (3)
: +- * ColumnarToRow (2)
: +- BatchScan (1)
+- ^ InputIteratorTransformer (12)
+- RowToVeloxColumnar (10)
+- * ColumnarToRow (9)
+- BatchScan (8)
```
**After fix** — Project runs natively in Velox:
```
== Final Plan ==
VeloxColumnarToRow (17)
+- ^ ProjectExecTransformer (15) ← native Velox
Project
+- ^ RegularHashAggregateExecTransformer (14)
+- ^ VeloxBroadcastNestedLoopJoinExecTransformer (13)
:- ^ InputIteratorTransformer (7)
: +- BroadcastQueryStage (5)
: +- ColumnarBroadcastExchange (4)
: +- RowToVeloxColumnar (3)
: +- * ColumnarToRow (2)
: +- BatchScan (1)
+- ^ InputIteratorTransformer (12)
+- RowToVeloxColumnar (10)
+- * ColumnarToRow (9)
+- BatchScan (8)
```
Key differences:
- Node (15) changes from `Project` (JVM, `*` = codegen) to
`ProjectExecTransformer` (Velox native, `^` = transformer)
- `VeloxColumnarToRow` moves from **before** Project (forced conversion
to feed JVM) to **after** Project (deferred until output)
- Aggregate→Project pipeline stays in Velox without breaking
---
.../expression/UnaryExpressionTransformer.scala | 2 +-
.../GlutenCheckOverflowTransformerSuite.scala | 70 ++++++++++++++++++++++
2 files changed, 71 insertions(+), 1 deletion(-)
diff --git
a/gluten-substrait/src/main/scala/org/apache/gluten/expression/UnaryExpressionTransformer.scala
b/gluten-substrait/src/main/scala/org/apache/gluten/expression/UnaryExpressionTransformer.scala
index b762ec95b6..1c0faf599b 100644
---
a/gluten-substrait/src/main/scala/org/apache/gluten/expression/UnaryExpressionTransformer.scala
+++
b/gluten-substrait/src/main/scala/org/apache/gluten/expression/UnaryExpressionTransformer.scala
@@ -87,7 +87,7 @@ case class CheckOverflowTransformer(
context,
substraitExprName,
child.doTransform(context),
- original.child.dataType,
+ child.dataType,
original.dataType,
original.nullable,
original.nullOnOverflow)
diff --git
a/gluten-ut/test/src/test/scala/org/apache/gluten/expressions/GlutenCheckOverflowTransformerSuite.scala
b/gluten-ut/test/src/test/scala/org/apache/gluten/expressions/GlutenCheckOverflowTransformerSuite.scala
new file mode 100644
index 0000000000..d97dea2f38
--- /dev/null
+++
b/gluten-ut/test/src/test/scala/org/apache/gluten/expressions/GlutenCheckOverflowTransformerSuite.scala
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.gluten.expressions
+
+import org.apache.gluten.expression._
+import org.apache.gluten.substrait.SubstraitContext
+import org.apache.gluten.utils.BackendTestUtils
+
+import org.apache.spark.SparkConf
+import org.apache.spark.sql.GlutenQueryTest
+import org.apache.spark.sql.catalyst.expressions.{CheckOverflow, Literal}
+import org.apache.spark.sql.test.SharedSparkSession
+import org.apache.spark.sql.types.{Decimal, DecimalType}
+
+class GlutenCheckOverflowTransformerSuite extends GlutenQueryTest with
SharedSparkSession {
+
+ override protected def sparkConf: SparkConf = {
+ super.sparkConf
+ .set("spark.plugins", "org.apache.gluten.GlutenPlugin")
+ .set("spark.default.parallelism", "1")
+ .set("spark.memory.offHeap.enabled", "true")
+ .set("spark.memory.offHeap.size", "1024MB")
+ .set("spark.ui.enabled", "false")
+ }
+
+ testWithSpecifiedSparkVersion(
+ "CheckOverflow transformer casts transformed child type",
+ "3.3",
+ "3.4",
+ "3.5",
+ "4.0",
+ "4.1") {
+ assume(BackendTestUtils.isVeloxBackendLoaded())
+
+ val targetType = DecimalType(38, 17)
+ val transformedChildType = DecimalType(38, 18)
+ val original = CheckOverflow(
+ Literal(Decimal(0, targetType.precision, targetType.scale), targetType),
+ targetType,
+ nullOnOverflow = true)
+ val child = LiteralTransformer(
+ Literal(
+ Decimal(0, transformedChildType.precision, transformedChildType.scale),
+ transformedChildType))
+ assert(original.child.dataType != child.dataType)
+
+ val transformedNode =
+ CheckOverflowTransformer(ExpressionNames.CHECK_OVERFLOW, child, original)
+ .doTransform(new SubstraitContext)
+ .toProtobuf
+ assert(transformedNode.hasCast)
+ val castType = transformedNode.getCast.getType.getDecimal
+ assert(castType.getPrecision == targetType.precision)
+ assert(castType.getScale == targetType.scale)
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]