Skip to content

Commit d4a00e8

Browse files
author
littlelittlewhite09
committed
[GLUTEN-12474][VL] Route WriteFiles required-ordering through the shim to fix Spark 3.3 build.
EnsureRowBasedWriteFilesOrdering imported V1WritesUtils directly, but that class only exists since Spark 3.4 (planned write, SPARK-37287). Since the rule lives in gluten-substrait, which is compiled against every supported Spark profile, building with -Pspark-3.3 failed to resolve the import. Move the version-specific `getSortOrder` behind SparkShims: - Add SparkShims.getWriteFilesRequiredOrdering, defaulting to Nil (covers Spark 3.2/3.3, where the planned-write WriteFilesExec path is not used at runtime, so the rule is a no-op there). - Override it in the Spark 3.4/3.5/4.0/4.1 shims to delegate to V1WritesUtils.getSortOrder, keeping behavior identical (including the vivo MergeSmallFilesContext and concurrent-writer cases). - Inline isOrderingMatched in the rule; it is pure catalyst and identical across versions, so it needs no shim.
1 parent f487b88 commit d4a00e8

6 files changed

Lines changed: 104 additions & 13 deletions

File tree

gluten-substrait/src/main/scala/org/apache/gluten/extension/columnar/EnsureRowBasedWriteFilesOrdering.scala

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@
1717
package org.apache.gluten.extension.columnar
1818

1919
import org.apache.gluten.execution.{SortExecTransformer, TransformSupport}
20+
import org.apache.gluten.sql.shims.SparkShimLoader
2021

21-
import org.apache.spark.sql.catalyst.expressions.SortOrder
22+
import org.apache.spark.sql.catalyst.expressions.{Expression, SortOrder}
2223
import org.apache.spark.sql.catalyst.rules.Rule
2324
import org.apache.spark.sql.execution.{ColumnarWriteFilesExec, SortExec, SparkPlan}
24-
import org.apache.spark.sql.execution.datasources.{V1WritesUtils, WriteFilesExec}
25+
import org.apache.spark.sql.execution.datasources.WriteFilesExec
2526

2627
/**
2728
* Re-adds the local sort on dynamic partition (and bucket) columns that a row-based
@@ -46,9 +47,13 @@ import org.apache.spark.sql.execution.datasources.{V1WritesUtils, WriteFilesExec
4647
* `WriteFilesExec` that [[ColumnarWriteFilesExec]] injects for the native path is skipped by
4748
* checking for its [[ColumnarWriteFilesExec.NoopLeaf]] child.
4849
*
49-
* The required ordering and the "already satisfied" check reuse [[V1WritesUtils]] so that the
50-
* behavior stays identical to vanilla `FileFormatWriter` (including the concurrent-writers and
51-
* small-file-merge cases where `getSortOrder` returns empty).
50+
* The required ordering is obtained through [[SparkShimLoader]] from `V1WritesUtils.getSortOrder`
51+
* so that the behavior stays identical to vanilla `FileFormatWriter` (including the
52+
* concurrent-writers and small-file-merge cases where `getSortOrder` returns empty). It is fetched
53+
* via the shim because `V1WritesUtils` only exists since Spark 3.4; on Spark 3.2/3.3 the shim
54+
* returns `Nil` and this rule is a no-op (the planned-write `WriteFilesExec` path is not used
55+
* there). The "already satisfied" check mirrors `V1WritesUtils.isOrderingMatched`, which is pure
56+
* catalyst and version-agnostic, so it is inlined here.
5257
*
5358
* This rule must run before [[org.apache.gluten.extension.columnar.transition.InsertTransitions]]:
5459
* when the write's child is an offloaded transformer we insert a native [[SortExecTransformer]]
@@ -58,22 +63,38 @@ import org.apache.spark.sql.execution.datasources.{V1WritesUtils, WriteFilesExec
5863
object EnsureRowBasedWriteFilesOrdering extends Rule[SparkPlan] {
5964
override def apply(plan: SparkPlan): SparkPlan = plan.transformUp {
6065
case w: WriteFilesExec if !w.child.isInstanceOf[ColumnarWriteFilesExec.NoopLeaf] =>
61-
val requiredOrdering: Seq[SortOrder] = V1WritesUtils.getSortOrder(
62-
w.child.output,
63-
w.partitionColumns,
64-
w.bucketSpec,
65-
w.options,
66-
w.staticPartitions.size)
66+
val requiredOrdering: Seq[SortOrder] =
67+
SparkShimLoader.getSparkShims.getWriteFilesRequiredOrdering(
68+
w.child.output,
69+
w.partitionColumns,
70+
w.bucketSpec,
71+
w.options,
72+
w.staticPartitions.size)
6773
if (
6874
requiredOrdering.isEmpty ||
69-
V1WritesUtils.isOrderingMatched(requiredOrdering.map(_.child), w.child.outputOrdering)
75+
isOrderingMatched(requiredOrdering.map(_.child), w.child.outputOrdering)
7076
) {
7177
w
7278
} else {
7379
w.withNewChildren(sortPlan(w.child, requiredOrdering) :: Nil)
7480
}
7581
}
7682

83+
// Mirrors `V1WritesUtils.isOrderingMatched`. Pure catalyst and identical across Spark versions,
84+
// so it is inlined to avoid depending on `V1WritesUtils`, which is absent before Spark 3.4.
85+
private def isOrderingMatched(
86+
requiredOrdering: Seq[Expression],
87+
outputOrdering: Seq[SortOrder]): Boolean = {
88+
if (requiredOrdering.length > outputOrdering.length) {
89+
false
90+
} else {
91+
requiredOrdering.zip(outputOrdering).forall {
92+
case (requiredOrder, outputOrder) =>
93+
outputOrder.satisfies(outputOrder.copy(child = requiredOrder))
94+
}
95+
}
96+
}
97+
7798
private def sortPlan(child: SparkPlan, requiredOrdering: Seq[SortOrder]): SparkPlan =
7899
child match {
79100
case c: TransformSupport =>

shims/common/src/main/scala/org/apache/gluten/sql/shims/SparkShims.scala

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import org.apache.spark.broadcast.Broadcast
2424
import org.apache.spark.internal.io.FileCommitProtocol
2525
import org.apache.spark.sql.{AnalysisException, SparkSession}
2626
import org.apache.spark.sql.catalyst.InternalRow
27-
import org.apache.spark.sql.catalyst.expressions.{Attribute, BinaryArithmetic, Expression, InputFileBlockLength, InputFileBlockStart, InputFileName, RaiseError, UnBase64}
27+
import org.apache.spark.sql.catalyst.catalog.BucketSpec
28+
import org.apache.spark.sql.catalyst.expressions.{Attribute, BinaryArithmetic, Expression, InputFileBlockLength, InputFileBlockStart, InputFileName, RaiseError, SortOrder, UnBase64}
2829
import org.apache.spark.sql.catalyst.plans.JoinType
2930
import org.apache.spark.sql.catalyst.plans.QueryPlan
3031
import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
@@ -129,6 +130,19 @@ trait SparkShims {
129130

130131
def enableNativeWriteFilesByDefault(): Boolean = false
131132

133+
/**
134+
* The local sort a row-based `WriteFilesExec` requires on its child for dynamic partition (and
135+
* bucket) writes. Delegates to `V1WritesUtils.getSortOrder`, which only exists since Spark 3.4;
136+
* on Spark 3.2/3.3 the planned-write `WriteFilesExec` path is not used at runtime, so the default
137+
* returns `Nil`.
138+
*/
139+
def getWriteFilesRequiredOrdering(
140+
outputColumns: Seq[Attribute],
141+
partitionColumns: Seq[Attribute],
142+
bucketSpec: Option[BucketSpec],
143+
options: Map[String, String],
144+
numStaticPartitionCols: Int): Seq[SortOrder] = Nil
145+
132146
def broadcastInternal[T: ClassTag](sc: SparkContext, value: T): Broadcast[T] = {
133147
// Since Spark 3.4, the `sc.broadcast` has been optimized to use `sc.broadcastInternal`.
134148
// More details see SPARK-39983.

shims/spark34/src/main/scala/org/apache/gluten/sql/shims/spark34/Spark34Shims.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import org.apache.spark.paths.SparkPath
2727
import org.apache.spark.sql.{AnalysisException, SparkSession}
2828
import org.apache.spark.sql.catalyst.InternalRow
2929
import org.apache.spark.sql.catalyst.analysis.DecimalPrecision
30+
import org.apache.spark.sql.catalyst.catalog.BucketSpec
3031
import org.apache.spark.sql.catalyst.expressions._
3132
import org.apache.spark.sql.catalyst.expressions.aggregate._
3233
import org.apache.spark.sql.catalyst.plans.QueryPlan
@@ -208,6 +209,19 @@ class Spark34Shims extends SparkShims {
208209

209210
override def enableNativeWriteFilesByDefault(): Boolean = true
210211

212+
override def getWriteFilesRequiredOrdering(
213+
outputColumns: Seq[Attribute],
214+
partitionColumns: Seq[Attribute],
215+
bucketSpec: Option[BucketSpec],
216+
options: Map[String, String],
217+
numStaticPartitionCols: Int): Seq[SortOrder] =
218+
V1WritesUtils.getSortOrder(
219+
outputColumns,
220+
partitionColumns,
221+
bucketSpec,
222+
options,
223+
numStaticPartitionCols)
224+
211225
override def broadcastInternal[T: ClassTag](sc: SparkContext, value: T): Broadcast[T] = {
212226
SparkContextUtils.broadcastInternal(sc, value)
213227
}

shims/spark35/src/main/scala/org/apache/gluten/sql/shims/spark35/Spark35Shims.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import org.apache.spark.paths.SparkPath
2727
import org.apache.spark.sql.{AnalysisException, SparkSession}
2828
import org.apache.spark.sql.catalyst.{ExtendedAnalysisException, InternalRow}
2929
import org.apache.spark.sql.catalyst.analysis.DecimalPrecision
30+
import org.apache.spark.sql.catalyst.catalog.BucketSpec
3031
import org.apache.spark.sql.catalyst.expressions._
3132
import org.apache.spark.sql.catalyst.expressions.aggregate._
3233
import org.apache.spark.sql.catalyst.plans.QueryPlan
@@ -249,6 +250,19 @@ class Spark35Shims extends SparkShims {
249250

250251
override def enableNativeWriteFilesByDefault(): Boolean = true
251252

253+
override def getWriteFilesRequiredOrdering(
254+
outputColumns: Seq[Attribute],
255+
partitionColumns: Seq[Attribute],
256+
bucketSpec: Option[BucketSpec],
257+
options: Map[String, String],
258+
numStaticPartitionCols: Int): Seq[SortOrder] =
259+
V1WritesUtils.getSortOrder(
260+
outputColumns,
261+
partitionColumns,
262+
bucketSpec,
263+
options,
264+
numStaticPartitionCols)
265+
252266
override def broadcastInternal[T: ClassTag](sc: SparkContext, value: T): Broadcast[T] = {
253267
SparkContextUtils.broadcastInternal(sc, value)
254268
}

shims/spark40/src/main/scala/org/apache/gluten/sql/shims/spark40/Spark40Shims.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import org.apache.spark.paths.SparkPath
2727
import org.apache.spark.sql.{AnalysisException, SparkSession}
2828
import org.apache.spark.sql.catalyst.{ExtendedAnalysisException, InternalRow}
2929
import org.apache.spark.sql.catalyst.analysis.DecimalPrecisionTypeCoercion
30+
import org.apache.spark.sql.catalyst.catalog.BucketSpec
3031
import org.apache.spark.sql.catalyst.expressions._
3132
import org.apache.spark.sql.catalyst.expressions.aggregate._
3233
import org.apache.spark.sql.catalyst.plans.{JoinType, LeftSingle}
@@ -254,6 +255,19 @@ class Spark40Shims extends SparkShims {
254255

255256
override def enableNativeWriteFilesByDefault(): Boolean = true
256257

258+
override def getWriteFilesRequiredOrdering(
259+
outputColumns: Seq[Attribute],
260+
partitionColumns: Seq[Attribute],
261+
bucketSpec: Option[BucketSpec],
262+
options: Map[String, String],
263+
numStaticPartitionCols: Int): Seq[SortOrder] =
264+
V1WritesUtils.getSortOrder(
265+
outputColumns,
266+
partitionColumns,
267+
bucketSpec,
268+
options,
269+
numStaticPartitionCols)
270+
257271
override def broadcastInternal[T: ClassTag](sc: SparkContext, value: T): Broadcast[T] = {
258272
SparkContextUtils.broadcastInternal(sc, value)
259273
}

shims/spark41/src/main/scala/org/apache/gluten/sql/shims/spark41/Spark41Shims.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import org.apache.spark.paths.SparkPath
2727
import org.apache.spark.sql.{AnalysisException, SparkSession}
2828
import org.apache.spark.sql.catalyst.{ExtendedAnalysisException, InternalRow}
2929
import org.apache.spark.sql.catalyst.analysis.DecimalPrecisionTypeCoercion
30+
import org.apache.spark.sql.catalyst.catalog.BucketSpec
3031
import org.apache.spark.sql.catalyst.expressions._
3132
import org.apache.spark.sql.catalyst.expressions.aggregate._
3233
import org.apache.spark.sql.catalyst.plans.{JoinType, LeftSingle}
@@ -253,6 +254,19 @@ class Spark41Shims extends SparkShims {
253254

254255
override def enableNativeWriteFilesByDefault(): Boolean = true
255256

257+
override def getWriteFilesRequiredOrdering(
258+
outputColumns: Seq[Attribute],
259+
partitionColumns: Seq[Attribute],
260+
bucketSpec: Option[BucketSpec],
261+
options: Map[String, String],
262+
numStaticPartitionCols: Int): Seq[SortOrder] =
263+
V1WritesUtils.getSortOrder(
264+
outputColumns,
265+
partitionColumns,
266+
bucketSpec,
267+
options,
268+
numStaticPartitionCols)
269+
256270
override def broadcastInternal[T: ClassTag](sc: SparkContext, value: T): Broadcast[T] = {
257271
SparkContextUtils.broadcastInternal(sc, value)
258272
}

0 commit comments

Comments
 (0)