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

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


The following commit(s) were added to refs/heads/master by this push:
     new 44472751 [AURON #2429] Add mixed-format fallback coverage for Iceberg 
changelog native scan (#2430)
44472751 is described below

commit 44472751550f3cd03b54683f7cf1cb800eff6922
Author: Ming Wei <[email protected]>
AuthorDate: Fri Jul 31 08:33:41 2026 +0800

    [AURON #2429] Add mixed-format fallback coverage for Iceberg changelog 
native scan (#2430)
    
    **Which issue does this PR close?**
    Closes #2429
    
    **Rationale for this change**
    Auron native Iceberg changelog scan requires all planned changelog tasks
    to use a single supported file format.
    
    Without validating the planned tasks, a mixed-format fallback test could
    pass because of an unrelated changelog fallback and fail to exercise the
    intended file-format guard.
    
    **What changes are included in this PR?**
    Adds fallback coverage for an Iceberg changelog range containing both
    Parquet and ORC data files.
    
    The test:
    
    - verifies that all planned changelog tasks are `AddedRowsScanTask`
    - verifies that the task file formats are exactly Parquet and ORC
    - compares the result with Spark execution
    - verifies that `NativeIcebergTableScan` is not used
    
    No execution logic is changed.
    
    **Are there any user-facing changes?**
    No user-facing changes. This is a test-only change.
    
    **How was this patch tested?**
    UT.
    
    ---------
    Co-authored-by: Shilun Fan <[email protected]>
    Signed-off-by: weimingdiit <[email protected]>
---
 .../iceberg/AuronIcebergIntegrationSuite.scala     | 97 +++++++++++++++++++++-
 1 file changed, 96 insertions(+), 1 deletion(-)

diff --git 
a/thirdparty/auron-iceberg/src/test/scala/org/apache/auron/iceberg/AuronIcebergIntegrationSuite.scala
 
b/thirdparty/auron-iceberg/src/test/scala/org/apache/auron/iceberg/AuronIcebergIntegrationSuite.scala
index 0032e289..d2953f04 100644
--- 
a/thirdparty/auron-iceberg/src/test/scala/org/apache/auron/iceberg/AuronIcebergIntegrationSuite.scala
+++ 
b/thirdparty/auron-iceberg/src/test/scala/org/apache/auron/iceberg/AuronIcebergIntegrationSuite.scala
@@ -22,8 +22,9 @@ import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 
 import scala.collection.JavaConverters._
+import scala.util.control.NonFatal
 
-import org.apache.iceberg.{FileFormat, FileScanTask, MetadataColumns}
+import org.apache.iceberg.{AddedRowsScanTask, ChangelogScanTask, FileFormat, 
FileScanTask, MetadataColumns, ScanTask}
 import org.apache.iceberg.data.{GenericAppenderFactory, Record}
 import org.apache.iceberg.deletes.PositionDelete
 import org.apache.iceberg.spark.Spark3Util
@@ -738,6 +739,55 @@ class AuronIcebergIntegrationSuite
     }
   }
 
+  test("iceberg changelog scan falls back for mixed file formats") {
+    withTable("local.db.t_changelog_mixed_formats") {
+      withTempView("t_changelog_mixed_formats_changes") {
+        sql("""
+              |create table local.db.t_changelog_mixed_formats (id int, v 
string)
+              |using iceberg
+              |tblproperties ('format-version' = '2')
+              |""".stripMargin)
+        sql("insert into local.db.t_changelog_mixed_formats values (0, 
'seed')")
+        val startSnapshotId = 
currentSnapshotId("local.db.t_changelog_mixed_formats")
+        sql("insert into local.db.t_changelog_mixed_formats values (1, 
'parquet')")
+        sql("""
+              |alter table local.db.t_changelog_mixed_formats
+              |set tblproperties ('write.format.default' = 'orc')
+              |""".stripMargin)
+        sql("insert into local.db.t_changelog_mixed_formats values (2, 'orc')")
+        val endSnapshotId = 
currentSnapshotId("local.db.t_changelog_mixed_formats")
+        createChangelogView(
+          "local.db.t_changelog_mixed_formats",
+          "t_changelog_mixed_formats_changes",
+          startSnapshotId,
+          endSnapshotId)
+
+        val query =
+          """
+            |select id, v, _change_type, _change_ordinal, _commit_snapshot_id
+            |from t_changelog_mixed_formats_changes
+            |order by id
+            |""".stripMargin
+        var expected: Seq[Row] = Nil
+        withSQLConf("spark.auron.enable" -> "false") {
+          expected = sql(query).collect().toSeq
+        }
+        val changelogTasks = changelogScanTasks(query)
+        assert(changelogTasks.forall(_.isInstanceOf[AddedRowsScanTask]), 
changelogTasks)
+        val formats = changelogTasks.collect { case task: AddedRowsScanTask =>
+          task.file().format()
+        }
+        assert(formats.toSet == Set(FileFormat.PARQUET, FileFormat.ORC), 
formats)
+        withSQLConf("spark.auron.enable" -> "true", 
"spark.auron.enable.iceberg.scan" -> "true") {
+          val df = sql(query)
+          checkAnswer(df, expected)
+          val plan = df.queryExecution.executedPlan.toString()
+          assert(!plan.contains("NativeIcebergTableScan"))
+        }
+      }
+    }
+  }
+
   test("iceberg scan falls back when reading unsupported metadata columns") {
     withTable("local.db.t4_pos") {
       sql("create table local.db.t4_pos using iceberg as select 1 as id, 'a' 
as v")
@@ -899,6 +949,51 @@ class AuronIcebergIntegrationSuite
     nativeScan
   }
 
+  private def changelogScanTasks(sqlText: String): Seq[ChangelogScanTask] = {
+    val scan = sql(sqlText).queryExecution.sparkPlan
+      .collectFirst {
+        case batchScan: BatchScanExec if isChangelogScan(batchScan.scan) =>
+          batchScan
+      }
+      .getOrElse {
+        throw new AssertionError(s"Cannot find changelog BatchScanExec for 
query: $sqlText")
+      }
+
+    val tasks =
+      scan.scan.toBatch.planInputPartitions().toSeq.flatMap { partition =>
+        icebergScanTasks(partition)
+      }
+    assert(tasks.forall(_.isInstanceOf[ChangelogScanTask]), tasks)
+    tasks.collect { case task: ChangelogScanTask => task }
+  }
+
+  private def isChangelogScan(scan: org.apache.spark.sql.connector.read.Scan): 
Boolean =
+    scan.getClass.getName == 
"org.apache.iceberg.spark.source.SparkChangelogScan"
+
+  private def icebergScanTasks(partition: AnyRef): Seq[ScanTask] = {
+    try {
+      val taskGroupField = partition.getClass.getDeclaredField("taskGroup")
+      taskGroupField.setAccessible(true)
+      val taskGroup = taskGroupField.get(partition)
+
+      val tasksMethod = taskGroup.getClass.getDeclaredMethod("tasks")
+      tasksMethod.setAccessible(true)
+      tasksMethod
+        .invoke(taskGroup)
+        .asInstanceOf[java.util.Collection[_]]
+        .asScala
+        .collect { case task: ScanTask =>
+          task
+        }
+        .toSeq
+    } catch {
+      case NonFatal(t) =>
+        throw new AssertionError(
+          s"Cannot read Iceberg scan tasks from ${partition.getClass.getName}",
+          t)
+    }
+  }
+
   private def icebergScanPlan(df: DataFrame) =
     df.queryExecution.sparkPlan.collectFirst { case scan: BatchScanExec =>
       IcebergScanSupport.plan(scan)

Reply via email to