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 061b799fbb [VL] Fix GlutenRemoveRedundantSortsSuite in Spark 4.x
(#12497)
061b799fbb is described below
commit 061b799fbb7dd7fbe7952daa375c1958d84d2df0
Author: Mingliang Zhu <[email protected]>
AuthorDate: Tue Jul 14 13:30:45 2026 +0800
[VL] Fix GlutenRemoveRedundantSortsSuite in Spark 4.x (#12497)
---
.../gluten/utils/velox/VeloxTestSettings.scala | 4 +-
.../GlutenRemoveRedundantSortsSuite.scala | 155 ++++++++++++++++++++-
.../gluten/utils/velox/VeloxTestSettings.scala | 4 +-
.../GlutenRemoveRedundantSortsSuite.scala | 155 ++++++++++++++++++++-
4 files changed, 312 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 48c70d1713..a7367ecd7a 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
@@ -738,7 +738,9 @@ class VeloxTestSettings extends BackendTestSettings {
// TODO: 4.x enableSuite[GlutenProjectedOrderingAndPartitioningSuite] // 6
failures
enableSuite[GlutenQueryPlanningTrackerEndToEndSuite]
// TODO: 4.x enableSuite[GlutenRemoveRedundantProjectsSuite] // 14 failures
- // TODO: 4.x enableSuite[GlutenRemoveRedundantSortsSuite] // 1 failure
+ enableSuite[GlutenRemoveRedundantSortsSuite]
+ // Rewrite as it check spark SortExec.
+ .includeAllGlutenTests()
enableSuite[GlutenRowToColumnConverterSuite]
enableSuite[GlutenSQLExecutionSuite]
enableSuite[GlutenSQLFunctionSuite]
diff --git
a/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantSortsSuite.scala
b/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantSortsSuite.scala
index 02df13e30c..5c4b439d41 100644
---
a/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantSortsSuite.scala
+++
b/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantSortsSuite.scala
@@ -16,6 +16,157 @@
*/
package org.apache.spark.sql.execution
-import org.apache.spark.sql.GlutenSQLTestsTrait
+import org.apache.gluten.config.GlutenConfig
+import org.apache.gluten.execution.SortExecTransformer
-class GlutenRemoveRedundantSortsSuite extends RemoveRedundantSortsSuite with
GlutenSQLTestsTrait {}
+import org.apache.spark.SparkConf
+import org.apache.spark.sql.{DataFrame, GlutenSQLTestsTrait}
+import org.apache.spark.sql.catalyst.plans.physical.{RangePartitioning,
UnknownPartitioning}
+import org.apache.spark.sql.execution.joins.ShuffledJoin
+import org.apache.spark.sql.internal.SQLConf
+
+class GlutenRemoveRedundantSortsSuite extends RemoveRedundantSortsSuite
+ with GlutenSQLTestsTrait {
+ import testImplicits._
+
+ override def sparkConf: SparkConf = {
+ super.sparkConf
+ .set(GlutenConfig.COLUMNAR_FORCE_SHUFFLED_HASH_JOIN_ENABLED.key, "false")
+ }
+
+ private def checkNumSorts(df: DataFrame, count: Int): Unit = {
+ val plan = df.queryExecution.executedPlan
+ assert(collectWithSubqueries(plan) { case s: SortExecTransformer => s
}.length == count)
+ }
+
+ private def checkSorts(query: String, enabledCount: Int, disabledCount:
Int): Unit = {
+ withSQLConf(SQLConf.REMOVE_REDUNDANT_SORTS_ENABLED.key -> "true") {
+ val df = sql(query)
+ checkNumSorts(df, enabledCount)
+ val result = df.collect()
+ withSQLConf(SQLConf.REMOVE_REDUNDANT_SORTS_ENABLED.key -> "false") {
+ val df = sql(query)
+ checkNumSorts(df, disabledCount)
+ checkAnswer(df, result)
+ }
+ }
+ }
+
+ testGluten("remove redundant sorts with limit") {
+ withTempView("t") {
+ spark.range(100).select($"id".as("key")).createOrReplaceTempView("t")
+ val query =
+ """
+ |SELECT key FROM
+ | (SELECT key FROM t WHERE key > 10 ORDER BY key DESC LIMIT 10)
+ |ORDER BY key DESC
+ |""".stripMargin
+ checkSorts(query, 0, 1)
+ }
+ }
+
+ testGluten("remove redundant sorts with broadcast hash join") {
+ withTempView("t1", "t2") {
+ spark.range(1000).select($"id".as("key")).createOrReplaceTempView("t1")
+ spark.range(1000).select($"id".as("key")).createOrReplaceTempView("t2")
+
+ val queryTemplate = """
+ |SELECT /*+ BROADCAST(%s) */ t1.key FROM
+ | (SELECT key FROM t1 WHERE key > 10 ORDER BY key
DESC LIMIT 10) t1
+ |JOIN
+ | (SELECT key FROM t2 WHERE key > 50 ORDER BY key
DESC LIMIT 100) t2
+ |ON t1.key = t2.key
+ |ORDER BY %s
+ """.stripMargin
+
+ // No sort should be removed since the stream side (t2) order DESC
+ // does not satisfy the required sort order ASC.
+ val buildLeftOrderByRightAsc = queryTemplate.format("t1", "t2.key ASC")
+ checkSorts(buildLeftOrderByRightAsc, 1, 1)
+
+ // The top sort node should be removed since the stream side (t2) order
DESC already
+ // satisfies the required sort order DESC.
+ val buildLeftOrderByRightDesc = queryTemplate.format("t1", "t2.key DESC")
+ checkSorts(buildLeftOrderByRightDesc, 0, 1)
+
+ // No sort should be removed since the sort ordering from broadcast-hash
join is based
+ // on the stream side (t2) and the required sort order is from t1.
+ val buildLeftOrderByLeftDesc = queryTemplate.format("t1", "t1.key DESC")
+ checkSorts(buildLeftOrderByLeftDesc, 1, 1)
+
+ // The top sort node should be removed since the stream side (t1) order
DESC already
+ // satisfies the required sort order DESC.
+ val buildRightOrderByLeftDesc = queryTemplate.format("t2", "t1.key DESC")
+ checkSorts(buildRightOrderByLeftDesc, 0, 1)
+ }
+ }
+
+ testGluten("remove redundant sorts with sort merge join") {
+ withTempView("t1", "t2") {
+ spark.range(1000).select($"id".as("key")).createOrReplaceTempView("t1")
+ spark.range(1000).select($"id".as("key")).createOrReplaceTempView("t2")
+ val query = """
+ |SELECT /*+ MERGE(t1) */ t1.key FROM
+ | (SELECT key FROM t1 WHERE key > 10 ORDER BY key DESC
LIMIT 10) t1
+ |JOIN
+ | (SELECT key FROM t2 WHERE key > 50 ORDER BY key DESC
LIMIT 100) t2
+ |ON t1.key = t2.key
+ |ORDER BY t1.key
+ """.stripMargin
+
+ val queryAsc = query + " ASC"
+ checkSorts(queryAsc, 2, 3)
+
+ // The top level sort should not be removed since the child output
ordering is ASC and
+ // the required ordering is DESC.
+ val queryDesc = query + " DESC"
+ checkSorts(queryDesc, 3, 3)
+ }
+ }
+
+ testGluten("cached sorted data doesn't need to be re-sorted") {
+ withSQLConf(SQLConf.REMOVE_REDUNDANT_SORTS_ENABLED.key -> "true") {
+ val df =
spark.range(1000).select($"id".as("key")).sort($"key".desc).cache()
+ df.collect()
+ val resorted = df.sort($"key".desc)
+ val sortedAsc = df.sort($"key".asc)
+ checkNumSorts(df, 0)
+ checkNumSorts(resorted, 0)
+ checkNumSorts(sortedAsc, 1)
+ val result = resorted.collect()
+ withSQLConf(SQLConf.REMOVE_REDUNDANT_SORTS_ENABLED.key -> "false") {
+ val resorted = df.sort($"key".desc)
+ resorted.collect()
+ checkNumSorts(resorted, 1)
+ checkAnswer(resorted, result)
+ }
+ }
+ }
+
+ testGluten("SPARK-33472: shuffled join with different left and right side
partition numbers") {
+ withTempView("t1", "t2") {
+ spark.range(0, 100, 1,
2).select($"id".as("key")).createOrReplaceTempView("t1")
+ (0 to 100).toDF("key").createOrReplaceTempView("t2")
+
+ val queryTemplate = """
+ |SELECT /*+ %s(t1) */ t1.key
+ |FROM t1 JOIN t2 ON t1.key = t2.key
+ |WHERE t1.key > 10 AND t2.key < 50
+ |ORDER BY t1.key ASC
+ """.stripMargin
+
+ Seq(("MERGE", 3), ("SHUFFLE_HASH", 1)).foreach {
+ case (hint, count) =>
+ val query = queryTemplate.format(hint)
+ val df = sql(query)
+ val sparkPlan = df.queryExecution.sparkPlan
+ val join = sparkPlan.collect { case j: ShuffledJoin => j }.head
+ val leftPartitioning = join.left.outputPartitioning
+ assert(leftPartitioning.isInstanceOf[RangePartitioning])
+ assert(leftPartitioning.numPartitions == 2)
+ assert(join.right.outputPartitioning == UnknownPartitioning(0))
+ checkSorts(query, count, count)
+ }
+ }
+ }
+}
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 a6a5c67396..18fc0e2c22 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
@@ -717,7 +717,9 @@ class VeloxTestSettings extends BackendTestSettings {
// TODO: 4.x enableSuite[GlutenProjectedOrderingAndPartitioningSuite] // 6
failures
enableSuite[GlutenQueryPlanningTrackerEndToEndSuite]
// TODO: 4.x enableSuite[GlutenRemoveRedundantProjectsSuite] // 14 failures
- // TODO: 4.x enableSuite[GlutenRemoveRedundantSortsSuite] // 1 failure
+ enableSuite[GlutenRemoveRedundantSortsSuite]
+ // Rewrite as it check spark SortExec.
+ .includeAllGlutenTests()
enableSuite[GlutenRowToColumnConverterSuite]
enableSuite[GlutenSQLExecutionSuite]
enableSuite[GlutenSQLFunctionSuite]
diff --git
a/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantSortsSuite.scala
b/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantSortsSuite.scala
index 02df13e30c..5c4b439d41 100644
---
a/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantSortsSuite.scala
+++
b/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/execution/GlutenRemoveRedundantSortsSuite.scala
@@ -16,6 +16,157 @@
*/
package org.apache.spark.sql.execution
-import org.apache.spark.sql.GlutenSQLTestsTrait
+import org.apache.gluten.config.GlutenConfig
+import org.apache.gluten.execution.SortExecTransformer
-class GlutenRemoveRedundantSortsSuite extends RemoveRedundantSortsSuite with
GlutenSQLTestsTrait {}
+import org.apache.spark.SparkConf
+import org.apache.spark.sql.{DataFrame, GlutenSQLTestsTrait}
+import org.apache.spark.sql.catalyst.plans.physical.{RangePartitioning,
UnknownPartitioning}
+import org.apache.spark.sql.execution.joins.ShuffledJoin
+import org.apache.spark.sql.internal.SQLConf
+
+class GlutenRemoveRedundantSortsSuite extends RemoveRedundantSortsSuite
+ with GlutenSQLTestsTrait {
+ import testImplicits._
+
+ override def sparkConf: SparkConf = {
+ super.sparkConf
+ .set(GlutenConfig.COLUMNAR_FORCE_SHUFFLED_HASH_JOIN_ENABLED.key, "false")
+ }
+
+ private def checkNumSorts(df: DataFrame, count: Int): Unit = {
+ val plan = df.queryExecution.executedPlan
+ assert(collectWithSubqueries(plan) { case s: SortExecTransformer => s
}.length == count)
+ }
+
+ private def checkSorts(query: String, enabledCount: Int, disabledCount:
Int): Unit = {
+ withSQLConf(SQLConf.REMOVE_REDUNDANT_SORTS_ENABLED.key -> "true") {
+ val df = sql(query)
+ checkNumSorts(df, enabledCount)
+ val result = df.collect()
+ withSQLConf(SQLConf.REMOVE_REDUNDANT_SORTS_ENABLED.key -> "false") {
+ val df = sql(query)
+ checkNumSorts(df, disabledCount)
+ checkAnswer(df, result)
+ }
+ }
+ }
+
+ testGluten("remove redundant sorts with limit") {
+ withTempView("t") {
+ spark.range(100).select($"id".as("key")).createOrReplaceTempView("t")
+ val query =
+ """
+ |SELECT key FROM
+ | (SELECT key FROM t WHERE key > 10 ORDER BY key DESC LIMIT 10)
+ |ORDER BY key DESC
+ |""".stripMargin
+ checkSorts(query, 0, 1)
+ }
+ }
+
+ testGluten("remove redundant sorts with broadcast hash join") {
+ withTempView("t1", "t2") {
+ spark.range(1000).select($"id".as("key")).createOrReplaceTempView("t1")
+ spark.range(1000).select($"id".as("key")).createOrReplaceTempView("t2")
+
+ val queryTemplate = """
+ |SELECT /*+ BROADCAST(%s) */ t1.key FROM
+ | (SELECT key FROM t1 WHERE key > 10 ORDER BY key
DESC LIMIT 10) t1
+ |JOIN
+ | (SELECT key FROM t2 WHERE key > 50 ORDER BY key
DESC LIMIT 100) t2
+ |ON t1.key = t2.key
+ |ORDER BY %s
+ """.stripMargin
+
+ // No sort should be removed since the stream side (t2) order DESC
+ // does not satisfy the required sort order ASC.
+ val buildLeftOrderByRightAsc = queryTemplate.format("t1", "t2.key ASC")
+ checkSorts(buildLeftOrderByRightAsc, 1, 1)
+
+ // The top sort node should be removed since the stream side (t2) order
DESC already
+ // satisfies the required sort order DESC.
+ val buildLeftOrderByRightDesc = queryTemplate.format("t1", "t2.key DESC")
+ checkSorts(buildLeftOrderByRightDesc, 0, 1)
+
+ // No sort should be removed since the sort ordering from broadcast-hash
join is based
+ // on the stream side (t2) and the required sort order is from t1.
+ val buildLeftOrderByLeftDesc = queryTemplate.format("t1", "t1.key DESC")
+ checkSorts(buildLeftOrderByLeftDesc, 1, 1)
+
+ // The top sort node should be removed since the stream side (t1) order
DESC already
+ // satisfies the required sort order DESC.
+ val buildRightOrderByLeftDesc = queryTemplate.format("t2", "t1.key DESC")
+ checkSorts(buildRightOrderByLeftDesc, 0, 1)
+ }
+ }
+
+ testGluten("remove redundant sorts with sort merge join") {
+ withTempView("t1", "t2") {
+ spark.range(1000).select($"id".as("key")).createOrReplaceTempView("t1")
+ spark.range(1000).select($"id".as("key")).createOrReplaceTempView("t2")
+ val query = """
+ |SELECT /*+ MERGE(t1) */ t1.key FROM
+ | (SELECT key FROM t1 WHERE key > 10 ORDER BY key DESC
LIMIT 10) t1
+ |JOIN
+ | (SELECT key FROM t2 WHERE key > 50 ORDER BY key DESC
LIMIT 100) t2
+ |ON t1.key = t2.key
+ |ORDER BY t1.key
+ """.stripMargin
+
+ val queryAsc = query + " ASC"
+ checkSorts(queryAsc, 2, 3)
+
+ // The top level sort should not be removed since the child output
ordering is ASC and
+ // the required ordering is DESC.
+ val queryDesc = query + " DESC"
+ checkSorts(queryDesc, 3, 3)
+ }
+ }
+
+ testGluten("cached sorted data doesn't need to be re-sorted") {
+ withSQLConf(SQLConf.REMOVE_REDUNDANT_SORTS_ENABLED.key -> "true") {
+ val df =
spark.range(1000).select($"id".as("key")).sort($"key".desc).cache()
+ df.collect()
+ val resorted = df.sort($"key".desc)
+ val sortedAsc = df.sort($"key".asc)
+ checkNumSorts(df, 0)
+ checkNumSorts(resorted, 0)
+ checkNumSorts(sortedAsc, 1)
+ val result = resorted.collect()
+ withSQLConf(SQLConf.REMOVE_REDUNDANT_SORTS_ENABLED.key -> "false") {
+ val resorted = df.sort($"key".desc)
+ resorted.collect()
+ checkNumSorts(resorted, 1)
+ checkAnswer(resorted, result)
+ }
+ }
+ }
+
+ testGluten("SPARK-33472: shuffled join with different left and right side
partition numbers") {
+ withTempView("t1", "t2") {
+ spark.range(0, 100, 1,
2).select($"id".as("key")).createOrReplaceTempView("t1")
+ (0 to 100).toDF("key").createOrReplaceTempView("t2")
+
+ val queryTemplate = """
+ |SELECT /*+ %s(t1) */ t1.key
+ |FROM t1 JOIN t2 ON t1.key = t2.key
+ |WHERE t1.key > 10 AND t2.key < 50
+ |ORDER BY t1.key ASC
+ """.stripMargin
+
+ Seq(("MERGE", 3), ("SHUFFLE_HASH", 1)).foreach {
+ case (hint, count) =>
+ val query = queryTemplate.format(hint)
+ val df = sql(query)
+ val sparkPlan = df.queryExecution.sparkPlan
+ val join = sparkPlan.collect { case j: ShuffledJoin => j }.head
+ val leftPartitioning = join.left.outputPartitioning
+ assert(leftPartitioning.isInstanceOf[RangePartitioning])
+ assert(leftPartitioning.numPartitions == 2)
+ assert(join.right.outputPartitioning == UnknownPartitioning(0))
+ checkSorts(query, count, count)
+ }
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]