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

   Stacked on #673 — only the top commit is new here.
   
   ### The problem
   
   #673 registered position-based merge as a *silent* gap: a read that asks to
   merge by position is merged by key instead, succeeds, and returns the same 
rows
   in every case except a file group holding duplicate keys — and there, 
different
   ones.
   
   Almost everything needed to close it was already ported: the position buffer,
   the `RECORD_POSITIONS` decoder, the loader's buffer selection. What was 
missing
   is that nothing produced the row-position column they all read. Enabling the
   path failed with `non-nullable column '_tmp_metadata_row_index' absent from
   source batch`.
   
   Three other things each defeat the path on their own, so all four had to land
   together:
   
   - The config key did not exist; the resolver hardcoded `false`.
   - The adapter passed `None` for the base file's commit time. A log block's
     positions are only meaningful against the base file they were computed 
over,
     so the loader's gate never opened and the read fell back to key merge. **A
     test written without fixing this passes while proving nothing.**
   - The engine dropped the column rather than attaching it.
   
   ### The change
   
   **The base reader emits row positions.** 
`BaseFileReadOptions::row_index_column`
   asks for a synthetic `Int64` column, served by parquet-rs's virtual 
`RowNumber`
   column. They are physical file indices, so a pushed-down `RowFilter` leaves 
gaps
   rather than renumbering — which is what makes them comparable to positions a
   writer recorded over the unfiltered file.
   
   parquet-rs 57.3.0 has a wrinkle here: with an explicit `ProjectionMask` it
   reports the stream's schema without the virtual column while still emitting 
it
   in every batch, because `build()` cuts the projected fields at the number of
   parquet leaves and the virtual leaf sits past that. `schema_with_row_index`
   restores it from the builder's own schema.
   
   **The config is Hudi's own key**, `hoodie.merge.use.record.positions`, not a
   `hoodie.read.*` one — a table written with record positions should be read 
with
   the spelling the writer was given. Parsed strictly: a typo errors rather than
   quietly merging by key, which is the one outcome this setting exists to 
change.
   
   **The gap entry is narrowed, not deleted.** A read that asks for position 
merge
   and is *declined* — unparseable base-file name, non-parquet base — still 
warns.
   Hudi is handed the instant by the file slice and never has to decline.
   
   ### The fixture
   
   `table_duplicate_keys` is the one fixture that can tell the two strategies
   apart: one file group holding `k1` twice and `k2` three times, an upsert of 
`k1`
   and a delete of `k2`. Generator checked in beside the zip.
   
   Hudi's writer tags an incoming record with *every* base row its index 
matches,
   so those become two and three log records. Keyed by record key they collapse 
to
   one entry each and the buffer removes the entry once used, so only the 
*first*
   base row of each key is merged. Hudi returns **5 rows** merging by key (a 
stale
   `k1` row, two undeleted `k2` rows) and **3** merging by position. Both 
snapshots
   ship in the zip; the sweep picks by strategy.
   
   ### Testing
   
   - `every_fixture_matches_hudi_when_merging_by_record_position` — all 62 
fixtures
     match Hudi with positions on. 27 of them actually take the path.
   - `merging_by_position_and_by_key_disagree_on_duplicate_record_keys` — pins 
that
     the two golds differ, and that the reader lands on whichever it was asked 
for.
     Without this the position sweep would still pass if the path silently fell
     back.
   - Reader-level: positions survive a `RowFilter` as `[1, 3]` rather than `[0, 
1]`.
   - Engine-level: the base source carries the column for a position merge and 
not
     for a key merge, on both the eager and streaming paths.
   - Poison-tested twice — dropping the column, and forcing 
`use_record_position()`
     false. Both make the position tests fail.
   
   The existing reader returns 2 rows on the new fixture; it sorts and dedups 
whole
   batches by record key, so duplicates collapse. Added to the 
known-disagreement
   list with that cause.
   
   Full workspace suite green.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)


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