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

Zouxxyy 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 e425fb42e8 [spark] Make DeletionVectorTest pass with 
write.use-v2-write enabled (#8614)
e425fb42e8 is described below

commit e425fb42e8121c290b749629884432db0c3ad62c
Author: Kerwin Zhang <[email protected]>
AuthorDate: Tue Jul 14 15:09:30 2026 +0800

    [spark] Make DeletionVectorTest pass with write.use-v2-write enabled (#8614)
---
 .../paimon/spark/sql/DeletionVectorTest.scala      | 76 +++++++++++++++-------
 1 file changed, 51 insertions(+), 25 deletions(-)

diff --git 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/DeletionVectorTest.scala
 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/DeletionVectorTest.scala
index 6a34236572..05c706b307 100644
--- 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/DeletionVectorTest.scala
+++ 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/DeletionVectorTest.scala
@@ -22,6 +22,7 @@ import org.apache.paimon.data.BinaryRow
 import org.apache.paimon.deletionvectors.{BucketedDvMaintainer, 
BucketedDvMaintainerTest, DeletionVector}
 import org.apache.paimon.fs.Path
 import org.apache.paimon.spark.PaimonSparkTestBase
+import org.apache.paimon.spark.SparkTable
 import org.apache.paimon.spark.read.PaimonSplitScan
 import org.apache.paimon.spark.schema.PaimonMetadataColumn
 import org.apache.paimon.table.FileStoreTable
@@ -44,7 +45,29 @@ class DeletionVectorTest extends PaimonSparkTestBase with 
AdaptiveSparkPlanHelpe
 
   import testImplicits._
 
-  private def runAndCheckSplitScan(query: String): Unit = {
+  /**
+   * Runs a DELETE/UPDATE/MERGE and asserts it went through a 
deletion-vector-aware row-level path.
+   *
+   * There are two such paths and this check is agnostic to which one the 
target table uses:
+   *   - the V1 deletion-vector copy-on-write path, whose read is a 
[[PaimonSplitScan]] carrying
+   *     row-id metadata columns (used when `write.use-v2-write=false`, or for 
bucketed
+   *     deletion-vector append tables); and
+   *   - the V2 delta path 
([[org.apache.paimon.spark.rowops.PaimonSparkDeltaOperation]] /
+   *     `WriteDelta`) for unaware-bucket deletion-vector append tables when 
`write.use-v2-write` is
+   *     enabled, which reads through a regular pushdown scan and therefore 
produces no
+   *     `PaimonSplitScan`.
+   *
+   * The routing decision is taken from [[SparkTable.supportsV2DeltaOps]], the 
single source of
+   * truth for the delta gating conditions, so the split-scan assertion is 
skipped exactly when the
+   * production code would pick the delta path. Without this the suite fails 
when run with
+   * `spark.paimon.write.use-v2-write=true`, because the delta path never 
yields a split scan.
+   */
+  private def runAndCheckSplitScan(query: String, target: FileStoreTable): 
Unit = {
+    if (SparkTable.supportsV2DeltaOps(SparkTable.of(target))) {
+      spark.sql(query).collect()
+      return
+    }
+
     val batchScans = new ArrayBuffer[(DataSourceV2Relation, BatchScanExec)]()
     val listener = new QueryExecutionListener {
       override def onFailure(f: String, qe: QueryExecution, e: Exception): 
Unit = {}
@@ -119,21 +142,24 @@ class DeletionVectorTest extends PaimonSparkTestBase with 
AdaptiveSparkPlanHelpe
           val table = loadTable("target")
           val dvMaintainerFactory =
             BucketedDvMaintainer.factory(table.store().newIndexFileHandler())
-          runAndCheckSplitScan(s"""
-                                  |MERGE INTO target
-                                  |USING source
-                                  |ON target.a = source.a
-                                  |WHEN MATCHED AND target.a = 5 THEN
-                                  |UPDATE SET b = source.b + target.b
-                                  |WHEN MATCHED AND source.c > 'c2' THEN
-                                  |UPDATE SET *
-                                  |WHEN MATCHED THEN
-                                  |DELETE
-                                  |WHEN NOT MATCHED AND c > 'c9' THEN
-                                  |INSERT (a, b, c) VALUES (a, b * 1.1, c)
-                                  |WHEN NOT MATCHED THEN
-                                  |INSERT *
-                                  |""".stripMargin)
+          runAndCheckSplitScan(
+            s"""
+               |MERGE INTO target
+               |USING source
+               |ON target.a = source.a
+               |WHEN MATCHED AND target.a = 5 THEN
+               |UPDATE SET b = source.b + target.b
+               |WHEN MATCHED AND source.c > 'c2' THEN
+               |UPDATE SET *
+               |WHEN MATCHED THEN
+               |DELETE
+               |WHEN NOT MATCHED AND c > 'c9' THEN
+               |INSERT (a, b, c) VALUES (a, b * 1.1, c)
+               |WHEN NOT MATCHED THEN
+               |INSERT *
+               |""".stripMargin,
+            table
+          )
 
           checkAnswer(
             spark.sql("SELECT * FROM target ORDER BY a, b"),
@@ -175,7 +201,7 @@ class DeletionVectorTest extends PaimonSparkTestBase with 
AdaptiveSparkPlanHelpe
 
           val cond1 = "id = 2"
           val rowMetaInfo1 = getFilePathAndRowIndex(cond1)
-          runAndCheckSplitScan(s"UPDATE T SET name = 'b_2' WHERE $cond1")
+          runAndCheckSplitScan(s"UPDATE T SET name = 'b_2' WHERE $cond1", 
table)
           checkAnswer(
             spark.sql(s"SELECT * from T ORDER BY id"),
             Row(1, "a") :: Row(2, "b_2") :: Row(3, "c") :: Nil)
@@ -195,12 +221,12 @@ class DeletionVectorTest extends PaimonSparkTestBase with 
AdaptiveSparkPlanHelpe
           Assertions.assertTrue(deletionVectors2 == deletionVectors3)
 
           val cond2 = "id % 2 = 1"
-          runAndCheckSplitScan(s"UPDATE T SET name = concat(name, '_2') WHERE 
$cond2")
+          runAndCheckSplitScan(s"UPDATE T SET name = concat(name, '_2') WHERE 
$cond2", table)
           checkAnswer(
             spark.sql(s"SELECT * from T ORDER BY id"),
             Row(1, "a_2") :: Row(2, "b_2") :: Row(3, "c_2") :: Row(4, "d") :: 
Row(5, "e_2") :: Nil)
 
-          runAndCheckSplitScan("UPDATE T SET name = '_all'")
+          runAndCheckSplitScan("UPDATE T SET name = '_all'", table)
           checkAnswer(
             spark.sql(s"SELECT * from T ORDER BY id"),
             Row(1, "_all") :: Row(2, "_all") :: Row(3, "_all") :: Row(4, 
"_all") :: Row(
@@ -248,7 +274,7 @@ class DeletionVectorTest extends PaimonSparkTestBase with 
AdaptiveSparkPlanHelpe
 
           val cond1 = "id = 2"
           val rowMetaInfo1 = getFilePathAndRowIndex(cond1)
-          runAndCheckSplitScan(s"UPDATE T SET name = 'b_2' WHERE $cond1")
+          runAndCheckSplitScan(s"UPDATE T SET name = 'b_2' WHERE $cond1", 
table)
           checkAnswer(
             spark.sql(s"SELECT * from T ORDER BY id"),
             Row(1, "a", "2024") :: Row(2, "b_2", "2024") :: Row(3, "c", 
"2025") :: Row(
@@ -269,7 +295,7 @@ class DeletionVectorTest extends PaimonSparkTestBase with 
AdaptiveSparkPlanHelpe
 
           val cond2 = "pt = '2025'"
           val rowMetaInfo2 = rowMetaInfo1 ++ getFilePathAndRowIndex(cond2)
-          runAndCheckSplitScan(s"UPDATE T SET name = concat(name, '_2') WHERE 
$cond2")
+          runAndCheckSplitScan(s"UPDATE T SET name = concat(name, '_2') WHERE 
$cond2", table)
           checkAnswer(
             spark.sql(s"SELECT * from T ORDER BY id"),
             Row(1, "a", "2024") :: Row(2, "b_2", "2024") :: Row(3, "c_2", 
"2025") :: Row(
@@ -333,7 +359,7 @@ class DeletionVectorTest extends PaimonSparkTestBase with 
AdaptiveSparkPlanHelpe
 
           val cond1 = "id = 2"
           val rowMetaInfo1 = getFilePathAndRowIndex(cond1)
-          runAndCheckSplitScan(s"DELETE FROM T WHERE $cond1")
+          runAndCheckSplitScan(s"DELETE FROM T WHERE $cond1", table)
           checkAnswer(spark.sql(s"SELECT * from T ORDER BY id"), Row(1, "a") 
:: Nil)
           val deletionVectors2 = getAllLatestDeletionVectors(table, 
dvMaintainerFactory)
           Assertions.assertEquals(1, deletionVectors2.size)
@@ -351,7 +377,7 @@ class DeletionVectorTest extends PaimonSparkTestBase with 
AdaptiveSparkPlanHelpe
           Assertions.assertTrue(deletionVectors2 == deletionVectors3)
 
           val cond2 = "id % 2 = 1"
-          runAndCheckSplitScan(s"DELETE FROM T WHERE $cond2")
+          runAndCheckSplitScan(s"DELETE FROM T WHERE $cond2", table)
           checkAnswer(spark.sql(s"SELECT * from T ORDER BY id"), Row(2, "bb") 
:: Row(4, "d") :: Nil)
 
           spark.sql("CALL sys.compact('T')")
@@ -398,7 +424,7 @@ class DeletionVectorTest extends PaimonSparkTestBase with 
AdaptiveSparkPlanHelpe
 
           val cond1 = "id = 2"
           val rowMetaInfo1 = getFilePathAndRowIndex(cond1)
-          runAndCheckSplitScan(s"DELETE FROM T WHERE $cond1")
+          runAndCheckSplitScan(s"DELETE FROM T WHERE $cond1", table)
           checkAnswer(
             spark.sql(s"SELECT * from T ORDER BY id"),
             Row(1, "a", "2024") :: Row(3, "c", "2025") :: Row(4, "d", "2025") 
:: Nil)
@@ -412,7 +438,7 @@ class DeletionVectorTest extends PaimonSparkTestBase with 
AdaptiveSparkPlanHelpe
 
           val cond2 = "id = 3"
           val rowMetaInfo2 = rowMetaInfo1 ++ getFilePathAndRowIndex(cond2)
-          runAndCheckSplitScan(s"DELETE FROM T WHERE $cond2")
+          runAndCheckSplitScan(s"DELETE FROM T WHERE $cond2", table)
           checkAnswer(
             spark.sql(s"SELECT * from T ORDER BY id"),
             Row(1, "a", "2024") :: Row(4, "d", "2025") :: Nil)

Reply via email to