SteNicholas opened a new pull request, #448:
URL: https://github.com/apache/paimon-cpp/pull/448

   <!-- PR titles must follow Conventional Commits: <type>(<optional-scope>): 
<description> -->
   
   ### Purpose
   
   <!-- Linking this pull request to the issue -->
   Linked issue: close #403
   
   <!-- What is the purpose of the change -->
   Deletion vectors were applied only when rows were read, so a full-text 
search on a data-evolution
   table with `deletion-vectors.enabled` still ranked deleted rows. They could 
take every top-k slot,
   and the read then dropped them: a search with `limit` k returned fewer than 
k rows, or none, even
   when enough live rows matched.
   
   The fix aligns with Java `GlobalIndexLiveRowFilter` and 
`DataEvolutionFullTextRead#includeRowIds`:
   the rows live in the snapshot the index is read from are computed once and 
intersected into the
   rows each index shard ranks, while BM25 statistics still come from the whole 
index.
   
   - **`GlobalIndexLiveRowFilter::LiveRows`**: as Java 
`GlobalIndexLiveRowFilter#liveRows` does, reads
     the data files of the scanned partitions in the pinned snapshot with 
`ScanMode::ALL`, unions
     their row id ranges, then subtracts the rows deleted by their deletion 
vectors. Every range is
     added before any deletion vector is subtracted, so a deleted row is not 
added back by another
     file covering the same rows, and only one deletion vector is held at a 
time. It returns nullopt,
     so every row counts as live, when deletion vectors or row tracking are 
disabled. A deletion
     vector that cannot be read is reported with its data file, snapshot and 
table.
   - **`DataFileMeta::NonNullRowIdRange`**: computes a file's row id range as 
Java
     `DataFileMeta#nonNullRowIdRange` does, and rejects a null or negative 
first row id, a file with
     no rows, or a range that would pass `INT64_MAX`, instead of overflowing. 
As in Java,
     `DataEvolutionSplitGenerator` takes its ranges from it too, which covers 
the split planning that
     live rows are read through.
   - **`LiveRowGlobalIndexReader`**: wraps each index shard. A full-text search 
ranks only the live
     rows of the shard, intersected with the include row ids if given, and is 
passed on unchanged
     when the whole shard is live. Predicate and vector searches are delegated 
unchanged.
   - **`LiveRowGlobalIndexReader::CreateUnion`**: loads live rows on the 
visiting thread before the
     search fans out to the shards, so a load that reads manifests on the 
shared executor never waits
     for it from one of its own threads. A search without a positive `limit`, 
or whose include row
     ids miss the rows every shard ranks, neither reads live rows nor fails on 
them.
   - **`GlobalIndexScanImpl`**: for tables with deletion vectors, wraps the 
shard readers of every
     index and loads live rows at most once per scan, on its first full-text 
search that ranks rows,
     pinned to the snapshot the scan reads the index from. A failed load is 
retried on the next
     search.
   - **`FileStoreScan::WithPartitionFilter`**: restricts the live-row scan to 
the partitions the
     global index scan reads.
   
   Vector search still ranks deleted rows, as before; Java filters it too, and 
the same live-row
   helper can be reused for it and for the table-level read in a follow-up.
   
   ### Tests
   
   <!-- List UT and IT cases to verify this change -->
   UT:
   
   - `src/paimon/core/global_index/live_row_global_index_reader_test.cpp`:
     `TestSearchUnchangedWhenEveryRowIsLive`, `TestSearchOnlyLiveRowsOfShard` 
(including a shard
     ending at `INT64_MAX`), `TestIncludeRowIdsClippedToShard`,
     `TestCreateUnionLoadsLiveRowsBeforeSearchingShards` (live rows loaded on 
the visiting thread with
     a one-thread executor), `TestCreateUnionRanksLiveRowsOfSearchRanges` 
(overlapping shards),
     `TestCacheLiveRows` (cached, retried after a failure, and nullopt when 
every row is live),
     `TestLiveRowsErrorOnlyFailsSearchesThatRankRows` (a failed load fails a 
search that ranks rows,
     but not one without a positive `limit` or whose include row ids miss every 
searched shard) and
     `TestOtherVisitsDoNotReadLiveRows`.
   - `src/paimon/core/io/data_file_meta_test.cpp`: `TestNonNullRowIdRange` (a 
range ending at
     `INT64_MAX`, one past it, no rows, a negative and a null first row id).
   - `src/paimon/core/table/source/split_generator_test.cpp`: 
`TestDataEvolutionRowIdRangeAtInt64Max`.
   
   IT:
   
   - `test/inte/data_evolution_table_test.cpp`: 
`DataEvolutionDvTableTest.TestGlobalIndexLiveRows`
     (live rows with and without a partition filter, in the snapshot before the 
deletion, and the
     error when a deletion vector cannot be read).
   - `test/inte/global_index_test.cpp`: 
`TestLuceneSearchSkipsRowsDeletedByDeletionVectors` and
     `TestFullTextSearchSkipsRowsDeletedByDeletionVectors` share 
`CheckSearchSkipsDeletedRows`: across
     two shards, live rows keep the scores they had before the deletion, every 
`limit` returns the
     top-k live rows, include row ids are intersected with the live rows, a 
scan created before the
     deletion still ranks every row, and reading the matched rows returns all 
of them.
   
   ### API and Format
   
   <!-- Does this change affect API in include dir or storage format or 
protocol -->
   No change in `include/`. For tables with deletion vectors and row tracking, 
full-text searches
   through readers created by `GlobalIndexScan` rank only live rows. 
Data-evolution scans now reject a
   data file whose row id range is invalid instead of overflowing. No storage 
format change.
   
   ### Documentation
   
   <!-- Does this change introduce a new feature -->
   No new feature. `docs/source/user_guide/global_index.rst` describes how 
deleted rows are excluded
   from full-text ranking and shows passing one snapshot id to both the index 
scan and the data scan.
   
   ### Generative AI tooling
   
   Generated-by: Claude Code 2.1.303 (Claude Opus 5.5)
   
   🤖 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