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

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 260fcc757b [spark] Support partition sink strategy for Format Table 
(#8786)
260fcc757b is described below

commit 260fcc757bda851fc6e9b7c00c16f75687fc5c24
Author: Zouxxyy <[email protected]>
AuthorDate: Tue Jul 21 23:47:27 2026 +0800

    [spark] Support partition sink strategy for Format Table (#8786)
---
 .../paimon/spark/format/PaimonFormatTable.scala    | 12 ++++-
 .../spark/write/PaimonWriteRequirement.scala       | 28 +++++++---
 .../sql/FormatTableWriteDistributeModeTest.scala   | 61 ++++++++++++++++++++++
 3 files changed, 93 insertions(+), 8 deletions(-)

diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/format/PaimonFormatTable.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/format/PaimonFormatTable.scala
index 776fc98ca1..eecaed851b 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/format/PaimonFormatTable.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/format/PaimonFormatTable.scala
@@ -22,7 +22,7 @@ import org.apache.paimon.CoreOptions
 import org.apache.paimon.format.csv.CsvOptions
 import org.apache.paimon.fs.Path
 import org.apache.paimon.spark.{BaseTable, FormatTableScanBuilder}
-import org.apache.paimon.spark.write.BaseV2WriteBuilder
+import org.apache.paimon.spark.write.{BaseV2WriteBuilder, 
PaimonWriteRequirement}
 import org.apache.paimon.table.FormatTable
 import org.apache.paimon.table.format.FormatTablePartitionManager
 import org.apache.paimon.types.RowType
@@ -31,6 +31,8 @@ import org.apache.paimon.utils.PartitionPathUtils
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.connector.catalog.{SupportsRead, SupportsWrite, 
TableCapability, TableCatalog}
 import org.apache.spark.sql.connector.catalog.TableCapability.{BATCH_READ, 
BATCH_WRITE, OVERWRITE_BY_FILTER, OVERWRITE_DYNAMIC}
+import org.apache.spark.sql.connector.distributions.Distribution
+import org.apache.spark.sql.connector.expressions.SortOrder
 import org.apache.spark.sql.connector.read.ScanBuilder
 import org.apache.spark.sql.connector.write._
 import org.apache.spark.sql.connector.write.streaming.StreamingWrite
@@ -268,7 +270,13 @@ case class PaimonFormatTableWriterBuilder(table: 
FormatTable, writeSchema: Struc
 
   override def partitionRowType(): RowType = table.partitionType
 
-  override def build: Write = new Write() {
+  override def build: Write = new Write with RequiresDistributionAndOrdering {
+    private val writeRequirement = PaimonWriteRequirement(table)
+
+    override def requiredDistribution(): Distribution = 
writeRequirement.distribution
+
+    override def requiredOrdering(): Array[SortOrder] = 
writeRequirement.ordering
+
     override def toBatch: BatchWrite = {
       SparkShimLoader.shim
         .createFormatTableBatchWrite(table, overwriteDynamic, 
overwritePartitions, writeSchema)
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/write/PaimonWriteRequirement.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/write/PaimonWriteRequirement.scala
index 1f95c19146..be661807e4 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/write/PaimonWriteRequirement.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/write/PaimonWriteRequirement.scala
@@ -18,10 +18,11 @@
 
 package org.apache.paimon.spark.write
 
+import org.apache.paimon.CoreOptions
 import org.apache.paimon.CoreOptions.PartitionSinkStrategy
 import org.apache.paimon.spark.commands.BucketExpression.quote
+import org.apache.paimon.table.{FileStoreTable, FormatTable}
 import org.apache.paimon.table.BucketMode._
-import org.apache.paimon.table.FileStoreTable
 
 import org.apache.spark.sql.connector.distributions.{ClusteredDistribution, 
Distribution, Distributions}
 import org.apache.spark.sql.connector.expressions.{Expression, Expressions, 
SortOrder}
@@ -39,7 +40,7 @@ object PaimonWriteRequirement {
 
   def apply(table: FileStoreTable): PaimonWriteRequirement = {
     val bucketSpec = table.bucketSpec()
-    val bucketTransforms = bucketSpec.getBucketMode match {
+    val bucketTransforms: Seq[Expression] = bucketSpec.getBucketMode match {
       case HASH_FIXED =>
         Seq(
           Expressions.bucket(
@@ -52,15 +53,30 @@ object PaimonWriteRequirement {
           s"Unsupported bucket mode ${bucketSpec.getBucketMode}")
     }
 
+    create(
+      table.schema().partitionKeys().asScala.toSeq,
+      bucketTransforms,
+      table.coreOptions().partitionSinkStrategy())
+  }
+
+  def apply(table: FormatTable): PaimonWriteRequirement = {
+    create(
+      table.partitionKeys().asScala.toSeq,
+      Seq.empty,
+      CoreOptions.fromMap(table.options()).partitionSinkStrategy())
+  }
+
+  private def create(
+      partitionKeys: Seq[String],
+      bucketTransforms: Seq[Expression],
+      partitionSinkStrategy: PartitionSinkStrategy): PaimonWriteRequirement = {
     val partitionTransforms =
-      table.schema().partitionKeys().asScala.map(key => 
Expressions.identity(quote(key)))
+      partitionKeys.map(key => Expressions.identity(quote(key)))
     val clusteringExpressions =
       (partitionTransforms ++ 
bucketTransforms).map(identity[Expression]).toArray
 
     if (
-      clusteringExpressions.isEmpty || (bucketTransforms.isEmpty && table
-        .coreOptions()
-        .partitionSinkStrategy()
+      clusteringExpressions.isEmpty || (bucketTransforms.isEmpty && 
partitionSinkStrategy
         .equals(PartitionSinkStrategy.NONE))
     ) {
       EMPTY
diff --git 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/FormatTableWriteDistributeModeTest.scala
 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/FormatTableWriteDistributeModeTest.scala
new file mode 100644
index 0000000000..946252fa42
--- /dev/null
+++ 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/FormatTableWriteDistributeModeTest.scala
@@ -0,0 +1,61 @@
+/*
+ * 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.paimon.spark.sql
+
+import org.apache.paimon.spark.PaimonHiveTestBase
+
+import org.apache.spark.sql.Row
+import org.apache.spark.sql.execution.CommandResultExec
+import org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanHelper
+import org.apache.spark.sql.execution.exchange.ShuffleExchangeLike
+
+class FormatTableWriteDistributeModeTest extends PaimonHiveTestBase with 
AdaptiveSparkPlanHelper {
+
+  override protected def beforeEach(): Unit = {
+    sql(s"USE $paimonHiveCatalogName")
+    sql(s"USE $hiveDbName")
+  }
+
+  test("Write distribute mode: write partitioned format table") {
+    for (distributeMode <- Seq("none", "hash")) {
+      withTable("t") {
+        sql(
+          "CREATE TABLE t (id INT, pt STRING) USING parquet PARTITIONED BY 
(pt) " +
+            "TBLPROPERTIES ('format-table.implementation'='paimon')")
+        val query = "INSERT INTO t VALUES (1, 'p1'), (2, 'p2')"
+
+        withSparkSQLConf("spark.paimon.partition.sink-strategy" -> 
distributeMode) {
+          val df = spark.sql(query)
+          val shuffleNodes = collect(
+            
df.queryExecution.executedPlan.asInstanceOf[CommandResultExec].commandPhysicalPlan)
 {
+            case shuffle: ShuffleExchangeLike => shuffle
+          }
+
+          if (distributeMode == "none") {
+            assert(shuffleNodes.isEmpty)
+          } else {
+            assert(shuffleNodes.size == 1)
+          }
+
+          checkAnswer(spark.sql("SELECT * FROM t ORDER BY id"), Seq(Row(1, 
"p1"), Row(2, "p2")))
+        }
+      }
+    }
+  }
+}

Reply via email to