This is an automated email from the ASF dual-hosted git repository.

hongze pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-gluten.git


The following commit(s) were added to refs/heads/main by this push:
     new acbdce981 [VL] Fix case-class inheritance for 
VeloxColumnarWriteFilesExec  (#5480)
acbdce981 is described below

commit acbdce9810186d0aa26ac72f3974a1310e67e6cd
Author: Hongze Zhang <[email protected]>
AuthorDate: Tue Apr 23 18:44:36 2024 +0800

    [VL] Fix case-class inheritance for VeloxColumnarWriteFilesExec  (#5480)
---
 .../clickhouse/CHSparkPlanExecApi.scala            |  4 +-
 .../backendsapi/velox/SparkPlanExecApiImpl.scala   |  6 +-
 .../execution/VeloxColumnarWriteFilesExec.scala    | 64 ++++++++++++++++++----
 .../gluten/backendsapi/SparkPlanExecApi.scala      |  4 +-
 .../org/apache/spark/util/SparkDirectoryUtil.scala |  6 +-
 .../datasources/GlutenV1WriteCommandSuite.scala    | 10 ++--
 .../spark/sql/sources/GlutenInsertSuite.scala      |  2 +-
 .../datasources/GlutenV1WriteCommandSuite.scala    | 10 ++--
 .../spark/sql/sources/GlutenInsertSuite.scala      |  2 +-
 9 files changed, 74 insertions(+), 34 deletions(-)

diff --git 
a/backends-clickhouse/src/main/scala/org/apache/gluten/backendsapi/clickhouse/CHSparkPlanExecApi.scala
 
b/backends-clickhouse/src/main/scala/org/apache/gluten/backendsapi/clickhouse/CHSparkPlanExecApi.scala
index 55983171b..7ea12ffe7 100644
--- 
a/backends-clickhouse/src/main/scala/org/apache/gluten/backendsapi/clickhouse/CHSparkPlanExecApi.scala
+++ 
b/backends-clickhouse/src/main/scala/org/apache/gluten/backendsapi/clickhouse/CHSparkPlanExecApi.scala
@@ -49,7 +49,7 @@ import org.apache.spark.sql.catalyst.rules.Rule
 import org.apache.spark.sql.delta.files.TahoeFileIndex
 import org.apache.spark.sql.execution._
 import org.apache.spark.sql.execution.adaptive.AQEShuffleReadExec
-import org.apache.spark.sql.execution.datasources.{FileFormat, 
HadoopFsRelation, WriteFilesExec}
+import org.apache.spark.sql.execution.datasources.{FileFormat, 
HadoopFsRelation}
 import 
org.apache.spark.sql.execution.datasources.GlutenWriterColumnarRules.NativeWritePostRule
 import org.apache.spark.sql.execution.datasources.parquet.ParquetFileFormat
 import 
org.apache.spark.sql.execution.datasources.v2.clickhouse.source.DeltaMergeTreeFileFormat
@@ -677,7 +677,7 @@ class CHSparkPlanExecApi extends SparkPlanExecApi {
       partitionColumns: Seq[Attribute],
       bucketSpec: Option[BucketSpec],
       options: Map[String, String],
-      staticPartitions: TablePartitionSpec): WriteFilesExec = {
+      staticPartitions: TablePartitionSpec): SparkPlan = {
     throw new GlutenNotSupportException("ColumnarWriteFilesExec is not support 
in ch backend.")
   }
 
diff --git 
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/SparkPlanExecApiImpl.scala
 
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/SparkPlanExecApiImpl.scala
index 9952147b9..64a2dc2ce 100644
--- 
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/SparkPlanExecApiImpl.scala
+++ 
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/SparkPlanExecApiImpl.scala
@@ -45,7 +45,7 @@ import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
 import org.apache.spark.sql.catalyst.plans.physical._
 import org.apache.spark.sql.catalyst.rules.Rule
 import org.apache.spark.sql.execution._
-import org.apache.spark.sql.execution.datasources.{FileFormat, WriteFilesExec}
+import org.apache.spark.sql.execution.datasources.FileFormat
 import org.apache.spark.sql.execution.exchange.{BroadcastExchangeExec, 
ShuffleExchangeExec}
 import org.apache.spark.sql.execution.joins.BuildSideRelation
 import org.apache.spark.sql.execution.metric.SQLMetric
@@ -506,8 +506,8 @@ class SparkPlanExecApiImpl extends SparkPlanExecApi {
       partitionColumns: Seq[Attribute],
       bucketSpec: Option[BucketSpec],
       options: Map[String, String],
-      staticPartitions: TablePartitionSpec): WriteFilesExec = {
-    new VeloxColumnarWriteFilesExec(
+      staticPartitions: TablePartitionSpec): SparkPlan = {
+    VeloxColumnarWriteFilesExec(
       child,
       fileFormat,
       partitionColumns,
diff --git 
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/VeloxColumnarWriteFilesExec.scala
 
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/VeloxColumnarWriteFilesExec.scala
index 57fbc8104..26d249f90 100644
--- 
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/VeloxColumnarWriteFilesExec.scala
+++ 
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/VeloxColumnarWriteFilesExec.scala
@@ -18,6 +18,7 @@ package org.apache.spark.sql.execution
 
 import org.apache.gluten.backendsapi.BackendsApiManager
 import org.apache.gluten.columnarbatch.ColumnarBatches
+import org.apache.gluten.exception.GlutenException
 import org.apache.gluten.extension.GlutenPlan
 import org.apache.gluten.memory.arrowalloc.ArrowBufferAllocators
 import org.apache.gluten.sql.shims.SparkShimLoader
@@ -32,7 +33,7 @@ import org.apache.spark.sql.catalyst.catalog.BucketSpec
 import org.apache.spark.sql.catalyst.catalog.CatalogTypes.TablePartitionSpec
 import org.apache.spark.sql.catalyst.expressions.{Attribute, AttributeSet, 
GenericInternalRow}
 import org.apache.spark.sql.connector.write.WriterCommitMessage
-import org.apache.spark.sql.execution.datasources.{BasicWriteTaskStats, 
DynamicPartitionDataSingleWriter, EmptyDirectoryDataWriter, 
ExecutedWriteSummary, FileFormat, PartitioningUtils, SingleDirectoryDataWriter, 
WriteFilesExec, WriteFilesSpec, WriteTaskResult}
+import org.apache.spark.sql.execution.datasources._
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.vectorized.ColumnarBatch
 import org.apache.spark.util.Utils
@@ -254,20 +255,32 @@ class VeloxColumnarWriteFilesRDD(
   }
 }
 
-class VeloxColumnarWriteFilesExec(
+// The class inherits from "BinaryExecNode" instead of "UnaryExecNode" because
+// we need to expose a dummy child (as right child) with type "WriteFilesExec" 
to let Spark
+// choose the new write code path (version >= 3.4). The actual plan to write 
is the left child
+// of this operator.
+case class VeloxColumnarWriteFilesExec(
     child: SparkPlan,
     fileFormat: FileFormat,
     partitionColumns: Seq[Attribute],
     bucketSpec: Option[BucketSpec],
     options: Map[String, String],
     staticPartitions: TablePartitionSpec)
-  extends WriteFilesExec(child, fileFormat, partitionColumns, bucketSpec, 
options, staticPartitions)
-  with GlutenPlan {
+  extends BinaryExecNode
+  with GlutenPlan
+  with VeloxColumnarWriteFilesExec.ExecuteWriteCompatible {
+  import VeloxColumnarWriteFilesExec._
 
   override lazy val references: AttributeSet = AttributeSet.empty
 
   override def supportsColumnar(): Boolean = true
 
+  override def output: Seq[Attribute] = Seq.empty
+
+  override protected def doExecute(): RDD[InternalRow] = {
+    throw new GlutenException(s"$nodeName does not support doExecute")
+  }
+
   /** Fallback to use vanilla Spark write files to generate an empty file for 
metadata only. */
   private def writeFilesForEmptyRDD(
       writeFilesSpec: WriteFilesSpec,
@@ -308,12 +321,39 @@ class VeloxColumnarWriteFilesExec(
     }
   }
 
-  override protected def withNewChildInternal(newChild: SparkPlan): 
WriteFilesExec =
-    new VeloxColumnarWriteFilesExec(
-      newChild,
-      fileFormat,
-      partitionColumns,
-      bucketSpec,
-      options,
-      staticPartitions)
+  override def left: SparkPlan = child
+
+  // This is a workaround for FileFormatWriter#write. Vanilla Spark (version 
>= 3.4) requires for
+  // a plan that has at least one node exactly of type `WriteFilesExec` that 
is a Scala case-class,
+  // to decide to choose new `#executeWrite` code path over the legacy 
`#execute` for write
+  // operation.
+  //
+  // So we add a no-op `WriteFilesExec` child to let Spark pick the new code 
path.
+  //
+  // See: FileFormatWriter#write
+  // See: V1Writes#getWriteFilesOpt
+  override val right: SparkPlan =
+    WriteFilesExec(NoopLeaf(), fileFormat, partitionColumns, bucketSpec, 
options, staticPartitions)
+
+  override protected def withNewChildrenInternal(
+      newLeft: SparkPlan,
+      newRight: SparkPlan): SparkPlan =
+    copy(newLeft, fileFormat, partitionColumns, bucketSpec, options, 
staticPartitions)
+}
+
+object VeloxColumnarWriteFilesExec {
+  private case class NoopLeaf() extends LeafExecNode {
+    override protected def doExecute(): RDD[InternalRow] =
+      throw new GlutenException(s"$nodeName does not support doExecute")
+    override def output: Seq[Attribute] = Seq.empty
+  }
+
+  sealed trait ExecuteWriteCompatible {
+    // To be compatible with Spark (version < 3.4)
+    protected def doExecuteWrite(writeFilesSpec: WriteFilesSpec): 
RDD[WriterCommitMessage] = {
+      throw new GlutenException(
+        s"Internal Error ${this.getClass} has write support" +
+          s" mismatch:\n${this}")
+    }
+  }
 }
diff --git 
a/gluten-core/src/main/scala/org/apache/gluten/backendsapi/SparkPlanExecApi.scala
 
b/gluten-core/src/main/scala/org/apache/gluten/backendsapi/SparkPlanExecApi.scala
index 74e03e329..8dfb1e641 100644
--- 
a/gluten-core/src/main/scala/org/apache/gluten/backendsapi/SparkPlanExecApi.scala
+++ 
b/gluten-core/src/main/scala/org/apache/gluten/backendsapi/SparkPlanExecApi.scala
@@ -38,7 +38,7 @@ import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
 import org.apache.spark.sql.catalyst.plans.physical.{BroadcastMode, 
Partitioning}
 import org.apache.spark.sql.catalyst.rules.Rule
 import org.apache.spark.sql.execution.{FileSourceScanExec, GenerateExec, 
LeafExecNode, SparkPlan}
-import org.apache.spark.sql.execution.datasources.{FileFormat, WriteFilesExec}
+import org.apache.spark.sql.execution.datasources.FileFormat
 import org.apache.spark.sql.execution.datasources.v2.{BatchScanExec, FileScan}
 import org.apache.spark.sql.execution.exchange.ShuffleExchangeExec
 import org.apache.spark.sql.execution.joins.BuildSideRelation
@@ -357,7 +357,7 @@ trait SparkPlanExecApi {
       partitionColumns: Seq[Attribute],
       bucketSpec: Option[BucketSpec],
       options: Map[String, String],
-      staticPartitions: TablePartitionSpec): WriteFilesExec
+      staticPartitions: TablePartitionSpec): SparkPlan
 
   /**
    * Generate extended DataSourceV2 Strategies. Currently only for ClickHouse 
backend.
diff --git 
a/gluten-core/src/main/scala/org/apache/spark/util/SparkDirectoryUtil.scala 
b/gluten-core/src/main/scala/org/apache/spark/util/SparkDirectoryUtil.scala
index 435ee8df7..fbc59edfd 100644
--- a/gluten-core/src/main/scala/org/apache/spark/util/SparkDirectoryUtil.scala
+++ b/gluten-core/src/main/scala/org/apache/spark/util/SparkDirectoryUtil.scala
@@ -30,8 +30,8 @@ import java.nio.file.Paths
  * Manages Gluten's local directories, for storing jars, libs, spill files, or 
other temporary
  * stuffs.
  */
-class SparkDirectoryUtil private (roots: Array[String]) extends Logging {
-  private val ROOTS = roots.flatMap {
+class SparkDirectoryUtil private (val roots: Array[String]) extends Logging {
+  private val ROOTS: Array[File] = roots.flatMap {
     rootDir =>
       try {
         val localDir = Utils.createDirectory(rootDir, "gluten")
@@ -78,7 +78,7 @@ object SparkDirectoryUtil extends Logging {
       INSTANCE = new SparkDirectoryUtil(roots)
       return
     }
-    if (INSTANCE.ROOTS.toSet != roots.toSet) {
+    if (INSTANCE.roots.toSet != roots.toSet) {
       logWarning(
         s"Reinitialize SparkDirectoryUtil with different root dirs: old: 
${INSTANCE.ROOTS
             .mkString("Array(", ", ", ")")}, new: ${roots.mkString("Array(", 
", ", ")")}"
diff --git 
a/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/execution/datasources/GlutenV1WriteCommandSuite.scala
 
b/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/execution/datasources/GlutenV1WriteCommandSuite.scala
index 7799252ba..3d277b94c 100644
--- 
a/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/execution/datasources/GlutenV1WriteCommandSuite.scala
+++ 
b/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/execution/datasources/GlutenV1WriteCommandSuite.scala
@@ -21,7 +21,7 @@ import org.apache.gluten.execution.SortExecTransformer
 import org.apache.spark.sql.GlutenSQLTestsBaseTrait
 import org.apache.spark.sql.catalyst.expressions.{Ascending, 
AttributeReference, NullsFirst, SortOrder}
 import org.apache.spark.sql.catalyst.plans.logical.{LogicalPlan, Sort}
-import org.apache.spark.sql.execution.{QueryExecution, SortExec}
+import org.apache.spark.sql.execution.{QueryExecution, SortExec, 
VeloxColumnarWriteFilesExec}
 import org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanExec
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.types.{IntegerType, StringType}
@@ -122,8 +122,8 @@ class GlutenV1WriteCommandSuite
             val executedPlan = FileFormatWriter.executedPlan.get
 
             val plan = if (enabled) {
-              assert(executedPlan.isInstanceOf[WriteFilesExec])
-              executedPlan.asInstanceOf[WriteFilesExec].child
+              assert(executedPlan.isInstanceOf[VeloxColumnarWriteFilesExec])
+              executedPlan.asInstanceOf[VeloxColumnarWriteFilesExec].child
             } else {
               executedPlan.transformDown { case a: AdaptiveSparkPlanExec => 
a.executedPlan }
             }
@@ -204,8 +204,8 @@ class GlutenV1WriteCommandSuite
           val executedPlan = FileFormatWriter.executedPlan.get
 
           val plan = if (enabled) {
-            assert(executedPlan.isInstanceOf[WriteFilesExec])
-            executedPlan.asInstanceOf[WriteFilesExec].child
+            assert(executedPlan.isInstanceOf[VeloxColumnarWriteFilesExec])
+            executedPlan.asInstanceOf[VeloxColumnarWriteFilesExec].child
           } else {
             executedPlan.transformDown { case a: AdaptiveSparkPlanExec => 
a.executedPlan }
           }
diff --git 
a/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/sources/GlutenInsertSuite.scala
 
b/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/sources/GlutenInsertSuite.scala
index cf6f35288..b951da84a 100644
--- 
a/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/sources/GlutenInsertSuite.scala
+++ 
b/gluten-ut/spark34/src/test/scala/org/apache/spark/sql/sources/GlutenInsertSuite.scala
@@ -141,7 +141,7 @@ class GlutenInsertSuite
         .find(_.isInstanceOf[SortExecTransformer])
         .isEmpty)
     // all operators should be transformed
-    assert(writeFiles.find(!_.isInstanceOf[GlutenPlan]).isEmpty)
+    assert(writeFiles.child.find(!_.isInstanceOf[GlutenPlan]).isEmpty)
 
     val parts = 
spark.sessionState.catalog.listPartitionNames(TableIdentifier("pt")).toSet
     assert(parts == expectedPartitionNames)
diff --git 
a/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/execution/datasources/GlutenV1WriteCommandSuite.scala
 
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/execution/datasources/GlutenV1WriteCommandSuite.scala
index 7799252ba..3d277b94c 100644
--- 
a/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/execution/datasources/GlutenV1WriteCommandSuite.scala
+++ 
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/execution/datasources/GlutenV1WriteCommandSuite.scala
@@ -21,7 +21,7 @@ import org.apache.gluten.execution.SortExecTransformer
 import org.apache.spark.sql.GlutenSQLTestsBaseTrait
 import org.apache.spark.sql.catalyst.expressions.{Ascending, 
AttributeReference, NullsFirst, SortOrder}
 import org.apache.spark.sql.catalyst.plans.logical.{LogicalPlan, Sort}
-import org.apache.spark.sql.execution.{QueryExecution, SortExec}
+import org.apache.spark.sql.execution.{QueryExecution, SortExec, 
VeloxColumnarWriteFilesExec}
 import org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanExec
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.types.{IntegerType, StringType}
@@ -122,8 +122,8 @@ class GlutenV1WriteCommandSuite
             val executedPlan = FileFormatWriter.executedPlan.get
 
             val plan = if (enabled) {
-              assert(executedPlan.isInstanceOf[WriteFilesExec])
-              executedPlan.asInstanceOf[WriteFilesExec].child
+              assert(executedPlan.isInstanceOf[VeloxColumnarWriteFilesExec])
+              executedPlan.asInstanceOf[VeloxColumnarWriteFilesExec].child
             } else {
               executedPlan.transformDown { case a: AdaptiveSparkPlanExec => 
a.executedPlan }
             }
@@ -204,8 +204,8 @@ class GlutenV1WriteCommandSuite
           val executedPlan = FileFormatWriter.executedPlan.get
 
           val plan = if (enabled) {
-            assert(executedPlan.isInstanceOf[WriteFilesExec])
-            executedPlan.asInstanceOf[WriteFilesExec].child
+            assert(executedPlan.isInstanceOf[VeloxColumnarWriteFilesExec])
+            executedPlan.asInstanceOf[VeloxColumnarWriteFilesExec].child
           } else {
             executedPlan.transformDown { case a: AdaptiveSparkPlanExec => 
a.executedPlan }
           }
diff --git 
a/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/sources/GlutenInsertSuite.scala
 
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/sources/GlutenInsertSuite.scala
index 51b034ca8..69e075b78 100644
--- 
a/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/sources/GlutenInsertSuite.scala
+++ 
b/gluten-ut/spark35/src/test/scala/org/apache/spark/sql/sources/GlutenInsertSuite.scala
@@ -141,7 +141,7 @@ class GlutenInsertSuite
         .find(_.isInstanceOf[SortExecTransformer])
         .isEmpty)
     // all operators should be transformed
-    assert(writeFiles.find(!_.isInstanceOf[GlutenPlan]).isEmpty)
+    assert(writeFiles.child.find(!_.isInstanceOf[GlutenPlan]).isEmpty)
 
     val parts = 
spark.sessionState.catalog.listPartitionNames(TableIdentifier("pt")).toSet
     assert(parts == expectedPartitionNames)


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

Reply via email to