Skip to content

fix(variant): close the shredded-read gaps exposed by a mixed-layout test matrix - #19687

Open
voonhous wants to merge 19 commits into
apache:masterfrom
voonhous:test-variant-shredding-mixed-layouts
Open

fix(variant): close the shredded-read gaps exposed by a mixed-layout test matrix#19687
voonhous wants to merge 19 commits into
apache:masterfrom
voonhous:test-variant-shredding-mixed-layouts

Conversation

@voonhous

@voonhous voonhous commented Aug 20, 2026

Copy link
Copy Markdown
Member

Describe the issue this Pull Request addresses

Closes #19688. Part of #18937 (variant shredding feature completeness). Follow-up #19689 (nested shredding parity) is blocked on this PR and #18961.

Shredded-variant tests force ONE typed_value layout per table; per-file inference (#18961) will make MIXED layouts routine (different layouts per file, shredded bases under unshredded logs, typed and residual rows in one file). Auditing the paths that meet these mixes found six defects, several of them silent data loss. Full audit narrative in #19688.

Summary and Changelog

Fixes (one commit each, each pinned by a test that was red before the fix):

Fix Before After
fix(schema): shredded typed_value declared nullable, per the spec Avro write of any non-object row under an object shredding schema crashed (Null-value for required field: typed_value); also the crash #18961's Avro inference hits on post-sample rows Non-matching rows write into the residual; old files stay readable (detection is shape-based)
fix(hive): fail fast on a requested shredded variant column, at any nesting depth, on the file-group-reader path and the legacy MapredParquetInputFormat fall-through (fgr disabled, schema-on-read, bootstrap splits) Silent nulls (plain parquet-avro read projects {metadata, value}) Error naming the column; count(*), no-variant projections, unshredded files keep reading
fix(bootstrap): one-arg getRecordIterator requests the caller's schema Data file read at its own footer schema; reconstruction never engaged, typed_value silently dropped Mirrors the two-arg overload; reconstruction anchors
fix(clustering): reject unsortable sort columns (VARIANT/MAP/BLOB/VECTOR), case-insensitively, via a shared SortUtils.validateSortableColumns applied in the partitioner constructors AnalysisException/ClassCastException deep in the job; consistent-bucket clustering and plain bulk_insert sort columns bypassed the check entirely Up-front error naming the column on every entry point (procedure order, plan-strategy sort columns, bulk_insert sort columns)
fix(spark): schema-on-read guard, recursing into nested types, shared with the four per-version Legacy parquet file formats; empty projections (count(*)) keep working Silent nulls with pushVariantIntoScan=false (internal schema clips typed_value), on the fgr path and the legacy relations alike Error naming the column; real reconstruction is #18285
fix(spark4.0): guard in reorderVariantType Could drop typed_value from the requested schema Throws naming the column (defense in depth; Spark's converter usually rejects first)

Tests: new TestVariantShreddingMixedLayouts (15 tests) + VariantShreddingTestSupport trait extracted from TestVariantDataType (no behavior change there). Coverage matrix in #19688; dimensions: same-file typed-vs-residual placement, four layouts in one table across snapshot/time-travel/incremental/RO, three-layout MOR compaction under three service-time layouts, table version 9, ordering modes, heterogeneous clustering (row-writer + RDD, COW + MOR-with-logs), variant_get across mixed files, CDC, nested, evolution, rollback/savepoint. Plus a Spark 4.1-written mixed-layout fixture zip for the Flink/Spark 4.0 fail-fast pins.

Impact

  • Hive, Spark 4.0 and schema-on-read reads of shredded files now fail fast naming the column instead of returning silent nulls.
  • Newly written shredded files declare typed_value optional (spec-compliant, matching the row writer).
  • Clustering rejects unorderable sort columns up front. No config changes.

Risk Level: low

Off-default surfaces; every fix pinned red-to-green. Verified locally: variant suites green on spark4.1 + spark4.2 (scala-2.13), spark4.0 (incl. TestHoodieRowParquetWriteSupportVariant), flink2.1 ITTestVariantCrossEngineCompatibility 4/4, hudi-hadoop-mr module. Note: every spark4.0 CI lane is [CI-TRIM]-commented, so the Spark 4.0 fix and its tests are covered by the local -Dspark4.0 run only.

Documentation Update

None. No configs added or changed; guard errors carry their own remediation.

Contributor's checklist

  • Read through contributor's guide
  • Enough context is provided in the sections above
  • Adequate tests were added if applicable

@voonhous voonhous changed the title Test variant shredding mixed layouts fix(variant): close the shredded-read gaps exposed by a mixed-layout test matrix Aug 20, 2026

@hudi-agent hudi-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for working on this! This PR expands variant-shredding test coverage to mixed layouts and adds production hardening: fail-fast guards in the Hive reader, Spark 4.0 read support, and the schema-on-read path so shredded-variant files error loudly instead of silently returning nulls; a bootstrap-reader fix so reconstruction engages at the caller schema; a typed_value nullability fix per the shredding spec; and up-front rejection of clustering sort on ordering-less types. I traced each production change against its surrounding source and callers: the guards are correctly scoped to fire only on genuine shredded variants, the bootstrap change is consistent with the two-arg overload, and the schema change is backward-readable within this unreleased feature. No correctness issues found. A few style/readability suggestions in the inline comments. Please take a look, and this should be ready for a Hudi committer or PMC member to take it from here.
. A couple of minor naming and message-consistency nits below.

cc @yihua

@codecov-commenter

codecov-commenter commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 18.78788% with 134 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.80%. Comparing base (2c14ff2) to head (c5691f3).

Files with missing lines Patch % Lines
...asources/parquet/ParquetSchemaEvolutionUtils.scala 0.00% 52 Missing ⚠️
...g/apache/hudi/hadoop/HoodieParquetInputFormat.java 25.00% 26 Missing and 7 partials ⚠️
...in/java/org/apache/hudi/common/util/SortUtils.java 28.57% 11 Missing and 4 partials ⚠️
...udi/client/utils/SparkInternalSchemaConverter.java 0.00% 13 Missing ⚠️
...di/command/procedures/RunClusteringProcedure.scala 0.00% 4 Missing ⚠️
...va/org/apache/hudi/common/schema/HoodieSchema.java 40.00% 1 Missing and 2 partials ⚠️
...pache/hudi/hadoop/HoodieColumnProjectionUtils.java 0.00% 3 Missing ⚠️
...on/bulkinsert/RDDCustomColumnsSortPartitioner.java 0.00% 2 Missing ⚠️
...udi/core/io/storage/HoodieBootstrapFileReader.java 0.00% 2 Missing ⚠️
.../parquet/Spark3LegacyHoodieParquetFileFormat.scala 0.00% 2 Missing ⚠️
... and 5 more
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19687      +/-   ##
============================================
- Coverage     77.85%   77.80%   -0.06%     
- Complexity    33189    33198       +9     
============================================
  Files          2532     2532              
  Lines        140050   140208     +158     
  Branches      16861    16910      +49     
============================================
+ Hits         109040   109082      +42     
- Misses        23386    23487     +101     
- Partials       7624     7639      +15     
Components Coverage Δ
hudi-common 83.31% <28.57%> (-0.05%) ⬇️
hudi-client 82.84% <5.00%> (-0.05%) ⬇️
hudi-flink 85.80% <ø> (+0.06%) ⬆️
hudi-spark-datasource 72.09% <0.00%> (-0.20%) ⬇️
hudi-utilities 74.04% <ø> (ø)
hudi-cli 15.06% <ø> (ø)
hudi-hadoop 68.92% <37.28%> (-0.16%) ⬇️
hudi-sync 75.55% <ø> (ø)
hudi-io 79.85% <ø> (ø)
hudi-timeline-service 83.44% <ø> (ø)
hudi-cloud 64.33% <ø> (ø)
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
flink-integration-tests 49.13% <0.00%> (-0.01%) ⬇️
hadoop-mr-java-client 44.00% <35.22%> (+0.10%) ⬆️
integration-tests 13.61% <0.00%> (-0.02%) ⬇️
spark-client-hadoop-common 50.50% <2.83%> (-0.07%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...n/bulkinsert/JavaCustomColumnsSortPartitioner.java 91.66% <100.00%> (+0.75%) ⬆️
...parquet/Spark42LegacyHoodieParquetFileFormat.scala 70.16% <ø> (ø)
...un/strategy/MultipleSparkJobExecutionStrategy.java 95.23% <0.00%> (-0.92%) ⬇️
...stentBucketIndexBulkInsertPartitionerWithRows.java 88.67% <0.00%> (-1.71%) ⬇️
...sert/RDDConsistentBucketBulkInsertPartitioner.java 92.00% <0.00%> (-1.88%) ⬇️
...on/bulkinsert/RowCustomColumnsSortPartitioner.java 80.00% <0.00%> (-3.34%) ⬇️
...rg/apache/hudi/hadoop/HiveHoodieReaderContext.java 76.63% <91.66%> (+1.89%) ⬆️
...on/bulkinsert/RDDCustomColumnsSortPartitioner.java 90.90% <0.00%> (-9.10%) ⬇️
...udi/core/io/storage/HoodieBootstrapFileReader.java 24.24% <0.00%> (-3.89%) ⬇️
.../parquet/Spark3LegacyHoodieParquetFileFormat.scala 0.00% <0.00%> (ø)
... and 7 more

... and 7 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@voonhous

voonhous commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

Pushed d8ffd05 after a self-review round. Short version: the guards this PR added had bypasses, and a few of the new tests could not fail for the reason they exist. This commit closes both.

Production

Area Gap Fix
Schema-on-read guard Only in ParquetSchemaEvolutionUtils; the four *LegacyHoodieParquetFileFormat copies of the same merge block were unguarded, and count(*) on a shredded table threw (empty projection = unpruned query schema) Guard hoisted to a shared helper called from all five sites, recursive into nested types, skipped for empty projections
Hive guard Top-level columns only (the row writer shreds nested variants too), and bypassed whenever shouldUseFilegroupReader is false: fgr disabled, schema-on-read enabled, bootstrap splits Detection via the recursive toShreddedReadSchema; the legacy MapredParquetInputFormat fall-through gets its own fail-fast, gated so non-variant tables never pay a footer read
Sort-column validation Bypassed by consistent-bucket clustering, plain bulk_insert sort columns and the java client; case-sensitive, so sort.columns=V against column v slipped through; MAP missing, and "has no ordering" was wrong for BLOB/VECTOR on the row path One SortUtils.validateSortableColumns (case-insensitive, VARIANT/MAP/BLOB/VECTOR, message "Sorting by column ... is not supported") in the partitioner constructors, with the early strategy/procedure checks kept for a cleaner error
bot.yml The adapter suites are JUnit classes: wildcardSuites cannot discover them and the java UT lanes already run them, so the hunk was a no-op and its "never ran in CI" claim wrong Reverted

Tests

Change Why
Nested fail-fast legs (Hive + schema-on-read) and a vectorized-reader sweep on the nested row-writer test Nested paths are exactly what a top-level-only guard misses; #18605 history for the vectorized leg
BLOB/VECTOR/MAP rejection pinned in TestClusteringProcedure, no Spark 4.1 gate The old pin sat behind gteqSpark4_1, leaving the validation untested on the 3.5 lane
Incremental, RO and time-travel legs assert values; the default pushVariantIntoScan leg asserts a variant-related failure Count-only asserts and a bare intercept[Throwable] stay green even when v reads back all-null
Flink pin asserts "Shredded Variant is not supported in Flink" Any unrelated error mentioning the column used to green it
CDC layout-flip folded into TestVariantDataType's CDC test; ordering-modes test folded into the three-layout compaction test (its delete-block leg survives); unshredded clustering twin retired Near-duplicates; each unique leg was kept
Dead Inferred scaffolding pruned; createVariantTable and a chain-walking checkNestedExceptionContains(runnable) shared Unused members; the reflective inference seam itself stays for #18961

Deliberately left as follow-ups: the flink 1.18-2.0 copies of ParquetSplitReaderUtil carry no shredded guard (only 2.1 does, and only 2.1 is in CI), and the Trino reader context has the same silent-null exposure the Hive guard closes. PR description updated to match (including that the spark4.0 lanes are [CI-TRIM]-commented, so the 4.0 leg is local-run coverage only).

VariantShreddingTestSupport carries the parquet-footer inspectors that
TestVariantDataType grew (listDataParquetFiles, readParquetSchema,
getFieldAsGroup, assertVariantLayout, assertSingleFileGroup) plus the
new mixed-layout machinery: a per-file layout map split by base vs
native log, a parquet-hadoop row inspector that counts typed vs
residual rows inside one file, a shape-drift SQL generator, a
session-conf write-layout toggle (Unshredded / Forced / Inferred with a
forced stand-in until apache#18961 lands), and the table-service idioms.
TestVariantDataType mixes the trait in; no test behavior changes.
…e shredding spec

The shredding spec makes typed_value OPTIONAL: a row whose value does
not match the shredding schema (a scalar or array under an object
schema, a JSON null) leaves typed_value null and carries everything in
the value residual. createVariantShredded attached the typed_value
schema as-is, so an object shredding schema produced a REQUIRED record
and any non-object row crashed the Avro write path with parquet-avro's
'Null-value for required field: typed_value'. The Spark row writer
already writes typed_value optional, and Spark4VariantShreddingProvider
already unwraps nullable typed_value at every touch point and puts an
explicit null for non-matching rows - only the schema builder
disagreed.

typed_value is now wrapped nullable (idempotently), the Variant
accessor unwraps the union so consumers keep seeing the value type, and
the Avro-to-parquet conversion pin flips to 'optional ... typed_value'.
Existing files with a required typed_value stay readable: read-side
detection is shape-based and never inspects typed_value nullability.
…ing and read modes

Every shredded test so far forces ONE layout per table. This matrix
pins what production will produce once per-file inference (apache#18961)
lands: files with different typed_value layouts in one table, shredded
bases under unshredded logs and the reverse, and rows inside one file
that fell back to the residual.

- same-file mix: forced schema with conflicting / disjoint / non-object
  rows, physical typed-vs-residual counts pinned per the spec
- same-table mix: per-commit layouts; snapshot, time travel,
  incremental and read-optimized over all of them; small-file bin-pack
  re-deriving the layout from the incoming commit (inputs never
  consulted)
- MOR compaction: three-layout log split compacted three times under
  three layouts (asserting the base re-derives each time), table
  version 9 legacy blocks, event-time vs commit-time ordering with
  deletes
- clustering: heterogeneous inputs rewritten under a configured layout
  on both the row-writer and RDD paths, COW and MOR-with-logs
- reads: variant_get/try_variant_get across files where the path is
  typed, residual, conflicted or absent, with pushVariantIntoScan on
  and off; CDC images across layout changes
- nested variant forced-shredded by the row writer; add-column
  evolution followed by compaction and clustering; rollback and
  savepoint restore across layouts

Inference legs substitute a forced stand-in until apache#18961 lands, so the
matrix keeps its shape on every profile.
The package holds TestBaseSpark3AdapterVariantMethods and
TestBaseSpark4AdapterVariantMethods, which no lane's wildcard filter
matched, so they never ran in CI.
variant_shredded_mixed_cow.zip carries a forced-shredded file group
(with one per-field residual row) and an unshredded file group, for the
engines that cannot write shredded files themselves: the Spark 4.0,
Hive and Flink read-behavior pins.
Flink has no shredded-variant read support; reading the mixed-layout
fixture must fail with an error naming the variant column rather than
return nulls or a partial payload. Verified on the flink2.1 profile
(the shredded file group in the fixture carries no parquet VARIANT
annotation, matching what Hudi's writers produce today, so this pins
the shape-based guard, not the annotation-based one).
The Hive reader hands base files to a plain parquet-avro read at the
requested {metadata, value} projection, so a file whose variant group
carries typed_value came back with silent nulls: the typed rows keep
their payload in typed_value, which the projection drops. The footer is
already read for schema pruning, so detect the shredded shape there and
throw an error naming the column and the remediation instead.

Detection reuses VariantSchemaUtils.isShreddedVariantTarget: shape-based
on the footer schema, anchored on the requested column being a variant,
so plain user structs of the same shape are left alone. Top-level
columns only, matching the writers' shredding scope. Queries that do
not project the variant column (e.g. count(*)) and unshredded variant
files keep reading as before, pinned by the new tests. The legacy
non-file-group-reader path (hoodie.file.group.reader.enabled=false)
has no requested-schema anchor and stays unguarded.
…le read

The one-argument getRecordIterator overload (used by HoodieMergeHelper's
bootstrap branch) read the data file at its OWN footer schema. A
shredded variant column surfaces there as a plain {metadata, value,
typed_value} record with the logical type lost, so
HoodieVariantReconstruction never engaged and the later rewrite to the
writer schema silently dropped typed_value. Requesting the caller's
schema minus meta fields - exactly what the two-argument overload does -
anchors reconstruction on the requested variant. Pinned by a round-trip
test with the real provider: red at the footer schema, green now.
Sorting a clustering plan by a VARIANT (or BLOB/VECTOR) column used to
fail deep in the Spark job - an AnalysisException from the row
partitioner or a ClassCastException from the RDD one. The procedure's
order parameter and the execution strategy's configured sort columns
(hoodie.clustering.plan.strategy.sort.columns, covering the inline and
async paths) now fail up front with an error naming the column.
The internal schema models a variant as a two-field {metadata, value}
record (sentinel negative field ids), so the merged schema-on-read
request clips the file's typed_value away and typed rows read back with
a null value residual - silent data loss. Until reconstruction under
schema-on-read lands (apache#18285), detect the shredded footer shape under
an internal-schema read and throw an error naming the column. The check
anchors on the sentinel ids, which no real user field can carry.

Under the default PushVariantIntoScan rewrite the same read already
fails before this hook (the internal-schema pruning cannot resolve the
projection struct's synthetic children); the guard closes the
pushVariantIntoScan=false leg, where the read otherwise succeeded with
nulls. Known apache#18285 residue documented in the test: the schema-on-read
DDL also degrades the CATALOG schema's variant column to a plain
struct, so plain reads after the DDL fail in Spark before any Hudi
hook.
Spark 4.0's unshredded converter reads only [value, metadata]; the
reorder workaround (apache#18334) dropped typed_value from the requested
schema, so shredded files written by Spark 4.1+ (or the forced test
DDL) read back with a partial or null payload. Throw an error naming
the column instead; unshredded groups keep the reorder. Spark 4.0
cannot reconstruct shredded variants (no SPARK-54410), so failing
loudly is the only safe behavior.
- clustering sort on a variant column rejected via the procedure order
  parameter AND the configured plan-strategy sort columns
- schema-on-read of a shredded file fails with the guard error on the
  pushVariantIntoScan=false leg and stays loud on the default leg
- Spark 4.0 read-back leg of the shredded write test asserts the fail
  fast instead of skipping
- assertQueryFailsWith joins the shared trait: checkNestedExceptionContains
  never executes the query (spark.sql is lazy), so it cannot see
  execution-time failures like per-file read guards
…-fast leg

Empirically Spark 4.0 rejects the 3-field group during file-schema
conversion (INVALID_VARIANT_FROM_PARQUET.WRONG_NUM_FIELDS) before the
Hudi read-support guard runs on this path; the guard remains defense in
depth for requested schemas that bypass the converter. Either way the
read fails naming the variant, which is what the leg pins.
…ort-rejection hint

- bootstrapReaderReconstructsShreddedDataFileAtTheTableSchema ->
  ...UsingTableSchema
- the procedure's sort-column rejection now ends with the same
  actionable hint as the execution strategy's twin
Guards:
- schema-on-read: the guard now recurses into structs, array elements and
  map values (the row writer shreds nested variants), skips empty
  projections so count(*) keeps working, and is shared with the four
  per-version Legacy parquet file formats that carry the same merge block
- hive: detect shredded variants at any depth via toShreddedReadSchema,
  and repeat the fail-fast on the non-file-group-reader path (fgr
  disabled, schema-on-read enabled, bootstrap splits), which lands on
  Hive's plain parquet reader with no guard
- sort columns: one shared SortUtils.validateSortableColumns - case
  insensitive, rejects MAP alongside VARIANT/BLOB/VECTOR, applied in the
  partitioner constructors (custom-columns row/RDD/java and both
  consistent-bucket partitioners) so the consistent-bucket strategy and
  plain bulk_insert sort columns cannot bypass the check; the execution
  strategy and run_clustering keep their early copies for a cleaner error

Tests:
- nested-variant fail-fast legs for the hive and schema-on-read guards
- BLOB/VECTOR/MAP rejection pinned in TestClusteringProcedure, not gated
  on Spark 4.1, so the validation is covered on every lane
- incremental, read-optimized and time-travel legs assert values, not
  counts; the pushVariantIntoScan default leg asserts a variant-related
  failure instead of a bare intercept[Throwable]
- the nested row-writer test sweeps the vectorized reader (apache#18605)
- the Flink pin asserts the guard's own message
- the CDC layout-flip folded into TestVariantDataType's CDC test; the
  ordering-modes test folded into the three-layout compaction test (its
  delete-block leg); the unshredded clustering twin retired in favor of
  the mixed-layout clustering matrix
- dead Inferred scaffolding pruned (unused shapes and helpers); shared
  createVariantTable and checkNestedExceptionContains helpers

Reverted the bot.yml filter hunk: the org.apache.spark.sql.adapter tests
are JUnit classes the java UT lanes already run; wildcardSuites only
discovers scalatest suites, so the hunk was a no-op.
@voonhous
voonhous force-pushed the test-variant-shredding-mixed-layouts branch from 6bc3e16 to c5691f3 Compare August 21, 2026 11:33
@hudi-bot

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands @hudi-bot supports the following commands:
  • @hudi-bot run azure re-run the last Azure build

@hudi-agent hudi-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for working on this! This PR closes several shredded-variant read gaps across the Spark legacy parquet formats and the Hive/legacy-input-format paths — wiring in a shared schema-on-read guard, adding fail-fast checks for shredded variant groups, fixing bootstrap reconstruction, and rejecting unsortable clustering sort columns, with supporting tests. The main thing to double-check is the legacy HoodieParquetInputFormat guard, which detects shredding via the variant logical type that the footer-derived schema may no longer carry, so it might never fire. Please take a look at the inline comments, and this should be ready for a Hudi committer or PMC member to take it from here. A couple of small maintainability nits — a duplicated magic-string constant and a redundant split — otherwise the code is clean and well-commented.

if (fileSchema.getType() != HoodieSchemaType.RECORD) {
return;
}
HoodieSchema strippedSchema = VariantSchemaUtils.stripVariantShredding(fileSchema);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 stripVariantShredding only strips nodes typed VARIANT (via stripVariantShreddingAt -> case VARIANT -> isShredded()), but the readSchema here goes through AvroSchemaConverterWithTimestampNTZ.convert(MessageType), which doesn't recognize the parquet VariantLogicalTypeAnnotation and falls back to plain-record conversion — so a shredded variant comes back as a plain {metadata, value, typed_value} record with no VARIANT node. stripVariantShredding never matches it, strippedSchema == fileSchema is always true, and this guard never throws, leaving the legacy Hive path silently nulling shredded columns. The FGR guard in HiveHoodieReaderContext avoids this by anchoring on the requested variant via toShreddedReadSchema/isShreddedVariantShape. Could you switch to shape-based detection here (the variant group already exposes typed_value in the footer schema)? @yihua

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

public static final String HOODIE_VALID_COMMITS_LIST = "hoodie.valid.commits.list";
// Mirrors Spark's VariantMetadata.METADATA_KEY (Spark 4.x only), referenced by literal
// because the class does not exist on Spark 3 classpaths.
private static final String SPARK_VARIANT_METADATA_KEY = "__VARIANT_METADATA_KEY";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 nit: SPARK_VARIANT_METADATA_KEY is private here, which forces ParquetSchemaEvolutionUtils.scala to define its own independent copy of the same string literal. If the key ever changes in Spark it would need updating in two places — could you move this to a shared (at least package-visible) constant so there's a single source of truth?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

throw new HoodieClusteringException("Order column not exist:" + col)
}
})
// The same validation the partitioners apply at execution time (see

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 nit: orderColumns.split(",") is computed twice — once on line 236 for the foreach existence check and again here for validateSortableColumns. Could you extract it to a val above the loop so the split is done once and both call sites share the same array?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL PR with lines of changes > 1000

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mixed shredded/unshredded variant layouts are untested, and several read paths silently drop shredded payloads

4 participants