JunRuiLee opened a new pull request, #479:
URL: https://github.com/apache/paimon-rust/pull/479

   ### Purpose
   
   Rust's `create_global_index` (and the lumina/vindex vector-index build 
builders) diverged from Java: each planned shards over the **full** data 
coverage and then errored on overlap with any existing index for the same 
field. As a result a global index was effectively build-once — a second build 
(e.g. after appending data) failed with `"Existing global index file '...' 
overlaps requested row range"`, and the only way to refresh was drop + full 
rebuild.
   
   Java's `create_global_index` is incremental by construction for **both** the 
sorted (btree) and the vector builders: it builds only the previously-unindexed 
row ranges (`gap = [0, nextRowId-1] − indexedRowRanges(index_type, field)`), 
appends new non-overlapping index files, and is a clean no-op when the gap is 
empty (`SortedGlobalIndexBuilder.incrementalScan()` and 
`GlobalIndexBuilderUtils.unindexedRowRanges` via `GenericIndexTopoBuilder`'s 
`autoIncremental`, both keyed on the same `indexedRowRanges` helper). This PR 
brings all three Rust build builders to that behavior.
   
   ### Brief change log
   
   - **Shared range primitives** (`table/source.rs`, 
`table/global_index_build_common.rs`):
     - `exclude_row_ranges(base, subtract)` — inclusive `RowRange` 
set-difference (`gap = coverage − indexed`), replacing a previously-untested 
private copy in `global_index_scanner.rs` so the read-side `unindexed_ranges` 
and the write-side build gap share one, now unit-tested, subtraction (handles 
the `i64::MAX` segment-exhaustion edge).
     - `indexed_row_ranges(table, manifest, index_type, field_id, 
extra_field_ids)` — reads the index manifest and returns coverage for one 
global-index identity, mirroring Java's single shared `indexedRowRanges`; used 
by all three builders.
   - **Incremental build** for `btree`, `lumina`, and `vindex` builders: 
`execute()` computes the indexed coverage for its own index type, subtracts it 
from the data coverage, clamps each coverage segment to the gap **before** 
shard-splitting (keeps every shard's row-id range contiguous even when the gap 
starts mid–grid cell), and returns `Ok(0)` when fully indexed. Build stays 
**strictly append-only** — no existing index file is read, rewritten, or merged.
   - **Overlap guards keyed on full identity**: the three duplicated builder 
`validate_existing_index_overlap` copies are consolidated into one shared 
guard, and both commit-layer guards 
(`TableCommit::validate_global_index_overlap` and 
`validate_added_global_index_overlap`) are narrowed, so overlap identity 
everywhere is `(index_type, index_field_id, same_extra_field_ids)` + range — 
matching the coverage key and Java's `GlobalIndexIdentifier`. 
Different-identity global indexes on the same field now coexist (e.g. a 
`lumina` and an `ivf-flat` index on the same embedding column); two files of 
the **same** identity overlapping are still rejected.
   - `exclude_row_ranges` is scoped `pub(crate)` (no external consumer).
   
   Not in scope: full-text (`FullTextSearchBuilder` is a search/read path with 
no build builder — nothing to make incremental); composite `extra_field_ids` 
index *build* (unimplemented in both Rust and Java, though the guards are now 
correct for it).
   
   ### Tests
   
   All added tests drive the real build path (write → commit → build → read 
back the index manifest); vindex/btree run fully in CI, lumina's native-lib 
build is `#[ignore]` so its incremental behavior is pinned by planner-level 
tests.
   
   - `exclude_row_ranges`: 10 unit tests 
(empty/prefix/mid-hole/full-cover/adjacent/multi-segment/`i64::MAX` boundary).
   - Per builder (btree, lumina, vindex): second-build-without-new-data is a 
no-op (returns 0, manifest unchanged); incremental build indexes only the 
appended gap and retains prior index files (vindex asserts this end-to-end); 
first build equals full coverage; planner-level exact-range tests for a 
mid-coverage hole split and an indexed-prefix suffix (both bounds pinned, none 
spans the hole, union == coverage − indexed).
   - Overlap identity: a build over a field with a pre-existing 
**different**-type index succeeds (no spurious overlap); commit guards allow 
different-type / different-extra overlaps and still reject same-identity 
overlaps; pre-existing same-type overlap-rejection tests unchanged.
   - Full suite: `cargo test -p paimon --lib` 1270 passed / 1 ignored; `cargo 
test -p paimon-datafusion --test procedures` 22 passed; `cargo fmt --all 
--check` and `cargo clippy -p paimon --all-targets -- -D warnings` clean.
   
   ### API and Format
   
   No storage-format change (same `GlobalIndexMeta` / `IndexFileMeta` layout, 
same `global-index.row-count-per-shard` shard unit). No public API signature 
change (`Table::new_*_index_build_builder` unchanged); the only behavioral 
change is that a repeat `create` now appends the gap instead of erroring. 
`exclude_row_ranges` is internal (`pub(crate)`).
   
   ### Documentation
   
   No doc/SQL changes required — this aligns existing `create` behavior with 
Java; no new option or syntax.
   


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