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 57e7bac77d [CORE] Eliminate local sort through partial project in 
EliminateLocalSort (#13208)
57e7bac77d is described below

commit 57e7bac77d9eab5a3af0662811f2e80e260674c4
Author: Felix Loesing <[email protected]>
AuthorDate: Wed Oct 7 18:29:35 2026 -0700

    [CORE] Eliminate local sort through partial project in EliminateLocalSort 
(#13208)
---
 .../execution/ColumnarPartialProjectExec.scala     |  6 ++---
 .../execution/ColumnarPartialProjectExec.scala     |  6 ++---
 .../gluten/expression/UDFPartialProjectSuite.scala | 14 ++++++++++-
 .../BasicPhysicalOperatorTransformer.scala         |  8 +++---
 .../extension/columnar/EliminateLocalSort.scala    | 29 ++++++++++++----------
 ...PartitioningAndOrderingPreservingNodeShim.scala | 20 ---------------
 ...PartitioningAndOrderingPreservingNodeShim.scala | 20 ---------------
 ...PartitioningAndOrderingPreservingNodeShim.scala | 20 ---------------
 ...PartitioningAndOrderingPreservingNodeShim.scala | 20 ---------------
 ...PartitioningAndOrderingPreservingNodeShim.scala | 20 ---------------
 10 files changed, 39 insertions(+), 124 deletions(-)

diff --git 
a/backends-bolt/src/main/scala/org/apache/gluten/execution/ColumnarPartialProjectExec.scala
 
b/backends-bolt/src/main/scala/org/apache/gluten/execution/ColumnarPartialProjectExec.scala
index 5cbed106d8..27b26e6d5b 100644
--- 
a/backends-bolt/src/main/scala/org/apache/gluten/execution/ColumnarPartialProjectExec.scala
+++ 
b/backends-bolt/src/main/scala/org/apache/gluten/execution/ColumnarPartialProjectExec.scala
@@ -29,7 +29,7 @@ import org.apache.spark.rdd.RDD
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.catalyst.expressions._
 import org.apache.spark.sql.catalyst.plans.QueryPlan
-import org.apache.spark.sql.execution.{ExplainUtils, OrderPreservingNodeShim, 
PartitioningPreservingNodeShim, ProjectExec, SparkPlan, UnaryExecNode}
+import org.apache.spark.sql.execution.{ExplainUtils, 
OrderPreservingUnaryExecNode, PartitioningPreservingUnaryExecNode, ProjectExec, 
SparkPlan, UnaryExecNode}
 import org.apache.spark.sql.execution.metric.{SQLMetric, SQLMetrics}
 import org.apache.spark.sql.hive.{BoltHiveUDFTransformer, HiveUDFTransformer}
 import org.apache.spark.sql.vectorized.{ColumnarBatch, ColumnVector}
@@ -52,8 +52,8 @@ import scala.collection.mutable.ListBuffer
 case class ColumnarPartialProjectExec(projectList: Seq[Expression], child: 
SparkPlan)(
     replacedAlias: Seq[Alias])
   extends UnaryExecNode
-  with OrderPreservingNodeShim
-  with PartitioningPreservingNodeShim
+  with OrderPreservingUnaryExecNode
+  with PartitioningPreservingUnaryExecNode
   with ValidatablePlan {
 
   private val projectAttributes: ListBuffer[Attribute] = ListBuffer()
diff --git 
a/backends-velox/src/main/scala/org/apache/gluten/execution/ColumnarPartialProjectExec.scala
 
b/backends-velox/src/main/scala/org/apache/gluten/execution/ColumnarPartialProjectExec.scala
index b8f7d34663..96bf21f418 100644
--- 
a/backends-velox/src/main/scala/org/apache/gluten/execution/ColumnarPartialProjectExec.scala
+++ 
b/backends-velox/src/main/scala/org/apache/gluten/execution/ColumnarPartialProjectExec.scala
@@ -31,7 +31,7 @@ import org.apache.spark.rdd.RDD
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.catalyst.expressions._
 import org.apache.spark.sql.catalyst.plans.QueryPlan
-import org.apache.spark.sql.execution.{ExplainUtils, OrderPreservingNodeShim, 
PartitioningPreservingNodeShim, ProjectExec, SparkPlan, UnaryExecNode}
+import org.apache.spark.sql.execution.{ExplainUtils, 
OrderPreservingUnaryExecNode, PartitioningPreservingUnaryExecNode, ProjectExec, 
SparkPlan, UnaryExecNode}
 import org.apache.spark.sql.execution.metric.{SQLMetric, SQLMetrics}
 import org.apache.spark.sql.hive.{HiveUDFTransformer, VeloxHiveUDFTransformer}
 import org.apache.spark.sql.vectorized.{ColumnarBatch, ColumnVector}
@@ -55,8 +55,8 @@ import scala.collection.mutable.ListBuffer
 case class ColumnarPartialProjectExec(projectList: Seq[Expression], child: 
SparkPlan)(
     replacedAlias: Seq[Alias])
   extends UnaryExecNode
-  with OrderPreservingNodeShim
-  with PartitioningPreservingNodeShim
+  with OrderPreservingUnaryExecNode
+  with PartitioningPreservingUnaryExecNode
   with ValidatablePlan {
 
   private val projectAttributes: ListBuffer[Attribute] = ListBuffer()
diff --git 
a/backends-velox/src/test/scala/org/apache/gluten/expression/UDFPartialProjectSuite.scala
 
b/backends-velox/src/test/scala/org/apache/gluten/expression/UDFPartialProjectSuite.scala
index 71b60aeefa..a8287f7e0a 100644
--- 
a/backends-velox/src/test/scala/org/apache/gluten/expression/UDFPartialProjectSuite.scala
+++ 
b/backends-velox/src/test/scala/org/apache/gluten/expression/UDFPartialProjectSuite.scala
@@ -17,7 +17,7 @@
 package org.apache.gluten.expression
 
 import org.apache.gluten.config.GlutenConfig
-import org.apache.gluten.execution.{ColumnarPartialProjectExec, 
WholeStageTransformerSuite}
+import org.apache.gluten.execution.{ColumnarPartialProjectExec, 
SortExecTransformer, WholeStageTransformerSuite}
 
 import org.apache.spark.SparkConf
 import org.apache.spark.sql.catalyst.optimizer.{ConstantFolding, 
NullPropagation}
@@ -119,6 +119,18 @@ class UDFPartialProjectSuite extends 
WholeStageTransformerSuite {
     }.size == 2)
   }
 
+  test("eliminate local sort below partial project to keep collect_list 
order") {
+    // A non-stable native sort on the grouping key would reorder collected 
values.
+    runQueryAndCompare(
+      """SELECT k, collect_list(concat_concat(v)) FROM (
+        |  SELECT id % 10 AS k, cast(id AS string) AS v FROM range(0, 10000, 
1, 1)
+        |) GROUP BY k""".stripMargin) {
+      df =>
+        checkGlutenPlan[ColumnarPartialProjectExec](df)
+        assert(getExecutedPlan(df).collect { case s: SortExecTransformer => s 
}.isEmpty)
+    }
+  }
+
   test("test plus_one with many columns in project") {
     runQueryAndCompare("SELECT plus_one(cast(l_orderkey as long)), 
hash(l_partkey) from lineitem") {
       checkGlutenPlan[ColumnarPartialProjectExec]
diff --git 
a/gluten-substrait/src/main/scala/org/apache/gluten/execution/BasicPhysicalOperatorTransformer.scala
 
b/gluten-substrait/src/main/scala/org/apache/gluten/execution/BasicPhysicalOperatorTransformer.scala
index 1514aca532..19864d2960 100644
--- 
a/gluten-substrait/src/main/scala/org/apache/gluten/execution/BasicPhysicalOperatorTransformer.scala
+++ 
b/gluten-substrait/src/main/scala/org/apache/gluten/execution/BasicPhysicalOperatorTransformer.scala
@@ -39,8 +39,8 @@ import scala.collection.JavaConverters._
 
 abstract class FilterExecTransformerBase(val cond: Expression, val input: 
SparkPlan)
   extends UnaryTransformSupport
-  with OrderPreservingNodeShim
-  with PartitioningPreservingNodeShim
+  with OrderPreservingUnaryExecNode
+  with PartitioningPreservingUnaryExecNode
   with PredicateHelper
   with Logging {
 
@@ -168,8 +168,8 @@ abstract class FilterExecTransformerBase(val cond: 
Expression, val input: SparkP
 
 abstract class ProjectExecTransformerBase(val list: Seq[NamedExpression], val 
input: SparkPlan)
   extends UnaryTransformSupport
-  with OrderPreservingNodeShim
-  with PartitioningPreservingNodeShim
+  with OrderPreservingUnaryExecNode
+  with PartitioningPreservingUnaryExecNode
   with PredicateHelper
   with Logging {
 
diff --git 
a/gluten-substrait/src/main/scala/org/apache/gluten/extension/columnar/EliminateLocalSort.scala
 
b/gluten-substrait/src/main/scala/org/apache/gluten/extension/columnar/EliminateLocalSort.scala
index 17e7f29eec..fdd0cdbffe 100644
--- 
a/gluten-substrait/src/main/scala/org/apache/gluten/extension/columnar/EliminateLocalSort.scala
+++ 
b/gluten-substrait/src/main/scala/org/apache/gluten/extension/columnar/EliminateLocalSort.scala
@@ -20,7 +20,7 @@ import org.apache.gluten.execution.{ProjectExecTransformer, 
ShuffledHashJoinExec
 
 import org.apache.spark.sql.catalyst.expressions.SortOrder
 import org.apache.spark.sql.catalyst.rules.Rule
-import org.apache.spark.sql.execution.{ProjectExec, SortExec, SparkPlan, 
UnaryExecNode}
+import org.apache.spark.sql.execution.{OrderPreservingUnaryExecNode, 
ProjectExec, SortExec, SparkPlan, UnaryExecNode}
 
 /**
  * This rule is used to eliminate unnecessary local sort.
@@ -44,6 +44,8 @@ object EliminateLocalSort extends Rule[SparkPlan] {
   private def canThrough(p: SparkPlan): Boolean = p match {
     case _: ProjectExec => true
     case _: ProjectExecTransformer => true
+    // e.g. ColumnarPartialProjectExec, which sits between a pulled-out 
project and its sort.
+    case p: OrderPreservingUnaryExecNode => 
p.requiredChildOrdering.forall(_.isEmpty)
     case _ => false
   }
 
@@ -56,22 +58,23 @@ object EliminateLocalSort extends Rule[SparkPlan] {
       case p if canEliminateLocalSort(p) =>
         val requiredChildOrdering = p.requiredChildOrdering
         assert(requiredChildOrdering.size == p.children.size)
-        val newChildren = p.children.zipWithIndex.map {
-          case (SortWithChild(gChild), i) if orderingSatisfies(gChild, 
requiredChildOrdering(i)) =>
-            gChild
-          case (p: UnaryExecNode, i) if canThrough(p) =>
-            // There may be more than one project between target operator and 
sort,
-            // e.g., both hash aggregate and sort pull out project
-            p.child match {
-              case SortWithChild(gChild) if orderingSatisfies(gChild, 
requiredChildOrdering(i)) =>
-                p.withNewChildren(gChild :: Nil)
-              case _ => p
-            }
-          case p => p._1
+        val newChildren = p.children.zip(requiredChildOrdering).map {
+          case (child, ordering) => eliminateSort(child, ordering)
         }
         p.withNewChildren(newChildren)
     }
   }
+
+  // There may be more than one project between target operator and sort, 
e.g., both hash
+  // aggregate and sort pull out project, or a partial project is inserted.
+  private def eliminateSort(plan: SparkPlan, requiredOrdering: 
Seq[SortOrder]): SparkPlan =
+    plan match {
+      case SortWithChild(gChild) if orderingSatisfies(gChild, 
requiredOrdering) =>
+        gChild
+      case p: UnaryExecNode if canThrough(p) =>
+        p.withNewChildren(eliminateSort(p.child, requiredOrdering) :: Nil)
+      case p => p
+    }
 }
 
 object SortWithChild {
diff --git 
a/shims/spark34/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
 
b/shims/spark34/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
deleted file mode 100644
index f0ffcc8b69..0000000000
--- 
a/shims/spark34/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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
-
-trait OrderPreservingNodeShim extends OrderPreservingUnaryExecNode
-trait PartitioningPreservingNodeShim extends 
PartitioningPreservingUnaryExecNode
diff --git 
a/shims/spark35/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
 
b/shims/spark35/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
deleted file mode 100644
index f0ffcc8b69..0000000000
--- 
a/shims/spark35/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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
-
-trait OrderPreservingNodeShim extends OrderPreservingUnaryExecNode
-trait PartitioningPreservingNodeShim extends 
PartitioningPreservingUnaryExecNode
diff --git 
a/shims/spark40/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
 
b/shims/spark40/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
deleted file mode 100644
index f0ffcc8b69..0000000000
--- 
a/shims/spark40/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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
-
-trait OrderPreservingNodeShim extends OrderPreservingUnaryExecNode
-trait PartitioningPreservingNodeShim extends 
PartitioningPreservingUnaryExecNode
diff --git 
a/shims/spark41/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
 
b/shims/spark41/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
deleted file mode 100644
index f0ffcc8b69..0000000000
--- 
a/shims/spark41/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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
-
-trait OrderPreservingNodeShim extends OrderPreservingUnaryExecNode
-trait PartitioningPreservingNodeShim extends 
PartitioningPreservingUnaryExecNode
diff --git 
a/shims/spark42/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
 
b/shims/spark42/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
deleted file mode 100644
index f0ffcc8b69..0000000000
--- 
a/shims/spark42/src/main/scala/org/apache/spark/sql/execution/PartitioningAndOrderingPreservingNodeShim.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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
-
-trait OrderPreservingNodeShim extends OrderPreservingUnaryExecNode
-trait PartitioningPreservingNodeShim extends 
PartitioningPreservingUnaryExecNode


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

Reply via email to