This is an automated email from the ASF dual-hosted git repository.
philo-he 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 454d9c648b [GLUTEN-12413][VL] Enable ReusedExchange by normalizing
Generate pre-project expressions as Alias (#12945)
454d9c648b is described below
commit 454d9c648bb97618a3bf9ca75c11cb198bd510ba
Author: jiangjiangtian <[email protected]>
AuthorDate: Tue Oct 20 07:54:59 2026 +0800
[GLUTEN-12413][VL] Enable ReusedExchange by normalizing Generate
pre-project expressions as Alias (#12945)
---
.../gluten/execution/GenerateExecTransformer.scala | 9 ++---
.../gluten/execution/MiscOperatorSuite.scala | 42 ++++++++++++++++++++--
2 files changed, 45 insertions(+), 6 deletions(-)
diff --git
a/backends-velox/src/main/scala/org/apache/gluten/execution/GenerateExecTransformer.scala
b/backends-velox/src/main/scala/org/apache/gluten/execution/GenerateExecTransformer.scala
index 8a97f488a9..5b43396181 100644
---
a/backends-velox/src/main/scala/org/apache/gluten/execution/GenerateExecTransformer.scala
+++
b/backends-velox/src/main/scala/org/apache/gluten/execution/GenerateExecTransformer.scala
@@ -186,11 +186,12 @@ object PullOutGenerateProjectHelper extends
PullOutProjectHelper {
// NOTE: DO NOT use eliminateProjectList to create the project
list because
// newGeneratorChild can be a duplicated Attribute in
generate.child.output. The native
// side identifies the last field of projection as generator's
input.
- val newGeneratorChildren = Seq(expressionMap.values.head)
+ val aliasExpr = expressionMap.values.head
generate.copy(
- generator =
-
generate.generator.withNewChildren(newGeneratorChildren).asInstanceOf[Generator],
- child = ProjectExec(generate.child.output ++
newGeneratorChildren, generate.child)
+ generator = generate.generator
+ .withNewChildren(Seq(aliasExpr.toAttribute))
+ .asInstanceOf[Generator],
+ child = ProjectExec(generate.child.output ++ Seq(aliasExpr),
generate.child)
)
} else {
// generator.child is Attribute, no need to introduce a Project.
diff --git
a/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
b/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
index da2c0a569a..0eff200466 100644
---
a/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
+++
b/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
@@ -25,6 +25,7 @@ import org.apache.spark.sql.{DataFrame, Row}
import org.apache.spark.sql.catalyst.expressions.Cast
import org.apache.spark.sql.execution._
import org.apache.spark.sql.execution.adaptive.{AdaptiveSparkPlanHelper,
AQEShuffleReadExec, ColumnarAQEShuffleReadExec, ShuffleQueryStageExec}
+import org.apache.spark.sql.execution.exchange.ReusedExchangeExec
import org.apache.spark.sql.execution.joins.BaseJoinExec
import org.apache.spark.sql.execution.window.WindowExec
import org.apache.spark.sql.functions._
@@ -1201,14 +1202,14 @@ class MiscOperatorSuite extends
VeloxWholeStageTransformerSuite with AdaptiveSpa
}
}
- // Fallback for array(struct(...), null) literal.
+ // array(struct(...), null) literal is not a sub-expression of
generator.
runQueryAndCompare(s"""
|SELECT $func(array(
| named_struct('c1', 0, 'c2', 1),
| named_struct('c1', 2, 'c2', null),
| null));
|""".stripMargin) {
- checkSparkPlan[GenerateExec]
+ checkGlutenPlan[GenerateExecTransformer]
}
}
}
@@ -2404,4 +2405,41 @@ class MiscOperatorSuite extends
VeloxWholeStageTransformerSuite with AdaptiveSpa
assert(GlutenShuffleUtils.getCompressionCodec(conf) === codec)
}
}
+
+ test("exchange reuse with CTE lateral view explode") {
+ // This test verifies the fix for Generate pre-project canonicalization.
+ // When explode(split(col)) is used, a pre-project is inserted to compute
the
+ // split expression as an Alias before feeding it to the generator. Before
the
+ // fix, the generator use an alias expression as its sub-expression, but
Spark's
+ // doCanonicalize does not cope with it.
+ // This caused structurally identical plans from different query
+ // parts to have different canonicalized forms, preventing
ReuseExchangeExec
+ // from reusing exchanges.
+ // With AQE on and shuffle partitions > 1, the self-join on the CTE
produces
+ // two identical shuffle exchanges that should be reused.
+ withSQLConf(
+ SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "true",
+ SQLConf.SHUFFLE_PARTITIONS.key -> "2") {
+ runQueryAndCompare("""
+ |WITH exploded AS (
+ | SELECT l_orderkey, word
+ | FROM lineitem
+ | LATERAL VIEW explode(split(l_comment, ' ')) t AS
word
+ | WHERE l_orderkey < 10
+ |)
+ |SELECT count(*), sum(t1.l_orderkey + t2.l_orderkey)
+ |FROM exploded t1
+ |JOIN exploded t2 ON t1.word = t2.word
+ |""".stripMargin) {
+ df =>
+ val plan = df.queryExecution.executedPlan
+ assert(
+ collect(plan) { case p: GenerateExecTransformer => p }.nonEmpty,
+ "Expected GenerateExecTransformer in the plan")
+ assert(
+ collect(plan) { case re: ReusedExchangeExec => re }.nonEmpty,
+ "Expected ReusedExchangeExec in the plan")
+ }
+ }
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]