wombatu-kun commented on code in PR #19558:
URL: https://github.com/apache/hudi/pull/19558#discussion_r3748118648


##########
hudi-client/hudi-spark-client/src/main/scala/org/apache/hudi/SparkFileFormatInternalRowReaderContext.scala:
##########
@@ -159,7 +159,31 @@ class 
SparkFileFormatInternalRowReaderContext(baseFileReader: SparkColumnarFileR
       structType
     }
 
-    val (readSchema, readFilters) = 
getSchemaAndFiltersForRead(parquetReadStructType, hasRowIndexField)
+    // Internal reads have no catalyst plan, so nothing rewrites VariantType 
fields the way
+    // PushVariantIntoScan does for user queries. Requesting native 
VariantType against a
+    // SHREDDED parquet base file clips the file group to {metadata, value} 
and reads
+    // value=null; write-side callers (compaction, clustering, merge) would 
then persist the
+    // nulls, silently losing the variant data (#19556). Request the 
full-variant projection
+    // shape instead and restore native VariantType after the scan. 
User-facing reads pass
+    // sparkRequiredSchema and are overlaid above; Spark < 4.1 has no shredded 
read support
+    // and the adapter returns None (shredded files cannot be written there 
either).

Review Comment:
   Spark4_0Adapter inherits generateVariantWriteShreddingSchema and 
createShreddedVariantWriter from BaseSpark4Adapter with no version gate, so 
Spark 4.0 does write shredded files - it is the read side that is missing 
there. Worth rewording the parenthetical.



##########
hudi-client/hudi-spark-client/src/main/scala/org/apache/hudi/SparkFileFormatInternalRowReaderContext.scala:
##########
@@ -159,7 +159,31 @@ class 
SparkFileFormatInternalRowReaderContext(baseFileReader: SparkColumnarFileR
       structType
     }
 
-    val (readSchema, readFilters) = 
getSchemaAndFiltersForRead(parquetReadStructType, hasRowIndexField)
+    // Internal reads have no catalyst plan, so nothing rewrites VariantType 
fields the way
+    // PushVariantIntoScan does for user queries. Requesting native 
VariantType against a
+    // SHREDDED parquet base file clips the file group to {metadata, value} 
and reads
+    // value=null; write-side callers (compaction, clustering, merge) would 
then persist the
+    // nulls, silently losing the variant data (#19556). Request the 
full-variant projection
+    // shape instead and restore native VariantType after the scan. 
User-facing reads pass
+    // sparkRequiredSchema and are overlaid above; Spark < 4.1 has no shredded 
read support
+    // and the adapter returns None (shredded files cannot be written there 
either).
+    val isParquetBaseFile = !isInlineLog && !FSUtils.isLogFile(filePath) &&
+      HoodieFileFormat.fromFileExtension(filePath.getFileExtension) == 
HoodieFileFormat.PARQUET
+    val (readStructTypeForScan, variantOrdinals) =
+      if (sparkRequiredSchema.isEmpty && isParquetBaseFile) {

Review Comment:
   HoodieMergeOnReadRDDV2 and CDCFileGroupIterator build this context without 
sparkRequiredSchema, so MOR incremental, streaming and CDC queries also take 
this rewrite. Is that intended - the impact section reads as internal-only?



##########
hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/TestVariantDataType.scala:
##########
@@ -253,6 +253,201 @@ class TestVariantDataType extends HoodieSparkSqlTestBase {
     })
   }
 
+  test("Test COW clustering preserves VARIANT values") {

Review Comment:
   Clustering is covered, but the compaction leg is not: the existing MOR 
compaction test compacts a log-only slice, so no test reads a shredded parquet 
base file through the internal reader. Worth a second compaction round after a 
base file exists, or a note that it is follow-up.



##########
hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/spark/sql/hudi/dml/schema/TestVariantDataType.scala:
##########
@@ -253,6 +253,201 @@ class TestVariantDataType extends HoodieSparkSqlTestBase {
     })
   }
 
+  test("Test COW clustering preserves VARIANT values") {
+    // Same Spark 4.1 gate as the compaction test above: clustering reads the 
shredded
+    // base files back through the native reader, which rejects the 3-field 
shredded
+    // layout before SPARK-54410 (Spark 4.1+).
+    assume(HoodieSparkUtils.gteqSpark4_1, "Shredded variant base-file read 
requires Spark 4.1 or higher")
+
+    withRecordType()(withTempDir { tmp =>
+      val tableName = generateTableName
+      val tablePath = tmp.getCanonicalPath
+      // Clustering rewrites ALL rows of the clustered file groups through the 
internal
+      // write-side reader context (SparkReaderContextFactory ->
+      // SparkFileFormatInternalRowReaderContext), the stack whose blob 
handling silently
+      // lost bytes in #19232. Nothing pinned its VARIANT behavior: this is 
the first
+      // clustering coverage for the type. Shredding is forced so the rewrite 
reads and
+      // rewrites the shredded layout, the default in production.
+      spark.sql(
+        s"""
+           |create table $tableName (
+           |  id int,
+           |  v variant,
+           |  ts long
+           |) using hudi
+           | location '$tablePath'
+           | tblproperties (
+           |  primaryKey = 'id',
+           |  type = 'cow',
+           |  preCombineField = 'ts',
+           |  hoodie.parquet.variant.write.shredding.enabled = 'true',
+           |  hoodie.parquet.variant.force.shredding.schema.for.test = 'key 
string',

Review Comment:
   Nothing here asserts the base files actually carry typed_value, so if the 
forced shredding schema ever stops taking effect this test silently degrades 
into the unshredded twin below and still passes. Worth adding the same 
listDataParquetFiles/getFieldAsGroup typed_value check the new bulk_insert test 
already does.



-- 
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]

Reply via email to