This is an automated email from the ASF dual-hosted git repository.
zml1206 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 8a60e5901b [GLUTEN-11550][VL] Fix GlutenRemoveRedundantProjectsSuite
in Spark 4.x (#12506)
8a60e5901b is described below
commit 8a60e5901ba70842a5ddfc3fd5a70f0a1a80d4c2
Author: Mingliang Zhu <[email protected]>
AuthorDate: Thu Jul 16 15:18:37 2026 +0800
[GLUTEN-11550][VL] Fix GlutenRemoveRedundantProjectsSuite in Spark 4.x
(#12506)
---
.../gluten/utils/velox/VeloxTestSettings.scala | 17 +-
.../GlutenRemoveRedundantProjectsSuite.scala | 188 ++++++++++++++++++++-
.../gluten/utils/velox/VeloxTestSettings.scala | 17 +-
.../GlutenRemoveRedundantProjectsSuite.scala | 188 ++++++++++++++++++++-
4 files changed, 404 insertions(+), 6 deletions(-)
diff --git
a/gluten-ut/spark40/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
b/gluten-ut/spark40/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
index 63bcb02f14..8a4862b90a 100644
---
a/gluten-ut/spark40/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
+++
b/gluten-ut/spark40/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
@@ -741,7 +741,22 @@ class VeloxTestSettings extends BackendTestSettings {
disableSuite[GlutenProjectedOrderingAndPartitioningSuite](
"Validates Spark planner output ordering and partitioning metadata")
enableSuite[GlutenQueryPlanningTrackerEndToEndSuite]
- // TODO: 4.x enableSuite[GlutenRemoveRedundantProjectsSuite] // 14 failures
+ enableSuite[GlutenRemoveRedundantProjectsSuite]
+ // Rewrite as result checks because Gluten transforms and may pull out
additional projects.
+ .exclude("project with filter")
+ .exclude("project with specific column ordering")
+ .exclude("project with extra columns")
+ .exclude("project with fewer columns")
+ .exclude("aggregate without ordering requirement")
+ .exclude("aggregate with ordering requirement")
+ .exclude("join without ordering requirement")
+ .exclude("join with ordering requirement")
+ .exclude("window function")
+ .exclude("generate should require column ordering")
+ .exclude("subquery")
+ .exclude("SPARK-33697: UnionExec should require column ordering")
+ .exclude("SPARK-33697: remove redundant projects under expand")
+ .exclude("SPARK-36020: Project should not be removed when child's logical
link is different")
enableSuite[GlutenRemoveRedundantSortsSuite]
// Rewrite as it check spark SortExec.
.includeAllGlutenTests()
diff --git
a/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantProjectsSuite.scala
b/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantProjectsSuite.scala
index e1984b3338..52ad850aa3 100644
---
a/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantProjectsSuite.scala
+++
b/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantProjectsSuite.scala
@@ -16,8 +16,192 @@
*/
package org.apache.spark.sql.execution
-import org.apache.spark.sql.GlutenSQLTestsTrait
+import org.apache.spark.sql.{GlutenSQLTestsTrait, Row}
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.types.StructType
class GlutenRemoveRedundantProjectsSuite
extends RemoveRedundantProjectsSuite
- with GlutenSQLTestsTrait {}
+ with GlutenSQLTestsTrait {
+
+ // The original tests count Spark ProjectExec nodes, while Gluten converts
offloaded projects to
+ // ProjectExecTransformer. PullOutPreProject and PullOutPostProject may also
insert additional
+ // projects while rewriting the Spark physical plan, so project counts are
not directly
+ // comparable. Therefore, these tests only verify that query results are
identical with redundant
+ // project removal enabled and disabled.
+ private def assertProjectExec(query: String, enabled: Int, disabled: Int):
Unit = {
+ val df = sql(query)
+ // When enabling AQE, the DPP subquery filters is replaced in runtime.
+ df.collect()
+ // assertProjectExecCount(df, enabled)
+ val result = df.collect()
+ withSQLConf(SQLConf.REMOVE_REDUNDANT_PROJECTS_ENABLED.key -> "false") {
+ val df2 = sql(query)
+ df2.collect()
+ // assertProjectExecCount(df2, disabled)
+ checkAnswer(df2, result)
+ }
+ }
+
+ testGluten("project with filter") {
+ val query = "select * from testView where a > 5"
+ assertProjectExec(query, 0, 1)
+ }
+
+ testGluten("project with specific column ordering") {
+ val query = "select key, a, b, c from testView"
+ assertProjectExec(query, 1, 1)
+ }
+
+ testGluten("project with extra columns") {
+ val query = "select a, b, c, key, a from testView"
+ assertProjectExec(query, 1, 1)
+ }
+
+ testGluten("project with fewer columns") {
+ val query = "select a from testView where a > 3"
+ assertProjectExec(query, 1, 1)
+ }
+
+ testGluten("aggregate without ordering requirement") {
+ val query = "select sum(a) as sum_a, key, last(b) as last_b " +
+ "from (select key, a, b from testView where a > 100) group by key"
+ assertProjectExec(query, 0, 1)
+ }
+
+ testGluten("aggregate with ordering requirement") {
+ val query = "select a, sum(b) as sum_b from testView group by a"
+ assertProjectExec(query, 1, 1)
+ }
+
+ testGluten("join without ordering requirement") {
+ val query = "select t1.key, t2.key, t1.a, t2.b from (select key, a, b, c
from testView)" +
+ " as t1 join (select key, a, b, c from testView) as t2 on t1.c > t2.c
and t1.key > 10"
+ assertProjectExec(query, 1, 3)
+ }
+
+ testGluten("join with ordering requirement") {
+ val query = "select * from (select key, a, c, b from testView) as t1 join
" +
+ "(select key, a, b, c from testView) as t2 on t1.key = t2.key where t2.a
> 50"
+ assertProjectExec(query, 2, 2)
+ }
+
+ testGluten("window function") {
+ val query = "select key, b, avg(a) over (partition by key order by a " +
+ "rows between 1 preceding and 1 following) as avg from testView"
+ assertProjectExec(query, 1, 2)
+ }
+
+ testGluten("generate should require column ordering") {
+ withTempView("testData") {
+ spark.range(0, 10, 1)
+ .selectExpr("id as key", "id * 2 as a", "id * 3 as b")
+ .createOrReplaceTempView("testData")
+
+ val data = sql("select key, a, b, count(*) from testData group by key,
a, b limit 2")
+ val df = data.selectExpr("a", "b", "key", "explode(array(key, a, b)) as
d").filter("d > 0")
+ df.collect()
+ val plan = df.queryExecution.executedPlan
+ val numProjects = collectWithSubqueries(plan) { case p: ProjectExec => p
}.length
+
+ // Create a new plan that reverse the GenerateExec output and add a new
ProjectExec between
+ // GenerateExec and its child. This is to test if the ProjectExec is
removed, the output of
+ // the query will be incorrect.
+ val newPlan = stripAQEPlan(plan).transform {
+ case g @ GenerateExec(_, requiredChildOutput, _, _, child) =>
+ g.copy(
+ requiredChildOutput = requiredChildOutput.reverse,
+ child = ProjectExec(requiredChildOutput.reverse, child))
+ }
+
+ // Re-apply remove redundant project rule.
+ val rule = RemoveRedundantProjects
+ val newExecutedPlan = rule.apply(newPlan)
+ // The manually added ProjectExec node shouldn't be removed.
+ // assert(collectWithSubqueries(newExecutedPlan) {
+ // case p: ProjectExec => p
+ // }.size == numProjects + 1)
+
+ // Check the original plan's output and the new plan's output are the
same.
+ val expectedRows = plan.executeCollect()
+ val actualRows = newExecutedPlan.executeCollect()
+ assert(expectedRows.length == actualRows.length)
+ expectedRows.zip(actualRows).foreach { case (expected, actual) =>
assert(expected == actual) }
+ }
+ }
+
+ testGluten("subquery") {
+ withTempView("testData") {
+ val data = spark.sparkContext.parallelize((1 to 100).map(i => Row(i,
i.toString)))
+ val schema = new StructType().add("key", "int").add("value", "string")
+ spark.createDataFrame(data, schema).createOrReplaceTempView("testData")
+ val query = "select key, value from testData where key in " +
+ "(select sum(a) from testView where a > 5 group by key)"
+ assertProjectExec(query, 0, 1)
+ }
+ }
+
+ testGluten("SPARK-33697: UnionExec should require column ordering") {
+ withTable("t1", "t2") {
+ spark.range(-10, 20)
+ .selectExpr(
+ "id",
+ "date_add(date '1950-01-01', cast(id as int)) as datecol",
+ "cast(id as string) strcol")
+ .write.mode("overwrite").format("parquet").saveAsTable("t1")
+ spark.range(-10, 20)
+ .selectExpr(
+ "cast(id as string) strcol",
+ "id",
+ "date_add(date '1950-01-01', cast(id as int)) as datecol")
+ .write.mode("overwrite").format("parquet").saveAsTable("t2")
+
+ val queryTemplate =
+ """
+ |SELECT DISTINCT datecol, strcol FROM
+ |(
+ |(SELECT datecol, id, strcol from t1)
+ | %s
+ |(SELECT datecol, id, strcol from t2)
+ |)
+ |""".stripMargin
+
+ Seq(("UNION", 1, 2), ("UNION ALL", 1, 2)).foreach {
+ case (setOperation, enabled, disabled) =>
+ val query = queryTemplate.format(setOperation)
+ assertProjectExec(query, enabled = enabled, disabled = disabled)
+ }
+ }
+ }
+
+ testGluten("SPARK-33697: remove redundant projects under expand") {
+ val query =
+ """
+ |SELECT t1.key, t2.key, sum(t1.a) AS s1, sum(t2.b) AS s2 FROM
+ |(SELECT a, key FROM testView) t1
+ |JOIN
+ |(SELECT b, key FROM testView) t2
+ |ON t1.key = t2.key
+ |GROUP BY t1.key, t2.key GROUPING SETS(t1.key, t2.key)
+ |ORDER BY t1.key, t2.key, s1, s2
+ |LIMIT 10
+ |""".stripMargin
+ // The Project above the Expand is not removed due to SPARK-36020.
+ assertProjectExec(query, 1, 3)
+ }
+
+ testGluten("SPARK-36020: Project should not be removed when child's logical
link is different") {
+ val query =
+ """
+ |WITH t AS (
+ | SELECT key, a, b, c, explode(d) AS d FROM testView
+ |)
+ |SELECT t1.key, t1.d, t2.key
+ |FROM (SELECT d, key FROM t) t1
+ |JOIN testView t2 ON t1.key = t2.key
+ |""".stripMargin
+ // The ProjectExec above the GenerateExec should not be removed because
+ // they have different logical links.
+ assertProjectExec(query, enabled = 2, disabled = 3)
+ }
+}
diff --git
a/gluten-ut/spark41/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
b/gluten-ut/spark41/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
index d6c003a756..6ab400070a 100644
---
a/gluten-ut/spark41/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
+++
b/gluten-ut/spark41/src/test/scala/org/apache/gluten/utils/velox/VeloxTestSettings.scala
@@ -720,7 +720,22 @@ class VeloxTestSettings extends BackendTestSettings {
disableSuite[GlutenProjectedOrderingAndPartitioningSuite](
"Validates Spark planner output ordering and partitioning metadata")
enableSuite[GlutenQueryPlanningTrackerEndToEndSuite]
- // TODO: 4.x enableSuite[GlutenRemoveRedundantProjectsSuite] // 14 failures
+ enableSuite[GlutenRemoveRedundantProjectsSuite]
+ // Rewrite as result checks because Gluten transforms and may pull out
additional projects.
+ .exclude("project with filter")
+ .exclude("project with specific column ordering")
+ .exclude("project with extra columns")
+ .exclude("project with fewer columns")
+ .exclude("aggregate without ordering requirement")
+ .exclude("aggregate with ordering requirement")
+ .exclude("join without ordering requirement")
+ .exclude("join with ordering requirement")
+ .exclude("window function")
+ .exclude("generate should require column ordering")
+ .exclude("subquery")
+ .exclude("SPARK-33697: UnionExec should require column ordering")
+ .exclude("SPARK-33697: remove redundant projects under expand")
+ .exclude("SPARK-36020: Project should not be removed when child's logical
link is different")
enableSuite[GlutenRemoveRedundantSortsSuite]
// Rewrite as it check spark SortExec.
.includeAllGlutenTests()
diff --git
a/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantProjectsSuite.scala
b/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantProjectsSuite.scala
index e1984b3338..52ad850aa3 100644
---
a/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantProjectsSuite.scala
+++
b/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantProjectsSuite.scala
@@ -16,8 +16,192 @@
*/
package org.apache.spark.sql.execution
-import org.apache.spark.sql.GlutenSQLTestsTrait
+import org.apache.spark.sql.{GlutenSQLTestsTrait, Row}
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.types.StructType
class GlutenRemoveRedundantProjectsSuite
extends RemoveRedundantProjectsSuite
- with GlutenSQLTestsTrait {}
+ with GlutenSQLTestsTrait {
+
+ // The original tests count Spark ProjectExec nodes, while Gluten converts
offloaded projects to
+ // ProjectExecTransformer. PullOutPreProject and PullOutPostProject may also
insert additional
+ // projects while rewriting the Spark physical plan, so project counts are
not directly
+ // comparable. Therefore, these tests only verify that query results are
identical with redundant
+ // project removal enabled and disabled.
+ private def assertProjectExec(query: String, enabled: Int, disabled: Int):
Unit = {
+ val df = sql(query)
+ // When enabling AQE, the DPP subquery filters is replaced in runtime.
+ df.collect()
+ // assertProjectExecCount(df, enabled)
+ val result = df.collect()
+ withSQLConf(SQLConf.REMOVE_REDUNDANT_PROJECTS_ENABLED.key -> "false") {
+ val df2 = sql(query)
+ df2.collect()
+ // assertProjectExecCount(df2, disabled)
+ checkAnswer(df2, result)
+ }
+ }
+
+ testGluten("project with filter") {
+ val query = "select * from testView where a > 5"
+ assertProjectExec(query, 0, 1)
+ }
+
+ testGluten("project with specific column ordering") {
+ val query = "select key, a, b, c from testView"
+ assertProjectExec(query, 1, 1)
+ }
+
+ testGluten("project with extra columns") {
+ val query = "select a, b, c, key, a from testView"
+ assertProjectExec(query, 1, 1)
+ }
+
+ testGluten("project with fewer columns") {
+ val query = "select a from testView where a > 3"
+ assertProjectExec(query, 1, 1)
+ }
+
+ testGluten("aggregate without ordering requirement") {
+ val query = "select sum(a) as sum_a, key, last(b) as last_b " +
+ "from (select key, a, b from testView where a > 100) group by key"
+ assertProjectExec(query, 0, 1)
+ }
+
+ testGluten("aggregate with ordering requirement") {
+ val query = "select a, sum(b) as sum_b from testView group by a"
+ assertProjectExec(query, 1, 1)
+ }
+
+ testGluten("join without ordering requirement") {
+ val query = "select t1.key, t2.key, t1.a, t2.b from (select key, a, b, c
from testView)" +
+ " as t1 join (select key, a, b, c from testView) as t2 on t1.c > t2.c
and t1.key > 10"
+ assertProjectExec(query, 1, 3)
+ }
+
+ testGluten("join with ordering requirement") {
+ val query = "select * from (select key, a, c, b from testView) as t1 join
" +
+ "(select key, a, b, c from testView) as t2 on t1.key = t2.key where t2.a
> 50"
+ assertProjectExec(query, 2, 2)
+ }
+
+ testGluten("window function") {
+ val query = "select key, b, avg(a) over (partition by key order by a " +
+ "rows between 1 preceding and 1 following) as avg from testView"
+ assertProjectExec(query, 1, 2)
+ }
+
+ testGluten("generate should require column ordering") {
+ withTempView("testData") {
+ spark.range(0, 10, 1)
+ .selectExpr("id as key", "id * 2 as a", "id * 3 as b")
+ .createOrReplaceTempView("testData")
+
+ val data = sql("select key, a, b, count(*) from testData group by key,
a, b limit 2")
+ val df = data.selectExpr("a", "b", "key", "explode(array(key, a, b)) as
d").filter("d > 0")
+ df.collect()
+ val plan = df.queryExecution.executedPlan
+ val numProjects = collectWithSubqueries(plan) { case p: ProjectExec => p
}.length
+
+ // Create a new plan that reverse the GenerateExec output and add a new
ProjectExec between
+ // GenerateExec and its child. This is to test if the ProjectExec is
removed, the output of
+ // the query will be incorrect.
+ val newPlan = stripAQEPlan(plan).transform {
+ case g @ GenerateExec(_, requiredChildOutput, _, _, child) =>
+ g.copy(
+ requiredChildOutput = requiredChildOutput.reverse,
+ child = ProjectExec(requiredChildOutput.reverse, child))
+ }
+
+ // Re-apply remove redundant project rule.
+ val rule = RemoveRedundantProjects
+ val newExecutedPlan = rule.apply(newPlan)
+ // The manually added ProjectExec node shouldn't be removed.
+ // assert(collectWithSubqueries(newExecutedPlan) {
+ // case p: ProjectExec => p
+ // }.size == numProjects + 1)
+
+ // Check the original plan's output and the new plan's output are the
same.
+ val expectedRows = plan.executeCollect()
+ val actualRows = newExecutedPlan.executeCollect()
+ assert(expectedRows.length == actualRows.length)
+ expectedRows.zip(actualRows).foreach { case (expected, actual) =>
assert(expected == actual) }
+ }
+ }
+
+ testGluten("subquery") {
+ withTempView("testData") {
+ val data = spark.sparkContext.parallelize((1 to 100).map(i => Row(i,
i.toString)))
+ val schema = new StructType().add("key", "int").add("value", "string")
+ spark.createDataFrame(data, schema).createOrReplaceTempView("testData")
+ val query = "select key, value from testData where key in " +
+ "(select sum(a) from testView where a > 5 group by key)"
+ assertProjectExec(query, 0, 1)
+ }
+ }
+
+ testGluten("SPARK-33697: UnionExec should require column ordering") {
+ withTable("t1", "t2") {
+ spark.range(-10, 20)
+ .selectExpr(
+ "id",
+ "date_add(date '1950-01-01', cast(id as int)) as datecol",
+ "cast(id as string) strcol")
+ .write.mode("overwrite").format("parquet").saveAsTable("t1")
+ spark.range(-10, 20)
+ .selectExpr(
+ "cast(id as string) strcol",
+ "id",
+ "date_add(date '1950-01-01', cast(id as int)) as datecol")
+ .write.mode("overwrite").format("parquet").saveAsTable("t2")
+
+ val queryTemplate =
+ """
+ |SELECT DISTINCT datecol, strcol FROM
+ |(
+ |(SELECT datecol, id, strcol from t1)
+ | %s
+ |(SELECT datecol, id, strcol from t2)
+ |)
+ |""".stripMargin
+
+ Seq(("UNION", 1, 2), ("UNION ALL", 1, 2)).foreach {
+ case (setOperation, enabled, disabled) =>
+ val query = queryTemplate.format(setOperation)
+ assertProjectExec(query, enabled = enabled, disabled = disabled)
+ }
+ }
+ }
+
+ testGluten("SPARK-33697: remove redundant projects under expand") {
+ val query =
+ """
+ |SELECT t1.key, t2.key, sum(t1.a) AS s1, sum(t2.b) AS s2 FROM
+ |(SELECT a, key FROM testView) t1
+ |JOIN
+ |(SELECT b, key FROM testView) t2
+ |ON t1.key = t2.key
+ |GROUP BY t1.key, t2.key GROUPING SETS(t1.key, t2.key)
+ |ORDER BY t1.key, t2.key, s1, s2
+ |LIMIT 10
+ |""".stripMargin
+ // The Project above the Expand is not removed due to SPARK-36020.
+ assertProjectExec(query, 1, 3)
+ }
+
+ testGluten("SPARK-36020: Project should not be removed when child's logical
link is different") {
+ val query =
+ """
+ |WITH t AS (
+ | SELECT key, a, b, c, explode(d) AS d FROM testView
+ |)
+ |SELECT t1.key, t1.d, t2.key
+ |FROM (SELECT d, key FROM t) t1
+ |JOIN testView t2 ON t1.key = t2.key
+ |""".stripMargin
+ // The ProjectExec above the GenerateExec should not be removed because
+ // they have different logical links.
+ assertProjectExec(query, enabled = 2, disabled = 3)
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]