feat(trino): resolve merge-required columns from the table schema - #19288
Conversation
6f089ae to
f6a1bc0
Compare
f6a1bc0 to
74e1c97
Compare
Fixes apache#19249, including the scope update in issue comment 4937143911; relates to the base read path of apache#18837. The file-group reader can require merge columns the query projection does not carry: Hudi meta fields like _hoodie_commit_time, delete markers, record-key data columns, a custom merger's mandatory fields, or the whole table schema when a CUSTOM merger is not projection compatible. buildRequiredColumnHandles previously hand-synthesized a HIVE_STRING/VARCHAR handle behind a HOODIE_META_COLUMNS guard and threw NOT_SUPPORTED for anything else, and the base-file read silently returned projection-only records that made such mergers see nulls. Log side: - Every required-schema field carries its real Avro type from the table schema, so unresolved handles are typed directly from the field (NativeLogicalTypesAvroTypeBlockHandler + HiveTypeTranslator) via the new HudiUtil.toColumnHandle; the synthesis, guard and exception are gone. trino-hive-formats moves from runtime to compile scope. Base side: - HudiPageSourceProvider mirrors the file-group reader's decision: HudiUtil.resolveMergeModeAndStrategyId applies the same version-gated inference as hudi-common (merge mode below table version 9, strategy id below version 8), and when the resolved CUSTOM merger is not projection compatible (interface default; also every payload-based table, since the payload strategy resolves HoodieAvroRecordMerger), the read projection expands to every table-schema column via HudiUtil.appendMissingSchemaColumns. Matches Spark; the extra I/O is inherent to such mergers. - getMergeRequiredColumnHandles predicts every column FileGroupReaderSchemaHandler.getMandatoryFieldsForMerging can demand: ordering columns under the inferred (not raw) merge mode, _hoodie_is_deleted and _hoodie_operation unconditionally (dropped when not table columns), the custom delete key when both hoodie.payload.delete.field and .marker are set, and the record-key data columns when hoodie.populate.meta.fields is false. - The base read reuses the pre-built page source, so its projection MUST cover requiredSchema: HudiTrinoReaderContext now guards that invariant and fails with HUDI_SCHEMA_ERROR naming any missing columns instead of merging with nulls. MOR splits with log files but no base file fail with a clear NOT_SUPPORTED instead of a bare NoSuchElementException. Cleanups in the same area: - Replace SynthesizedColumnHandler with PrefilledColumnValues, a thin per-split adapter over trino-hive's HiveUtil.getPrefilledColumnValue. Fixes $partition rendering multi-key partitions in hash order, handles the hive null marker (backslash-N) in partition values, adds previously unsupported partition-key types, and deletes SynthesizedColumnStrategy plus the hand-rolled appendPartitionKey dispatch. - Remove dead code (HudiTrinoReaderContext.colToPosMap and dataHandles, HudiPageSource.readerContext); make fields private final. - remapColumnIndicesToPhysical throws a diagnosable TrinoException instead of an autoboxing NPE for columns absent from the parquet file schema, and lowercases with Locale.ROOT. Tests: - TestHudiNonProjectionCompatibleMerger + NonProjectionCompatibleRankMerger (no isProjectionCompatible/getMandatoryFieldsForMerging overrides) + initializer: narrow-projection MOR queries that never project merge_rank, laid out so one key's winning rank is on the log record and the other's on the base record; sum(value)=199 discriminates the correct merge from base-only (110) and newest-wins (103). - Unit coverage: toColumnHandle, appendMissingSchemaColumns, usesNonProjectionCompatibleMerger, resolveMergeModeAndStrategyId (table versions 6/8/9), merge-required column prediction, PrefilledColumnValues (incl. the $partition ordering fix), and the typed missing-column remap error (both remap tests). - Existing projection-compatible merger tests stay as fast-path regression coverage; stale MaxRankRecordMerger javadoc refreshed.
74e1c97 to
6569ea3
Compare
TestHudiCustomMerger's negative test was added on master while this change was in flight, pinning the old NOT_SUPPORTED rejection per the plan in issue comment 4937143911. Flip it to the positive assertions that plan called for: narrow-projection sum(value)=199 plus a full-row check under NonProjectionCompatibleTestRecordMerger, whose javadoc is refreshed to describe the full-schema path.
|
@wombatu-kun Can you please help to review this? I have a few more PRs to get the Trino integrations landed in a good shape. I'd like to get this track of work done by this week. Thank you! |
- soften requiresFullSchemaRead javadoc to the actual guarantee - remap missing columns one past the last field so the parquet reader null-fills them like the name-based path (was a typed hard failure) - reject CUSTOM merge mode without a persisted strategy id with an actionable HUDI_BAD_DATA error instead of an NPE in hudi-common - recover merge-required columns the metastore lacks from the resolved table schema on the merge path (appendMissingMergeRequiredColumns); no extra I/O, pinned by the file-operation-count tests - document the $file_modified_time zone change in PrefilledColumnValues - extract AbstractMergerHudiTablesInitializer; the three MoR fixture initializers keep only their deltas - add payload-only table version 6 fixture (RankBasedTestPayload) and E2E coverage in TestHudiNonProjectionCompatibleMerger
New EVENT_TIME_ORDERING MOR fixture (omitted_ordering_field_mor) whose metastore column list omits the ts its Avro schema carries, the shape hive sync leaves when a column is not synced. Event-time merging needs ts on both sides, so the row-level and narrow-projection sum(value)=199 cases only pass when the merge path recovers the column from the table schema; reverting the recovery line fails both with the missing-column guard error.
- recover a CUSTOM merger's own mandatory fields on the merge path too: appendMissingMergeRequiredColumns asks the same resolved merger the file-group reader will use (classloading only, no I/O), unit-tested with MaxRankRecordMerger and pinned end to end by a fixture whose metastore omits the merger-declared merge_rank column - widen the no-strategy-id guard wording to version 8 or later - scope the schema-gate javadoc claims to _hoodie_is_deleted and _hoodie_operation, the two fields the reader actually schema-gates, and split the metastore/schema halves across the functions that implement them - move the withPreCombineField constraint comment back into the write config builder chain - document that the composite initializer cannot host fixtures that keep their write client open - strip only the merge strategy id in the payload-only v6 fixture: the merge mode never reaches disk (its config is since-version 1.0.0, dropped by table creation itself)
…dColumns Mirror the two usesNonProjectionCompatibleMerger cases onto the append: CUSTOM without a strategy id and CUSTOM with an id no configured merger declares both fall back to the table-config-derived names, so removing either guard fails a unit test instead of resurfacing the NPE. The unresolvable case must not use the all-zeros uuid -- that is PAYLOAD_BASED_MERGE_STRATEGY_UUID, which always resolves; the first version of the test passed vacuously for exactly that reason until a mutation check caught it. Shared fixture helpers dedup the CUSTOM tests.
|
@wombatu-kun Can i request for another round of review please? For the comments addressed and if you have no further comments, can you please help to "resolve" them so they are collapsed? |
wombatu-kun
left a comment
There was a problem hiding this comment.
Verified the CUSTOM-branch guards are pinned: without the isNullOrEmpty half the null-id test does reach createValidRecordMerger's leading equals(), and the strategy id the unresolvable-merger test uses is neither the payload-based uuid nor one any configured merger declares, so neither test is vacuous. No further comments from me - all my threads are resolved.
Follow-up to the RFC-105 migration (#18837, merged); rebased on master.
Describe the issue this Pull Request addresses
Closes #19249
The file-group reader can require columns the query projection does not carry: meta columns like
_hoodie_commit_time, delete markers, or the full table schema when a CUSTOM merger is not projection compatible. The connector hand-built a VARCHAR handle for the meta-column case and threw NOT_SUPPORTED for everything else, so non-projection-compatible mergers could not be used at all.Summary and Changelog
HudiUtil.toColumnHandle) instead of being synthesized as VARCHAR. TheHOODIE_META_COLUMNSguard and the NOT_SUPPORTED error are gone.FileGroupReaderSchemaHandler.generateRequiredSchema, including the pre-v9 merge-config inference (a 0.x table with a custom payload class infers CUSTOM mode with no persisted strategy id)._hoodie_is_deleted,_hoodie_operation, the custom delete key, record-key fields whenhoodie.populate.meta.fields=false), and the reader context now fails loudly if the base projection ever misses a column the file-group reader demands, instead of merging with nulls.SynthesizedColumnHandleris replaced byPrefilledColumnValues, a thin adapter over trino-hive'sgetPrefilledColumnValue. This intentionally goes past the issue's "no behavioral change" acceptance:$partitionrenders keys in partition order instead of hash order, the\Nnull marker is handled, more partition-key types work, and$file_modified_timerenders in the JVM default zone as the Hive connector does instead of the previous pinned UTC (same instant, different rendered zone).hudi.parquet.use-column-names=false, a projected column absent from a base file (schema-on-write evolution never rewrites old files) now null-fills exactly like the name-based path, instead of failing hard (previously an autoboxing NPE). Column renames under full schema evolution (hoodie.schema.on.read.enable) stay out of scope on both paths: [FEATURE] hudi-trino: resolve parquet columns through the internal schema for full schema evolution (hoodie.schema.on.read.enable) #19381.hoodie.record.merge.strategy.id(possible at table version 8 or later) fails with an actionableHUDI_BAD_DATAerror instead of an NPE inside hudi-common's merger resolution.omit_metadata_fields=true) are recovered on the merge read path from the already-resolved table schema -- including columns a CUSTOM merger declares mandatory, by asking the same resolved merger -- at no extra I/O (pinned by the file-operation-count tests).New tests:
TestHudiNonProjectionCompatibleMergerruns narrow-projection MOR queries that only merge correctly if both the base and log side supply the never-projectedmerge_rankcolumn (one key wins on each side); unit coverage for the new helpers and for the pre-v9 inference at table versions 6, 8 and 9. Review rounds added: a payload-class-only table version 6 fixture read end to end, fixtures whose metastore omits a merge-required column (the ordering field, and a merger-declared column) pinning the schema recovery end to end, and a shared initializer base (AbstractMergerHudiTablesInitializer) replacing three near-identical fixture copies.Impact
Custom mergers no longer have to override
isProjectionCompatible()/getMandatoryFieldsForMerging(). Note that payload-class tables resolveHoodieAvroRecordMerger, which is not projection compatible, so such tables read the full table schema on MOR splits with log files -- same as Spark, and inherent to payload-based merging. Projection-compatible and base-file-only reads are unchanged.Risk Level
low -- confined to hudi-trino; the projection-compatible fast path keeps its existing test coverage.
Documentation Update
none
Contributor's checklist