linliu-code opened a new pull request, #699:
URL: https://github.com/apache/hudi-rs/pull/699
## Description
**Stacked on #696, which is stacked on #695, #694, #693, #692 and #691.
Review those first.** Until they merge the diff here shows their commits too;
the delta is `9340f5b..HEAD`, six files.
File group reader version 2 resolved a slice's base file format from
`hoodie.table.base.file.format` alone, ignoring the file's path. A base file
whose format only its extension names, a Lance table that never sets the
config, would have been opened with the parquet reader and died on its footer.
No read ever hit that, and why not is the load-bearing part: the capability
gate in `version_two_unsupported_reason` already resolves per path, and refused
every non-parquet format before version 2 was reached. The gate refusing first
is what kept the resolver's config-only resolution unreachable. So the cost was
never a wrong value; it was that version 1 stays alive for Lance tables, which
is the last capability gap between the two readers. Serving Lance through
version 2 rather than dropping it is a product decision recorded on the ticket,
not one taken here.
That is also why the two halves below cannot be split: narrowing the gate
alone would make the misread reachable for the first time.
`resolve_reader_context` now takes the slice's base file path and makes the
same `resolve_from_configs(configs, Some(path))` call the gate makes, so the
gate that admits a slice and the reader that opens it cannot disagree about its
format. An empty path names no format, so a log-only slice passes `None`. The
gate narrows from "anything not parquet" to HFile, naming the format it
refuses. Lance passes into version 2. HFile still falls back: version 1 refuses
it by name, which is a better answer than reaching version 2's
`HFileBaseFileReader`, which is set up for the metadata table's records rather
than a regular table's. Whether version 2 should serve a regular table's HFile
base files is a separate question from this gate.
The gate is consulted regardless of `base_file_only`, so narrowing it moves
every Lance read shape onto version 2, not only the config-absent merge-on-read
one this is named for: copy-on-write eager, snapshot eager, snapshot streaming,
read-optimized, incremental, log-only slices, and reads that ask for
position-based merge (where version 2 declines positions and merges by key,
since `engine.rs` and `buffer/loader.rs` both gate positions on the base file
being parquet). Worth stating plainly because Lance ships no Spark gold
snapshot: all six Lance fixtures sit under `EXPECTED_WITHOUT_GOLD` in
`gold_parity_tests.rs`, so this reroute is validated by hand-written assertions
rather than by parity with Spark.
`metadata/table/v2_reader.rs` also passes the slice's path now. That is a
no-op rather than a behaviour change: every metadata table sets `HFILE` in
config, and config resolves first, so the same `HFileBaseFileReader` branch is
reached as before.
Two alternatives were rejected. Resolving inside
`engine.rs::base_file_reader()` from `input_split.base_file_path` needs no
signature change, but it puts a second format-resolution point behind the gate
and leaves `ReaderContext.base_file_format` holding a value the engine ignores;
two resolution points is the defect being fixed. Removing the gate outright
would admit a regular table's HFile base files, which is separate work, and
would make the misread reachable again if the resolution ever regressed.
## How are the changes test-covered
- [ ] N/A
- [x] Automated tests (unit and/or integration tests)
- [ ] Manual tests
- [ ] Details are described below
The test that pinned the old gate is updated rather than removed.
`test_version_two_unsupported_reason_non_parquet_base_file_returns_reason`
becomes `test_version_two_unsupported_reason_refuses_hfile_and_admits_lance`,
asserting that HFile is refused by name and that Lance and parquet are both
admitted.
A second gate test,
`test_version_two_admits_the_lance_fixtures_own_base_file`, uses the Lance
fixture's own loaded configs and a real `.lance` base file path, in both the
merge and base-file-only shapes. The first test uses a synthetic
`"part/f.lance"` and the minimal props fixture, so a gate reason that depended
on a table's configuration would slip past it: such a read would quietly fall
back to version 1, and a test comparing only rows would still pass. The path is
discovered by walking the fixture rather than hardcoded, since the base file
names carry a UUID and a commit instant.
On the resolver,
`resolves_base_file_format_from_the_path_when_the_table_names_none` covers
`.lance`, `.hfile`, `.parquet` and an unrecognised extension, and first asserts
that the fixture really names no format, so it cannot pass without the path
being consulted. `an_explicit_base_file_format_outranks_the_path` configures
`hfile` rather than `parquet`, because parquet is also the default and also the
fallback when path resolution finds nothing, so a parquet expectation would
hold even if the config were ignored entirely.
End to end,
`test_v9_lance_nonhivestyle_mor_read_resolves_format_from_path_per_reader_version`
strips `hoodie.table.base.file.format` from a Lance merge-on-read fixture, so
the extension is the only thing naming the format, and reads it under each
reader version on both the eager and the streaming path, cross-checking all
four shapes against version 1's eager read. A fixture that sets the config
cannot discriminate: it passes whether or not the path reaches the resolution.
The partition column is asserted on every row rather than merely projected,
because the table is non-hive-style, the value is not recoverable from the
directory name, and a null renders here as an empty string, so a reader that
null-filled it would pass every other assertion.
`test_v9_lance_nonhivestyle_mor_read_optimized_agrees_across_reader_versions`
covers the read-optimized shape, which reaches version 2 through the
base-file-only branch of the gate.
Each half was reverted in turn to confirm a named test fails. Reverting the
gate to "anything not parquet" fails both gate tests, with the second naming
the real fixture file it can no longer reach, while the five sibling
`version_two_unsupported_reason` tests stay green. Reverting the resolution to
config-only fails the resolver test and three read tests, all three with
`Invalid Parquet file. Corrupt footer` on a `.lance` base file. One of those
three,
`test_v9_lance_nonpartitioned_cow_read_uses_extension_fallback_without_format_config`,
predates this change and becomes discriminating for the resolver half now that
the gate admits Lance into version 2 at all.
The added work is at most three suffix comparisons per file-slice read, and
only when the table names no format, since `resolve_from_configs` returns the
configured value first. Measured in release over five million calls, three
rounds: 13.38ns to 15.03ns per call with the format unset, and 96.17ns to
95.93ns with it set, which is noise around zero. Nothing moved into a per-row
or per-batch path.
Run locally: 1389 lib tests pass with default features and 1369 with
`--no-default-features`, clippy is clean on both feature sets with warnings
denied, fmt is clean, and `cargo test --no-fail-fast --all-targets
--all-features --workspace` passes on every target. `cargo test -p hudi-cpp
--doc` fails with E0464 on `main` too, which is pre-existing and outside this
gate since CI uses `--all-targets`.
No CI run has happened on this PR or on any of #691 through #697: fork pull
requests sit at `action_required` until a committer approves the workflow, 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]