ulysses-you commented on code in PR #57396:
URL: https://github.com/apache/spark/pull/57396#discussion_r3626874013


##########
sql/core/src/test/scala/org/apache/spark/sql/execution/PushDownLocalSortSuite.scala:
##########
@@ -0,0 +1,320 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.sql.execution
+
+import org.apache.spark.sql.DataFrame
+import org.apache.spark.sql.catalyst.expressions.{Alias, Ascending, 
AttributeReference, IsNotNull, SortOrder}
+import org.apache.spark.sql.catalyst.plans.physical.UnspecifiedDistribution
+import org.apache.spark.sql.execution.adaptive.{AdaptiveSparkPlanHelper, 
DisableAdaptiveExecutionSuite, EnableAdaptiveExecutionSuite}
+import org.apache.spark.sql.execution.exchange.ValidateRequirements
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.test.SharedSparkSession
+import org.apache.spark.sql.types.IntegerType
+
+
+abstract class PushDownLocalSortSuiteBase
+    extends SharedSparkSession
+    with AdaptiveSparkPlanHelper {
+
+  private def checkNumSorts(df: DataFrame, count: Int): Unit = {
+    val plan = df.queryExecution.executedPlan
+    assert(collectWithSubqueries(plan) { case s: SortExec => s }.length == 
count)
+  }
+
+  private def checkSorts(query: String, enabledCount: Int, disabledCount: 
Int): Unit = {
+    withSQLConf(SQLConf.PUSH_DOWN_LOCAL_SORT_ENABLED.key -> "true") {
+      val df = sql(query)
+      checkNumSorts(df, enabledCount)
+      val result = df.collect()
+      withSQLConf(SQLConf.PUSH_DOWN_LOCAL_SORT_ENABLED.key -> "false") {
+        val df = sql(query)
+        checkNumSorts(df, disabledCount)
+        checkAnswer(df, result)
+      }
+    }
+  }
+
+  test("Push a wider local sort down across stacked windows with 
prefix-compatible order specs") {
+    withTempView("t") {
+      spark.range(100).selectExpr("id % 10 as a", "id % 7 as b", "id as c")
+        .createOrReplaceTempView("t")
+      // The narrower window (order by b) is listed first, so it is planned 
closest to the leaf and
+      // the wider window (order by b, c) ends up above it. Without the rule 
two local sorts
+      // ([a, b] below the inner window, [a, b, c] above it) are computed. The 
rule widens the
+      // lower sort to [a, b, c] so it serves both windows and drops the upper 
sort, leaving a
+      // single [a, b, c] sort.
+      val query =
+        """
+          |SELECT a, b, c,
+          |  RANK() OVER (PARTITION BY a ORDER BY b) AS rk,
+          |  ROW_NUMBER() OVER (PARTITION BY a ORDER BY b, c) AS rn
+          |FROM t
+          |""".stripMargin
+      checkSorts(query, 1, 2)
+    }
+  }
+
+  test("No-op when the wider sort is already below the narrower one") {
+    withTempView("t") {
+      spark.range(100).selectExpr("id % 10 as a", "id % 7 as b", "id as c")
+        .createOrReplaceTempView("t")
+      // The wider window (order by b, c) is listed first, so it is planned 
closest to the leaf and
+      // the narrower window (order by b) ends up above it. 
`EnsureRequirements` inserts only one
+      // sort here: the wider window's [a, b, c] sort already satisfies the 
narrower window's
+      // [a, b] requirement, so no second sort is added. This rule only pushes 
a wider sort down,
+      // so it does not fire; the single sort is unchanged whether it is on or 
off.
+      val query =
+        """
+          |SELECT a, b, c,
+          |  ROW_NUMBER() OVER (PARTITION BY a ORDER BY b, c) AS rn,
+          |  RANK() OVER (PARTITION BY a ORDER BY b) AS rk
+          |FROM t
+          |""".stripMargin
+      checkSorts(query, 1, 1)
+    }
+  }
+
+  test("Push-down still applies and stays correct with a filter on a window 
output") {
+    withTempView("t") {
+      spark.range(200).selectExpr("id % 10 as a", "id % 7 as b", "id as c")
+        .createOrReplaceTempView("t")
+      val query =
+        """
+          |SELECT * FROM (
+          |  SELECT a, b, c,
+          |    RANK() OVER (PARTITION BY a ORDER BY b) AS rk,
+          |    ROW_NUMBER() OVER (PARTITION BY a ORDER BY b, c) AS rn
+          |  FROM t
+          |) WHERE rn > 1
+          |""".stripMargin
+      // The filter on rn sits above both windows and does not affect the two 
sorts that feed them,
+      // so the push-down still reduces 2 sorts to 1 and the results are 
unchanged.
+      checkSorts(query, 1, 2)
+    }
+  }
+
+  test("Push a wider sort down through a window to feed a sort aggregate above 
it") {
+    withTempView("t") {
+      spark.range(200).selectExpr("id % 10 as a", "id % 7 as b", "id % 5 as c")

Review Comment:
   You are right — I confirmed `count(distinct c)` per `(a, b)` is `1` for that 
data (`lcm(10,7)=70`, `70 % 5 == 0`), so the widening could not reorder any tie 
and the check passed trivially. Fixed: switched to `id % 13` for `c`, which 
varies within `(a, b)` (`70 % 13 != 0`), and changed the window function to the 
tie-safe `RANK()`. Now the `[a,b]`->`[a,b,c]` widening genuinely reorders rows 
within the window's `ORDER BY b` ties, while `RANK()` keeps the result stable — 
so the test exercises the reorder and still verifies correctness plus the 
sort-count drop. Added a comment explaining the data choice.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to