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

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


The following commit(s) were added to refs/heads/master by this push:
     new 85f9de41e04f test(variant): pin the scan ReadSchema per arm (#20155)
85f9de41e04f is described below

commit 85f9de41e04f6f73107d94ceba3342bdb984add5
Author: voonhous <[email protected]>
AuthorDate: Wed Sep 30 16:41:33 2026 +0800

    test(variant): pin the scan ReadSchema per arm (#20155)
    
    Both arms of a spark.sql.variant.pushVariantIntoScan sweep return the
    same rows, so only the plan proves that the true arm ran with the
    rewrite. The other sweeps of TestVariantShreddingMixedLayouts assert
    that through variantProjectionPushedIntoScan; the mixed-layout
    variant_get test, the first sweep of the suite, had no plan assertion
    on its COW and MOR legs.
    
    Add scanReadSchema to the test support, the ReadSchema of the file
    scan as its explain line prints it, and pin it on those legs:
    v:struct<0:bigint> with the rule on and v:variant with it off, plus
    the eight-member struct behind the partial-update read, whose
    expectation rests on seven of the eight pushed fields being null.
    
    Test only.
    
    Part of #18285.
---
 .../schema/TestVariantShreddingMixedLayouts.scala  | 18 ++++++++++++++
 .../dml/schema/VariantShreddingTestSupport.scala   | 29 ++++++++++++++++++----
 2 files changed, 42 insertions(+), 5 deletions(-)

diff --git 
a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/TestVariantShreddingMixedLayouts.scala
 
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/TestVariantShreddingMixedLayouts.scala
index 1be340407c56..1ccaa716c343 100644
--- 
a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/TestVariantShreddingMixedLayouts.scala
+++ 
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/TestVariantShreddingMixedLayouts.scala
@@ -594,6 +594,18 @@ class TestVariantShreddingMixedLayouts extends 
HoodieSparkSqlTestBase with Varia
   test("variant_get filters and projections resolve per file across mixed 
layouts") {
     assume(HoodieSparkUtils.gteqSpark4_1, SPARK_4_1_GATE)
 
+    // Both arms of a leg expect the very same rows, so the scan is what tells 
them apart: with the
+    // rule on, its ReadSchema carries v as a projection struct with one 
member per pushed path. A
+    // true arm that silently fell back to reading the whole variant would 
show `v:variant` there.
+    def assertScanReadsV(sql: String, pushIntoScan: String, pushedPaths: Int, 
leg: String): Unit = {
+      val readV = if (pushIntoScan.toBoolean) {
+        (0 until pushedPaths).map(i => s"$i:bigint").mkString("struct<", ",", 
">")
+      } else {
+        "variant"
+      }
+      assert(scanReadSchema(sql) == s"struct<id:int,v:$readV>", s"[$leg] 
ReadSchema of the scan of: $sql")
+    }
+
     // Read-mode test; SPARK pinned (see the mixed-files test above).
     Seq("true", "false").foreach { pushIntoScan =>
       withSQLConf("spark.sql.variant.pushVariantIntoScan" -> pushIntoScan) {
@@ -629,6 +641,8 @@ class TestVariantShreddingMixedLayouts extends 
HoodieSparkSqlTestBase with Varia
           checkAnswer(s"select count(*) from $tableName where v is 
null")(Seq(0))
           assertVariantSegments(tableName, leg, Seq(("v", Seq(
             (0 until 20, ObjA), (20 until 30, ObjAConflict), (30 until 40, 
ObjB)))))
+          assertScanReadsV(s"select id, try_variant_get(v, '$$.a', 'bigint') 
from $tableName",
+            pushIntoScan, pushedPaths = 1, leg)
         }
       }
     }
@@ -667,6 +681,8 @@ class TestVariantShreddingMixedLayouts extends 
HoodieSparkSqlTestBase with Varia
             s"select count(*) from $tableName where try_variant_get(v, '$$.a', 
'bigint') > 100")(Seq(2))
           checkAnswer(
             s"select id from $tableName where variant_get(v, '$$.b', 'string') 
= 'b7'")(Seq(7))
+          assertScanReadsV(s"select id, try_variant_get(v, '$$.a', 'bigint') 
from $tableName",
+            pushIntoScan, pushedPaths = 1, leg)
 
           // A MERGE INTO that assigns ts alone writes a partial log block
           // (hoodie.spark.sql.merge.into.partial.updates defaults to true on 
MOR), so the read
@@ -682,6 +698,8 @@ class TestVariantShreddingMixedLayouts extends 
HoodieSparkSqlTestBase with Varia
           val widePaths = ('a' to 'h').map(c => s"try_variant_get(v, '$$.$c', 
'bigint')").mkString(", ")
           checkAnswer(s"select $widePaths from $tableName where id = 4")(
             Seq(4L, null, null, null, null, null, null, null))
+          // The eight paths are what the scan was asked for, not what the row 
happens to hold.
+          assertScanReadsV(s"select $widePaths from $tableName where id = 4", 
pushIntoScan, pushedPaths = 8, leg)
         }
       }
     }
diff --git 
a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/VariantShreddingTestSupport.scala
 
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/VariantShreddingTestSupport.scala
index 3e141478b488..8f1181093fa7 100644
--- 
a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/VariantShreddingTestSupport.scala
+++ 
b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/VariantShreddingTestSupport.scala
@@ -644,14 +644,33 @@ trait VariantShreddingTestSupport { self: 
HoodieSparkSqlTestBase =>
    * Whether the physical plan of `sql` reads a variant through a 
PushVariantIntoScan projection
    * struct: the file scan's required schema carries the projected struct at 
some struct path.
    * Pins that the rule actually fired for a true arm, so it cannot silently 
become a copy of the
-   * arm with the rule off. `sparkPlan` rather than `executedPlan`: under AQE 
the latter is a
-   * placeholder whose children only exist once the query runs.
+   * arm with the rule off.
    */
-  protected def variantProjectionPushedIntoScan(sql: String): Boolean = {
+  protected def variantProjectionPushedIntoScan(sql: String): Boolean =
+    fileScansOf(sql).exists(_.requiredSchema.fields.exists(f =>
+      SparkAdapterSupport.sparkAdapter.containsVariantProjection(f.dataType)))
+
+  /**
+   * The ReadSchema of the one file scan in the physical plan of `sql`, as its 
explain line prints
+   * it (the catalog string of the scan's required schema). A variant that 
PushVariantIntoScan
+   * rewrote shows there as a projection struct with one ordinal-named member 
per pushed path,
+   * `v:struct<0:bigint>`; one that reaches the reader whole shows as 
`v:variant`. Where
+   * [[variantProjectionPushedIntoScan]] says whether the rule fired, this 
pins what it pushed.
+   */
+  protected def scanReadSchema(sql: String): String = {
+    val scans = fileScansOf(sql)
+    assert(scans.size == 1, s"expected a single file scan in the plan of: 
$sql")
+    scans.head.requiredSchema.catalogString
+  }
+
+  /**
+   * The file scans in the physical plan of `sql`. `sparkPlan` rather than 
`executedPlan`: under
+   * AQE the latter is a placeholder whose children only exist once the query 
runs.
+   */
+  private def fileScansOf(sql: String): Seq[FileSourceScanExec] = {
     val scans = spark.sql(sql).queryExecution.sparkPlan.collect { case scan: 
FileSourceScanExec => scan }
     assert(scans.nonEmpty, s"expected a file scan in the plan of: $sql")
-    scans.exists(_.requiredSchema.fields.exists(f =>
-      SparkAdapterSupport.sparkAdapter.containsVariantProjection(f.dataType)))
+    scans
   }
 
   // 
---------------------------------------------------------------------------------------------

Reply via email to