sunchao commented on code in PR #6640:
URL: https://github.com/apache/datafusion-comet/pull/6640#discussion_r4180992529


##########
spark/src/test/spark-4.1+/org/apache/comet/exec/CometMergeRowsSuite.scala:
##########
@@ -47,63 +50,162 @@ class CometMergeRowsSuite extends CometTestBase with 
AdaptiveSparkPlanHelper {
       .set("spark.sql.autoBroadcastJoinThreshold", "-1")
       .set("spark.sql.adaptive.autoBroadcastJoinThreshold", "-1")
       .set("spark.sql.shuffle.partitions", "4")
+      .setMaster("local[5,2]")
   }
 
-  test("Spark 4.1+ MERGE retains Spark MergeRowsExec for write-summary 
compatibility") {
-    val target = s"$catalog.default.rowlevel_target"
-    val source = s"$catalog.default.rowlevel_source"
-
-    sql(s"DROP TABLE IF EXISTS $target")
-    sql(s"DROP TABLE IF EXISTS $source")
-    sql(s"CREATE TABLE $target (id INT, amount DOUBLE) USING parquet")
-    sql(s"CREATE TABLE $source (id INT, amount DOUBLE) USING parquet")
-    sql(s"INSERT INTO $target VALUES (1, 10.0), (2, 20.0)")
-    sql(s"INSERT INTO $source VALUES (2, 200.0), (3, 300.0)")
-
-    val captured = ArrayBuffer[QueryExecution]()
-    val listener = new QueryExecutionListener {
-      override def onSuccess(funcName: String, qe: QueryExecution, durationNs: 
Long): Unit =
-        captured += qe
-      override def onFailure(funcName: String, qe: QueryExecution, exception: 
Exception): Unit =
-        ()
+  test("native MergeRows semantic counters match Spark for Keep, Discard and 
Split") {
+    withTempPath { path =>
+      spark
+        .range(1, 9)
+        .selectExpr(
+          "CAST(id AS INT) AS id",
+          "id <= 6 AS source_present",
+          "id != 6 AS target_present")
+        .repartition(2)
+        .write
+        .parquet(path.getCanonicalPath)
+      val input = 
spark.read.parquet(path.getCanonicalPath).queryExecution.analyzed
+      val id = input.output(0)
+      def is(value: Int) = EqualTo(id, Literal(value))
+      def plan = MergeRows(
+        input.output(1),
+        input.output(2),
+        Seq(
+          MergeRows.Keep(MergeRows.Copy, is(1), Seq(id)),
+          MergeRows.Discard(is(2)),
+          MergeRows.Keep(MergeRows.Update, is(3), Seq(id)),
+          MergeRows.Split(is(4), Seq(id), Seq(id)),
+          MergeRows.Keep(MergeRows.Delete, is(5), Seq(id))),
+        Seq(MergeRows.Keep(MergeRows.Insert, Literal(true), Seq(id))),
+        Seq(MergeRows.Keep(MergeRows.Update, is(7), Seq(id)), 
MergeRows.Discard(is(8))),
+        checkCardinality = false,
+        output = Seq(id),
+        child = input)
+      val expected = Map(
+        "numTargetRowsCopied" -> 1L,
+        "numTargetRowsDeleted" -> 3L,
+        "numTargetRowsUpdated" -> 3L,
+        "numTargetRowsInserted" -> 1L,
+        "numTargetRowsMatchedUpdated" -> 2L,
+        "numTargetRowsMatchedDeleted" -> 2L,
+        "numTargetRowsNotMatchedBySourceUpdated" -> 1L,
+        "numTargetRowsNotMatchedBySourceDeleted" -> 1L)
+
+      Seq(true, false).foreach { adaptiveEnabled =>
+        withSQLConf("spark.sql.adaptive.enabled" -> adaptiveEnabled.toString) {
+          val baseline = withSQLConf(CometConf.COMET_ENABLED.key -> "false") {
+            val df = datasetOfRows(spark, 
ShimCometMergeRows.withNativeMergeSummary(plan))
+            val rows = df.collect().map(_.toString).sorted.toSeq
+            val merge = 
find(df.queryExecution.executedPlan)(_.isInstanceOf[MergeRowsExec]).get
+            assert(expected.map { case (key, _) =>
+              key -> MergeRowsMetricsShim.value(merge.metrics(key))
+            } == expected)
+            rows
+          }
+          withSQLConf(CometConf.COMET_EXEC_MERGE_ROWS_ENABLED.key -> "true") {
+            val df = datasetOfRows(spark, 
ShimCometMergeRows.withNativeMergeSummary(plan))
+            assert(df.collect().map(_.toString).sorted.toSeq == baseline)
+            val merge = 
find(df.queryExecution.executedPlan)(_.isInstanceOf[CometMergeRowsExec])
+              .getOrElse(
+                fail(s"native MergeRows did not engage: 
${df.queryExecution.executedPlan}"))
+            assert(expected.map { case (key, _) =>
+              key -> MergeRowsMetricsShim.value(merge.metrics(key))
+            } == expected)
+          }
+        }
+      }
+    }
+  }
+
+  test("Spark 4.2 semantic metrics retain only successful task attempt 
updates") {
+    assume(isSpark42Plus, "last-attempt MERGE metrics require Spark 4.2")
+    val metrics = MergeRowsMetricsShim.metrics(spark.sparkContext)
+    val node = CometMetricNode(metrics, Seq.empty)
+    val names = metrics.keys.toSeq
+    def runAttempt(): Unit = {
+      val attempts = spark.sparkContext
+        .parallelize(0 until 4, 4)
+        .mapPartitions { input =>
+          val count = input.size.toLong
+          val attempt = TaskContext.get().attemptNumber()
+          if (attempt == 0) {
+            names.foreach(node.set(_, 100L))
+            throw new IllegalStateException("injected failed MERGE metric 
attempt")
+          }
+          // JNI publishes cumulative snapshots, so repeated sets must not add 
the old value.
+          names.foreach(node.set(_, count + 10L))

Review Comment:
   [P2] Use monotonic cumulative snapshots in the retry test. With Spark 4.2, 
each successful partition has `count == 1`, but these calls report 11 followed 
by 1. `CometMetricNode.set` adds only positive deltas and retains the highest 
reported value, so each successful attempt contributes 11. The summary is 
therefore 44, and the assertion expecting 4 fails before the second RDD 
executes. This breaks the registered Spark 4.2 suite. Please repeat `count`, or 
use increasing snapshots ending at `count`, while retaining the failed-attempt 
injection.
   
   Evidence: A bounded standalone Spark 4.2.0 reproduction compiled the PR’s 
`MergeRowsMetricsShim` and used the exact `CometMetricNode.set` method with the 
new test’s retry/RDD body. It failed with `numTargetRowsCopied actual=44 
expected=4` and `AssertionError: incorrect last-attempt numTargetRowsCopied`. 
Changing only the first successful-attempt snapshot from `count + 10L` to 
`count` made all eight counters equal 4 for both RDD executions. The full 
ScalaTest suite was not run.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to