Backend
VL (Velox)
Bug description
When Gluten collects Delta write statistics, GlutenDeltaJobStatsTracker builds a small local Velox aggregation plan and assumes the offload always succeeds, i.e. that the transformed plan is always a WholeStageTransformer. When the stats expression references a data type / expression that Velox does not yet support (for example TIMESTAMP_NTZ), offload validation correctly rejects the plan and it stays a vanilla Spark ProjectExec. The unconditional cast to WholeStageTransformer then throws a ClassCastException, failing the whole write.
Expected behavior: an unsupported type/expression in the Delta stats plan should induce a fallback to Spark's native Delta stats collection (the DeltaJobStatisticsTracker delegate path), not crash the write.
Actual behavior: the write task fails with:
java.lang.ClassCastException: class org.apache.spark.sql.execution.ProjectExec cannot be cast
to class org.apache.gluten.execution.WholeStageTransformer
at org.apache.spark.sql.delta.stats.GlutenDeltaJobStatsTracker$GlutenDeltaTaskStatsTracker.<init>(GlutenDeltaJobStatsTracker.scala:174)
at org.apache.spark.sql.delta.stats.GlutenDeltaJobStatsTracker.newTaskInstance(GlutenDeltaJobStatsTracker.scala:80)
at org.apache.spark.sql.execution.datasources.FileFormatDataWriter.$anonfun$statsTrackers$1(FileFormatDataWriter.scala:63)
at scala.collection.immutable.List.map(List.scala:236)
Root cause
In GlutenDeltaTaskStatsTracker the transform result is cast unconditionally:
val veloxTransformer = transformRule(projOp)
val wholeStageTransformer = ColumnarCollapseTransformStages(config)(veloxTransformer)
.asInstanceOf[WholeStageTransformer] // <-- throws when offload was rejected
.child
.asInstanceOf[TransformSupport]
transformRule runs the offload heuristic with a validator. If the stats expression contains something Velox can't handle (e.g. TIMESTAMP_NTZ in an aggregate/function), the validator rejects offload and transformRule returns the original vanilla ProjectExec. ColumnarCollapseTransformStages then leaves it as-is, and .asInstanceOf[WholeStageTransformer] fails.
This affects both source variants:
backends-velox/src-delta40/main/scala/org/apache/spark/sql/delta/stats/GlutenDeltaJobStatsTracker.scala (~line 174-177)
backends-velox/src-delta33/main/scala/org/apache/spark/sql/delta/stats/GlutenDeltaJobStatsTracker.scala (~line 170-173)
How to reproduce
Run the Delta Spark UTs on the Gluten Velox bundle (as in #12388). Any test that collects Delta statistics on a TIMESTAMP_NTZ column reproduces it, e.g.:
data skipping on TIMESTAMP_NTZ
recompute stats multiple columns and files
More generally: create/write a Delta table whose statistics columns include a type not yet offloadable by Velox and trigger stats collection.
Proposed fix
Per the discussion in #12388, rather than fixing TIMESTAMP_NTZ support itself (tracked separately in #11622), make the Gluten Delta stats path detect a rejected/failed offload and fall back to the native DeltaJobStatisticsTracker (or the existing fallback tracker) instead of assuming a WholeStageTransformer. Unsupported operations should induce a fallback for Delta plans generally.
Gluten version
main branch
Spark version
spark-4.0.x and later (reproduced on the Delta pipeline running Spark 4.1 / Delta v4.2.0; the same unchecked cast exists in the Delta 3.3 source variant).
References
This issue was written with the assistance of AI tooling (GitHub Copilot CLI).
Backend
VL (Velox)
Bug description
When Gluten collects Delta write statistics,
GlutenDeltaJobStatsTrackerbuilds a small local Velox aggregation plan and assumes the offload always succeeds, i.e. that the transformed plan is always aWholeStageTransformer. When the stats expression references a data type / expression that Velox does not yet support (for exampleTIMESTAMP_NTZ), offload validation correctly rejects the plan and it stays a vanilla SparkProjectExec. The unconditional cast toWholeStageTransformerthen throws aClassCastException, failing the whole write.Expected behavior: an unsupported type/expression in the Delta stats plan should induce a fallback to Spark's native Delta stats collection (the
DeltaJobStatisticsTrackerdelegate path), not crash the write.Actual behavior: the write task fails with:
Root cause
In
GlutenDeltaTaskStatsTrackerthe transform result is cast unconditionally:transformRuleruns the offload heuristic with a validator. If the stats expression contains something Velox can't handle (e.g.TIMESTAMP_NTZin an aggregate/function), the validator rejects offload andtransformRulereturns the original vanillaProjectExec.ColumnarCollapseTransformStagesthen leaves it as-is, and.asInstanceOf[WholeStageTransformer]fails.This affects both source variants:
backends-velox/src-delta40/main/scala/org/apache/spark/sql/delta/stats/GlutenDeltaJobStatsTracker.scala(~line 174-177)backends-velox/src-delta33/main/scala/org/apache/spark/sql/delta/stats/GlutenDeltaJobStatsTracker.scala(~line 170-173)How to reproduce
Run the Delta Spark UTs on the Gluten Velox bundle (as in #12388). Any test that collects Delta statistics on a
TIMESTAMP_NTZcolumn reproduces it, e.g.:data skipping on TIMESTAMP_NTZrecompute stats multiple columns and filesMore generally: create/write a Delta table whose statistics columns include a type not yet offloadable by Velox and trigger stats collection.
Proposed fix
Per the discussion in #12388, rather than fixing
TIMESTAMP_NTZsupport itself (tracked separately in #11622), make the Gluten Delta stats path detect a rejected/failed offload and fall back to the nativeDeltaJobStatisticsTracker(or the existing fallback tracker) instead of assuming aWholeStageTransformer. Unsupported operations should induce a fallback for Delta plans generally.Gluten version
main branch
Spark version
spark-4.0.x and later (reproduced on the Delta pipeline running Spark 4.1 / Delta v4.2.0; the same unchecked cast exists in the Delta 3.3 source variant).
References
This issue was written with the assistance of AI tooling (GitHub Copilot CLI).