linliu-code opened a new pull request, #693:
URL: https://github.com/apache/hudi-rs/pull/693

   ## Description
   
   **Stacked on #692, which is stacked on #691 and #689. Review those first.** 
Until they merge the diff here shows their commits too; the delta is 
`4e1d4a3..HEAD`.
   
   The metadata table states `hoodie.record.merge.mode=CUSTOM`, and version two 
refuses CUSTOM, so a metadata file slice with log files cannot be read through 
it. Five places refused: the schema handler, the record merger factory, the 
record buffer loader, the merge-mode resolver (twice, and `MergeMode` had no 
way to represent CUSTOM at all since it is a two-variant enum), and the file 
group reader's own version gate. All five now resolve through one function, so 
they admit or refuse together.
   
   Selection is on the payload class, not the merge strategy id. The metadata 
table's id is the all-zeros value Java names 
`PAYLOAD_BASED_MERGE_STRATEGY_UUID`, which every payload-based custom table 
carries and which means only that the payload decides; selecting on it would 
claim tables this merger has no business merging. A CUSTOM table naming any 
other payload keeps erroring rather than being merged by an ordering rule it 
did not ask for, including one that names a real merger of its own under the 
pre-version-eight spelling of the strategy id.
   
   The merger mirrors `HoodieMetadataPayload.preCombine`. All six partition 
types share one flat Arrow schema with an integer type discriminator, so this 
is one merger dispatching on one column, and the six rules collapse to three 
shapes. Bloom filters, record index and secondary index take the newer record 
whole, which leaves the payload a zero-copy row reference. Files and 
all-partitions fold the `filesystemMetadata` map. Column statistics and 
partition statistics share a rule that widens the bounds and sums four 
counters. A folded record is the newer row with one column replaced, matching 
Java, which carries every other field from the newer payload.
   
   The vectorized merge kernel needed a change of its own. It decides 
base-versus-log conflicts inline through `pick_winner`, which models the two 
ordering modes and nothing else, so a mode it cannot express fell into the 
event-time branch, found no ordering value on either side, and took the log 
record whole. That would have dropped every file listed only in the base file, 
silently, on any slice with both a base file and log files. A mode 
`pick_winner` does not model now goes through the merger, as the row path 
already did; the two ordering modes keep the allocation-free fast path.
   
   Two limits, stated rather than left to be discovered. A statistics bound 
whose union branch differs between the two records is refused rather than 
reconciled: Java promotes across wrapper types using a `valueType` field the 
schemas here do not carry, so promotion could be written but not tested, and 
column statistics drive file pruning, where a bound reported under the wrong 
type prunes away files holding matching rows. A folded statistics record also 
holds its source block's union buffers alive, because slicing a dense union 
keeps its children whole; that is retention, not corruption, and it is worth 
measuring before optimising.
   
   Nothing routes the metadata table through version two yet: the version gate 
sends it to version one before config is read, and the adapter refuses it. This 
makes version two capable of the merge and changes no read. The layer that 
would use it is separate work.
   
   ## How are the changes test-covered
   
   - [ ] N/A
   - [x] Automated tests (unit and/or integration tests)
   - [ ] Manual tests
     - [ ] Details are described below
   
   Three reads of real metadata slices, each with an oracle that does not come 
from this code. A `files` slice, base file plus seven log files, is compared 
entry by entry against what `MetadataTableFileGroupReader` returns for the same 
slice, which folds through a separate implementation on decoded structs rather 
than Arrow; before this change the same read returns four keys holding one file 
entry each where the fold holds fourteen. A `partition_stats` slice, eight log 
files and no base file, is compared against the newest record per key, built by 
reading each log file alone in commit order. A `secondary_index` base file plus 
a log file that deletes its record is asserted to return nothing, with a guard 
that the base file held a record to begin with.
   
   Base-versus-log folding is covered separately, through `merge_base_batch` 
rather than through the metadata fixtures. The fixtures cannot cover it: their 
base file holds one record whose entries are a subset of what the log fold 
already produces, so they pass whether or not the base row is folded. That was 
found by mutating the kernel's call site rather than the fold, which is now 
part of the routine below.
   
   Every rule was mutation-checked, and each mutation fails the test that names 
that rule: the fold skipped, the fold's call site removed, min and max swapped, 
`min` for `max` on file size, a tombstone marking instead of cancelling, a 
tombstone losing to an older tombstone, the two statistics short-circuits 
removed, a null bound winning, the selection types folding, a delete not 
winning, and the version gate's narrowing reverted.
   
   Five rules have no real-data coverage and are covered by unit tests alone, 
which is a property of the fixture rather than an omission: `column_stats` 
record keys never repeat, since the key is per partition, file and column and 
each commit writes new files; every `partition_stats` record in the fixture is 
tight-bound, so that rule short-circuits on all ninety-nine of them; and no 
index key exists as live data on both sides, so both index slices merge by 
deleting. `bloom_filters` has no fixture at all. A fixture that produces those 
situations is separate work.
   
   Run locally on both legs: default features and `--no-default-features`, 
clippy clean on both with warnings denied, fmt clean, and `cargo check 
--workspace --all-targets --all-features` clean including the Python and C++ 
bindings. No CI run has happened: fork pull requests sit at `action_required` 
until a committer approves them, so all of the above is local only.
   


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