|
16 | 16 | */ |
17 | 17 | package org.apache.spark.sql.execution |
18 | 18 |
|
19 | | -import org.apache.spark.sql.GlutenSQLTestsTrait |
| 19 | +import org.apache.gluten.config.GlutenConfig |
| 20 | +import org.apache.gluten.execution.SortExecTransformer |
20 | 21 |
|
21 | | -class GlutenRemoveRedundantSortsSuite extends RemoveRedundantSortsSuite with GlutenSQLTestsTrait {} |
| 22 | +import org.apache.spark.SparkConf |
| 23 | +import org.apache.spark.sql.{DataFrame, GlutenSQLTestsTrait} |
| 24 | +import org.apache.spark.sql.catalyst.plans.physical.{RangePartitioning, UnknownPartitioning} |
| 25 | +import org.apache.spark.sql.execution.joins.ShuffledJoin |
| 26 | +import org.apache.spark.sql.internal.SQLConf |
| 27 | + |
| 28 | +class GlutenRemoveRedundantSortsSuite extends RemoveRedundantSortsSuite |
| 29 | + with GlutenSQLTestsTrait { |
| 30 | + import testImplicits._ |
| 31 | + |
| 32 | + override def sparkConf: SparkConf = { |
| 33 | + super.sparkConf |
| 34 | + .set(GlutenConfig.COLUMNAR_FORCE_SHUFFLED_HASH_JOIN_ENABLED.key, "false") |
| 35 | + } |
| 36 | + |
| 37 | + private def checkNumSorts(df: DataFrame, count: Int): Unit = { |
| 38 | + val plan = df.queryExecution.executedPlan |
| 39 | + assert(collectWithSubqueries(plan) { case s: SortExecTransformer => s }.length == count) |
| 40 | + } |
| 41 | + |
| 42 | + private def checkSorts(query: String, enabledCount: Int, disabledCount: Int): Unit = { |
| 43 | + withSQLConf(SQLConf.REMOVE_REDUNDANT_SORTS_ENABLED.key -> "true") { |
| 44 | + val df = sql(query) |
| 45 | + checkNumSorts(df, enabledCount) |
| 46 | + val result = df.collect() |
| 47 | + withSQLConf(SQLConf.REMOVE_REDUNDANT_SORTS_ENABLED.key -> "false") { |
| 48 | + val df = sql(query) |
| 49 | + checkNumSorts(df, disabledCount) |
| 50 | + checkAnswer(df, result) |
| 51 | + } |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + testGluten("remove redundant sorts with limit") { |
| 56 | + withTempView("t") { |
| 57 | + spark.range(100).select($"id".as("key")).createOrReplaceTempView("t") |
| 58 | + val query = |
| 59 | + """ |
| 60 | + |SELECT key FROM |
| 61 | + | (SELECT key FROM t WHERE key > 10 ORDER BY key DESC LIMIT 10) |
| 62 | + |ORDER BY key DESC |
| 63 | + |""".stripMargin |
| 64 | + checkSorts(query, 0, 1) |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + testGluten("remove redundant sorts with broadcast hash join") { |
| 69 | + withTempView("t1", "t2") { |
| 70 | + spark.range(1000).select($"id".as("key")).createOrReplaceTempView("t1") |
| 71 | + spark.range(1000).select($"id".as("key")).createOrReplaceTempView("t2") |
| 72 | + |
| 73 | + val queryTemplate = """ |
| 74 | + |SELECT /*+ BROADCAST(%s) */ t1.key FROM |
| 75 | + | (SELECT key FROM t1 WHERE key > 10 ORDER BY key DESC LIMIT 10) t1 |
| 76 | + |JOIN |
| 77 | + | (SELECT key FROM t2 WHERE key > 50 ORDER BY key DESC LIMIT 100) t2 |
| 78 | + |ON t1.key = t2.key |
| 79 | + |ORDER BY %s |
| 80 | + """.stripMargin |
| 81 | + |
| 82 | + // No sort should be removed since the stream side (t2) order DESC |
| 83 | + // does not satisfy the required sort order ASC. |
| 84 | + val buildLeftOrderByRightAsc = queryTemplate.format("t1", "t2.key ASC") |
| 85 | + checkSorts(buildLeftOrderByRightAsc, 1, 1) |
| 86 | + |
| 87 | + // The top sort node should be removed since the stream side (t2) order DESC already |
| 88 | + // satisfies the required sort order DESC. |
| 89 | + val buildLeftOrderByRightDesc = queryTemplate.format("t1", "t2.key DESC") |
| 90 | + checkSorts(buildLeftOrderByRightDesc, 0, 1) |
| 91 | + |
| 92 | + // No sort should be removed since the sort ordering from broadcast-hash join is based |
| 93 | + // on the stream side (t2) and the required sort order is from t1. |
| 94 | + val buildLeftOrderByLeftDesc = queryTemplate.format("t1", "t1.key DESC") |
| 95 | + checkSorts(buildLeftOrderByLeftDesc, 1, 1) |
| 96 | + |
| 97 | + // The top sort node should be removed since the stream side (t1) order DESC already |
| 98 | + // satisfies the required sort order DESC. |
| 99 | + val buildRightOrderByLeftDesc = queryTemplate.format("t2", "t1.key DESC") |
| 100 | + checkSorts(buildRightOrderByLeftDesc, 0, 1) |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + testGluten("remove redundant sorts with sort merge join") { |
| 105 | + withTempView("t1", "t2") { |
| 106 | + spark.range(1000).select($"id".as("key")).createOrReplaceTempView("t1") |
| 107 | + spark.range(1000).select($"id".as("key")).createOrReplaceTempView("t2") |
| 108 | + val query = """ |
| 109 | + |SELECT /*+ MERGE(t1) */ t1.key FROM |
| 110 | + | (SELECT key FROM t1 WHERE key > 10 ORDER BY key DESC LIMIT 10) t1 |
| 111 | + |JOIN |
| 112 | + | (SELECT key FROM t2 WHERE key > 50 ORDER BY key DESC LIMIT 100) t2 |
| 113 | + |ON t1.key = t2.key |
| 114 | + |ORDER BY t1.key |
| 115 | + """.stripMargin |
| 116 | + |
| 117 | + val queryAsc = query + " ASC" |
| 118 | + checkSorts(queryAsc, 2, 3) |
| 119 | + |
| 120 | + // The top level sort should not be removed since the child output ordering is ASC and |
| 121 | + // the required ordering is DESC. |
| 122 | + val queryDesc = query + " DESC" |
| 123 | + checkSorts(queryDesc, 3, 3) |
| 124 | + } |
| 125 | + } |
| 126 | + |
| 127 | + testGluten("cached sorted data doesn't need to be re-sorted") { |
| 128 | + withSQLConf(SQLConf.REMOVE_REDUNDANT_SORTS_ENABLED.key -> "true") { |
| 129 | + val df = spark.range(1000).select($"id".as("key")).sort($"key".desc).cache() |
| 130 | + df.collect() |
| 131 | + val resorted = df.sort($"key".desc) |
| 132 | + val sortedAsc = df.sort($"key".asc) |
| 133 | + checkNumSorts(df, 0) |
| 134 | + checkNumSorts(resorted, 0) |
| 135 | + checkNumSorts(sortedAsc, 1) |
| 136 | + val result = resorted.collect() |
| 137 | + withSQLConf(SQLConf.REMOVE_REDUNDANT_SORTS_ENABLED.key -> "false") { |
| 138 | + val resorted = df.sort($"key".desc) |
| 139 | + resorted.collect() |
| 140 | + checkNumSorts(resorted, 1) |
| 141 | + checkAnswer(resorted, result) |
| 142 | + } |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + testGluten("SPARK-33472: shuffled join with different left and right side partition numbers") { |
| 147 | + withTempView("t1", "t2") { |
| 148 | + spark.range(0, 100, 1, 2).select($"id".as("key")).createOrReplaceTempView("t1") |
| 149 | + (0 to 100).toDF("key").createOrReplaceTempView("t2") |
| 150 | + |
| 151 | + val queryTemplate = """ |
| 152 | + |SELECT /*+ %s(t1) */ t1.key |
| 153 | + |FROM t1 JOIN t2 ON t1.key = t2.key |
| 154 | + |WHERE t1.key > 10 AND t2.key < 50 |
| 155 | + |ORDER BY t1.key ASC |
| 156 | + """.stripMargin |
| 157 | + |
| 158 | + Seq(("MERGE", 3), ("SHUFFLE_HASH", 1)).foreach { |
| 159 | + case (hint, count) => |
| 160 | + val query = queryTemplate.format(hint) |
| 161 | + val df = sql(query) |
| 162 | + val sparkPlan = df.queryExecution.sparkPlan |
| 163 | + val join = sparkPlan.collect { case j: ShuffledJoin => j }.head |
| 164 | + val leftPartitioning = join.left.outputPartitioning |
| 165 | + assert(leftPartitioning.isInstanceOf[RangePartitioning]) |
| 166 | + assert(leftPartitioning.numPartitions == 2) |
| 167 | + assert(join.right.outputPartitioning == UnknownPartitioning(0)) |
| 168 | + checkSorts(query, count, count) |
| 169 | + } |
| 170 | + } |
| 171 | + } |
| 172 | +} |
0 commit comments