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

   <!-- PR titles must follow Conventional Commits: <type>(<optional-scope>): 
<description> -->
   
   ### Purpose
   
   <!-- Linking this pull request to the issue -->
   Linked issue: close #402
   
   <!-- What is the purpose of the change -->
   `UnionGlobalIndexReader::VisitFullTextSearch` ORed shard results, so a 
full-text search with
   `limit` k over multiple index shards could return up to shards × k rows, and 
it failed when two
   shards returned the same row id. `OffsetGlobalIndexReader` shifted global 
`include_row_ids` by the
   shard offset without clipping them, so row ids before a shard became 
negative local ids, row ids
   past it were kept, and a shard with no included row was still searched.
   
   This PR follows Java `DataEvolutionFullTextRead`, which asks each split for 
`limit` rows, combines
   them with `ScoredGlobalIndexResult#merge` and applies `#topK(limit)` last.
   
   - **`UnionGlobalIndexReader::VisitFullTextSearch`**: merges shard results, 
where a duplicate row id
     keeps its first score, and keeps the `limit` highest-scoring rows. Scores 
are compared as Java
     `Double#compare` does (via `FieldsComparator::CompareFloatingPoint`), and 
ties among merged rows
     are broken by ascending row id. A null search or a non-positive `limit` is 
rejected before any
     shard is searched.
   - **`OffsetGlobalIndexReader`**: takes the last row id of its shard and 
clips `include_row_ids` to
     the shard with `FullTextSearch::OffsetRange(offset, to)`, as Java
     `OffsetGlobalIndexReader(wrapped, offset, to)` does. A shard whose include 
set becomes empty is
     not searched.
   - **Overlapping index ranges**: as Java 
`DataEvolutionFullTextScan#chooseIndexRanges`,
     `GlobalIndexScanImpl` ranks a row covered by overlapping ranges of one 
index type only in the
     first of them in `(from, to)` order. Each `OffsetGlobalIndexReader` is 
given the last row ranked
     by earlier ranges (`skip_to`) and ranks only the rows after it, so the top 
`limit` rows of each
     shard include all of its rows that score above the global `limit`-th 
score. Passing the last
     skipped row instead of the first row to rank does not overflow when a 
range ends at `INT64_MAX`.
     A fully covered shard is not searched but still rejects a non-positive 
`limit`.
   
   As in Java, each shard's index engine decides which of its rows tied at its 
cutoff score it
   returns, so ascending row id order is guaranteed only among the rows the 
shards return.
   
   ### Tests
   
   <!-- List UT and IT cases to verify this change -->
   UT:
   
   - `src/paimon/common/global_index/union_global_index_reader_test.cpp`:
     `TestVisitFullTextSearchUnion`, 
`TestVisitFullTextSearchKeepsTopKAcrossReaders` (top-k and ties
     by row id), `TestVisitFullTextSearchDuplicateRowIdKeepsFirstScore`,
     `TestVisitFullTextSearchOrdersSpecialScoresAsJava` (NaN, infinity and 
signed zeros),
     `TestVisitFullTextSearchAllNullptr`, `TestVisitFullTextSearchErrors`.
   - `src/paimon/common/global_index/offset_global_index_reader_test.cpp`:
     `TestVisitFullTextSearchConvertsIncludeRowIds` (clipping to the shard, and 
an empty include set
     that is not searched), `TestVisitFullTextSearchRejectsNonPositiveLimit`,
     `TestVisitFullTextSearchSkipsRows`, 
`TestVisitFullTextSearchEndingAtLargestRowId`. The other
     tests pass the shard end.
   
   IT, `test/inte/global_index_test.cpp`:
   
   - `TestWriteCommitScanReadLuceneIndexWithPartition` and
     `TestWriteCommitScanReadFullTextIndexWithPartition`: global top-k for 
every `limit` across two
     shards, with and without `include_row_ids`.
   - `TestLuceneSearchOverlappingIndexRanges`: index ranges `[0, 2]`, `[1, 1]` 
and `[1, 3]`, where the
     best match is covered only by the tail of `[1, 3]`, also with 
`include_row_ids` and a
     `RowRangeIndex` that prunes `[0, 2]`.
   
   ### API and Format
   
   <!-- Does this change affect API in include dir or storage format or 
protocol -->
   No signature change in `include/`. The documented behavior changes:
   
   - `GlobalIndexReader::VisitFullTextSearch` 
(`include/paimon/global_index/global_index_reader.h`)
     returns at most `limit` rows across all index shards.
   - `FullTextSearch::limit` (`include/paimon/predicate/full_text_search.h`) is 
the maximum number of
     rows returned; each index shard is asked for that many rows before the 
merge.
   
   No storage format change.
   
   ### Documentation
   
   <!-- Does this change introduce a new feature -->
   Yes. `docs/source/user_guide/global_index.rst` replaces the note that 
searches across shards return
   the union of shard candidates with the global top-k behavior, tie handling, 
overlapping index
   ranges and `include_row_ids` clipping.
   
   ### Generative AI tooling
   
   Generated-by: Claude Code 2.1.296 (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