This is an automated email from the ASF dual-hosted git repository.

yihua pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hudi-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 66ab307b docs: correct read config scoping and MOR streaming, document 
reader v2 knobs (#709)
66ab307b is described below

commit 66ab307b5b64d4ec3e3a48191fda84c36ec856b2
Author: Y Ethan Guo <[email protected]>
AuthorDate: Thu Sep 3 15:09:28 2026 -0700

    docs: correct read config scoping and MOR streaming, document reader v2 
knobs (#709)
---
 README.md           | 43 +++++++++++++++++++++++++++++++++++++++----
 docs/reader-spec.md | 11 ++++++-----
 2 files changed, 45 insertions(+), 9 deletions(-)

diff --git a/README.md b/README.md
index aab198cd..e5a3ca6c 100644
--- a/README.md
+++ b/README.md
@@ -56,6 +56,13 @@ Hudi integration in the data ecosystems for a diverse range 
of users and project
 [crates]: https://crates.io/crates/hudi
 [crates-badge]: 
https://img.shields.io/crates/d/hudi?style=flat-square&color=163669
 
+The `hudi` crate carries two features: `datafusion` (off by default, see
+[Apache DataFusion](#apache-datafusion)) and `spill-rocksdb` (on by default), 
the merge map's
+on-disk tier, which a merge-on-read merge spills to when a file group's log 
records exceed
+`hoodie.memory.merge.max.size`. RocksDB is built from source with `bindgen`, 
so it needs `libclang`
+and a C++ toolchain; `default-features = false` drops it, and a merge that 
would have spilled then
+fails instead.
+
 ## Usage Examples
 
 > [!NOTE]
@@ -331,7 +338,7 @@ All read APIs accept a `ReadOptions` (Rust) / 
`HudiReadOptions` (Python) value.
 - `batch_size` (`with_batch_size`) — rows per batch (streaming only; eager 
reads return one batch per file slice).
 - `as_of_timestamp` (`with_as_of_timestamp`) — snapshot/time-travel timestamp 
(defaults to latest commit).
 - `start_timestamp` / `end_timestamp` (`with_start_timestamp` / 
`with_end_timestamp`) — incremental range (defaults to earliest…latest).
-- `hudi_options` — per-read Hudi configs (e.g. 
`hoodie.read.use.read_optimized.mode`). Read configs are not stored in the 
table; they flow exclusively through `ReadOptions`.
+- `hudi_options` — Hudi configs for this read (e.g. 
`hoodie.read.use.read_optimized.mode`). A config that selects *which* read to 
perform — `hoodie.read.query.type` and the as-of/start/end timestamps — is 
per-read only and is dropped when set on the table. The rest describe *how* to 
read: set them on the table and override them here. See [Read 
configs](#read-configs).
 
 | Stage           | API                                                        
      | Description                                                             
                                 |
 
|-----------------|------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
@@ -340,6 +347,34 @@ All read APIs accept a `ReadOptions` (Rust) / 
`HudiReadOptions` (Python) value.
 | Query execution | `create_file_group_reader_with_options(read_options, 
extra_storage_overrides)` | Create a file group reader with the table's 
configs. Both args are optional. Timestamps are resolved automatically (e.g. 
`AsOfTimestamp` → `EndTimestamp`), so callers can pass the same options used 
for `get_file_slices`. |
 |                 | `read(options)` / `read_stream(options)`                   
      | Record-read APIs. Dispatch on `options.query_type`. `read_stream` 
errors on `Incremental` for now. Per-slice streaming lives on 
`FileGroupReader`. |
 
+### Read configs
+
+Read configs reach a read either through `ReadOptions` / `HudiReadOptions` or, 
for those scoped
+`table or read`, through the table (`TableBuilder`, `hoodie.properties`, 
`hudi-defaults.conf`), where
+a per-read value wins. A `per read` config set on the table is dropped: baked 
in there, it would
+silently redirect every later read.
+
+| Config                                                          | Default    
   | Scope         | Notes                                                      
                                                              |
+|-----------------------------------------------------------------|---------------|---------------|--------------------------------------------------------------------------------------------------------------------------|
+| `hoodie.read.query.type`                                        | `snapshot` 
   | per read      | `snapshot` or `incremental`.                               
                                                              |
+| `hoodie.read.as.of.timestamp`                                   | latest 
commit | per read      | Snapshot time-travel point.                            
                                                                  |
+| `hoodie.read.start.timestamp` / `hoodie.read.end.timestamp`     | earliest / 
latest | per read  | Incremental window, half-open `(start, end]`.              
                                                              |
+| `hoodie.read.file.group.reader.version`                         | `2`        
   | table or read | Which file group reader merges a slice. Version 2 is the 
default; a read it cannot serve falls back to version 1.        |
+| `hoodie.read.use.read_optimized.mode`                           | `false`    
   | table or read | Read base files only, skipping the log files, on MOR 
tables.                                                             |
+| `hoodie.read.stream.batch_size`                                 | `1024`     
   | table or read | Rows per batch for streaming reads.                        
                                                              |
+| `hoodie.read.file.slice.read.concurrency`                       | `4`        
   | table or read | File slices read concurrently.                             
                                                              |
+| `hoodie.read.scan.max.memory.size`                              | unset      
   | table or read | Total bytes a whole scan may use for concurrent slice 
reads; when set, the concurrency is derived from it. Reaching the limit lowers 
throughput, it never fails the read. |
+| `hoodie.read.input.partitions`                                  | `0`        
   | table or read | How many partitions the DataFusion table provider buckets 
the file slices into. `0` defers to DataFusion's `target_partitions`. |
+| `hoodie.merge.use.record.positions`                             | `false`    
   | table or read | Match a log record to the base row it updates by position 
rather than by record key. Honored by reader version 2 only; a log block 
written without positions is merged by key regardless. |
+
+A table whose `hoodie.record.merge.mode` is `CUSTOM` needs a merger for its 
payload class. When
+neither reader has one, the read fails rather than returning wrong rows; set
+`hoodie.read.file.group.reader.version=1` to read it the way it was read 
before, unless its base
+files are HFile, which version 1 cannot read.
+
+Base files are read from Parquet, Lance 
(`hoodie.table.base.file.format=lance`), and HFile (metadata
+tables only).
+
 ### File Group API
 
 Create a Hudi file group reader instance using its constructor or the Hudi 
table API `create_file_group_reader_with_options()`.
@@ -348,13 +383,13 @@ Create a Hudi file group reader instance using its 
constructor or the Hudi table
 
|-----------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 | Query execution | `read_file_slice()`                     | Read records 
from a given file slice; based on the configs, read records from only base 
file, or from base file and log files, and merge records based on the 
configured strategy. |
 |                 | `read_file_slice_from_paths()`          | Read records 
from an explicit base file path and a list of log file paths. Pass an empty log 
path list to read just the base file.                                           
      |
-|                 | `read_file_slice_stream()`              | Streaming 
version of `read_file_slice()`. Yields true streaming batches when the slice is 
base-file-only or read-optimized; for MOR slices with log files, falls back to 
a single merged batch. |
+|                 | `read_file_slice_stream()`              | Streaming 
version of `read_file_slice()`. A base-file-only or read-optimized slice 
streams straight from the base file. A MOR slice with log files streams merged 
chunks under file group reader version 2 (the default), and collapses to a 
single merged batch under version 1, whose merge has no incremental form. |
 |                 | `read_file_slice_from_paths_stream()`   | Streaming 
version of `read_file_slice_from_paths()`.                                      
                                                                                
         |
 
 
 ### Apache DataFusion
 
-Enabling the `hudi` crate with `datafusion` feature will provide a 
[DataFusion](https://datafusion.apache.org/) 
+Enabling the `hudi` crate with `datafusion` feature will provide a 
[DataFusion](https://datafusion.apache.org/)
 extension to query Hudi tables.
 
 <details>
@@ -408,7 +443,7 @@ table = HudiDataFusionDataSource(
     "/tmp/trips_table", [("hoodie.read.input.partitions", "5")]
 )
 ctx = SessionContext()
-ctx.register_table_provider("trips", table)
+ctx.register_table("trips", table)
 ctx.sql("SELECT max(fare), city from trips group by city order by 1 
desc").show()
 ```
 
diff --git a/docs/reader-spec.md b/docs/reader-spec.md
index 07ace4ac..54405f12 100644
--- a/docs/reader-spec.md
+++ b/docs/reader-spec.md
@@ -79,7 +79,7 @@ Which knobs each API consumes:
 Notes:
 
 - `read_stream` errors with `Unsupported` for `query_type = Incremental` — 
incremental streaming is not yet implemented.
-- The `hudi_options` bag is a per-read override layer — set arbitrary 
`hoodie.read.*` configs (e.g. `hoodie.read.use.read_optimized.mode = true`) for 
this single read. Read configs (`hoodie.read.*`) are not stored in the `Table` 
instance; they flow exclusively through `ReadOptions`.
+- The `hudi_options` bag is a per-read override layer — set arbitrary 
`hoodie.read.*` configs (e.g. `hoodie.read.use.read_optimized.mode = true`) for 
this single read. Each read config declares a scope. A `ReadOnly` one — 
`hoodie.read.query.type` and the as-of/start/end timestamps — selects *which* 
read to perform, so it flows exclusively through `ReadOptions` and is dropped 
when set on the table. A `TableOrRead` one describes *how* to read, so it may 
be set on the table and overridden h [...]
 - Per-slice reads are exposed only by `FileGroupReader`. The `Table` type owns 
logical reads (snapshot, incremental); per-slice reads are physical and belong 
at the file-group layer. To read one slice with table-level configs, build a 
`FileGroupReader` via `Table::create_file_group_reader_with_options` and call 
its per-slice methods. The method resolves timestamps automatically (e.g. 
`AsOfTimestamp` → `EndTimestamp`), so callers can pass the same `ReadOptions` 
used for `get_file_slices`.
 - For parallel reads, call `get_file_slices(...)` and bucket the result with 
`hudi::util::collection::split_into_chunks` or your engine's preferred 
partitioning policy.
 
@@ -102,6 +102,7 @@ The `field` may be any column. Filters drive three things:
 
 - **Partition pruning** when the field is a partition column. Always applied.
 - **File-level stats pruning** when the field is a data column with min/max 
stats in the metadata table. Snapshot/time-travel only — incremental file 
planning does not stats-prune.
+- **Row-group pruning** inside a base file, from the parquet footer's own 
statistics. Conservative: a row group that cannot match is never fetched, and 
one that might match is kept.
 - **Row-level mask** applied to every returned batch. This is the 
authoritative filter; pruning is best-effort.
 
 Values are strings; they are cast to the target column's Arrow type at filter 
time. Unparseable values (e.g. `"abc"` against `Int64`) error.
@@ -176,7 +177,7 @@ let table = 
HudiTableBuilder::from_base_uri("/tmp/trips_table")
     .await?;
 ```
 
-Available pairs: `with_hudi_option` / `with_hudi_options`, 
`with_storage_option` / `with_storage_options`, `with_option` / `with_options` 
(the generic forms route by key prefix). Read configs (`hoodie.read.*`) passed 
at table construction are silently dropped — they belong in `ReadOptions` 
per-call.
+Available pairs: `with_hudi_option` / `with_hudi_options`, 
`with_storage_option` / `with_storage_options`, `with_option` / `with_options` 
(the generic forms route by key prefix). A read config passed at table 
construction is kept when its scope is `TableOrRead` 
(`hoodie.read.file.group.reader.version`, 
`hoodie.read.use.read_optimized.mode`, `hoodie.read.stream.batch_size`, 
`hoodie.read.file.slice.read.concurrency`, `hoodie.read.scan.max.memory.size`, 
`hoodie.read.input.partitions`, `hood [...]
 
 ### Filter, Timeline, FileSlice
 
@@ -214,7 +215,7 @@ table = (
 )
 ```
 
-`with_hudi_option` and `with_option` accept a string key or a `HudiReadConfig` 
/ `HudiTableConfig` enum member. The bulk variants (`with_hudi_options`, 
`with_options`) currently accept dicts of string keys. Read configs 
(`hoodie.read.*`) are silently dropped at table construction — pass them via 
`HudiReadOptions` per-call instead.
+`with_hudi_option` and `with_option` accept a string key or a `HudiReadConfig` 
/ `HudiTableConfig` enum member. The bulk variants (`with_hudi_options`, 
`with_options`) currently accept dicts of string keys. Read configs scoped 
`TableOrRead` are kept at table construction and overridden per read; the 
`ReadOnly` ones (`hoodie.read.query.type` and the as-of/start/end timestamps) 
are dropped there — pass them via `HudiReadOptions` per-call instead. See 
[§2](#2-readoptions).
 
 ### `HudiTable`
 
@@ -304,7 +305,7 @@ The range is half-open: (`start_timestamp`, 
`end_timestamp`]. A record updated m
 
 ### MOR streaming fallback
 
-Streaming yields true streaming batches when the slice is base-file-only or 
`hoodie.read.use.read_optimized.mode = true`. For MOR slices with log files, 
the implementation collects-and-merges and yields the result as a single batch 
on the stream.
+Streaming yields true streaming batches when the slice is base-file-only or 
`hoodie.read.use.read_optimized.mode = true`. A MOR slice with log files is 
merged, and what the stream yields depends on which file group reader serves 
it: version 2, the default, merges a bounded chunk at a time and yields each 
chunk, while version 1 has no incremental form for its whole-batch sort and 
dedup, so it collects-and-merges and yields the result as a single batch.
 
 ### `batch_size` and `projection`
 
@@ -342,6 +343,6 @@ Reader APIs documented here are the supported public 
surface as of this release.
 
 For I/O cost estimation (on-disk base + log file sizes), use 
`FileSlice::total_size_bytes()` instead.
 
-`Table` / `HudiTable` only stores table configs (`HudiTableConfig`). Read 
configs (`HudiReadConfig`, keyed under `hoodie.read.*`) are filtered out during 
construction and flow exclusively through `ReadOptions` / `HudiReadOptions` 
per-call. `hudi_options()` on the table reflects the stored table configs, not 
any read configs the caller may have passed at construction.
+`Table` / `HudiTable` stores table configs (`HudiTableConfig`) and those read 
configs whose scope is `TableOrRead`; a per-read value overrides them. 
`ReadOnly` read configs — `hoodie.read.query.type` and the as-of/start/end 
timestamps — are filtered out during construction and flow exclusively through 
`ReadOptions` / `HudiReadOptions` per-call, so `hudi_options()` on the table 
never reflects them.
 
 Out of scope for this version: writer APIs, internal architecture (timeline 
parsing, log-record merging, metadata table layout), the full configuration key 
glossary (`HudiReadConfig` / `HudiTableConfig` members), and the DataFusion and 
C++ bindings — separate spec follow-ups.

Reply via email to