feat(table): add primary-key full-text read path#599
Merged
Conversation
JunRuiLee
marked this pull request as draft
July 23, 2026 11:11
Add the read side of primary-key full-text search, mirroring Java PrimaryKeyFullTextScan / PrimaryKeyFullTextBucketSearch / PrimaryKeyFullTextRead: - PrimaryKeyFullTextScan + PrimaryKeyFullTextSearchSplit: pin a snapshot, scan the index manifest, group payloads by (partition, bucket) via the shared PkFullTextBucketState reconciliation, and emit splits (covered union uncovered equals the active data files; buckets below zero are skipped). - PrimaryKeyFullTextBucketSearch: lay each payload's source files out as a cumulative archive, build a live-row include allow-list (active ranges minus deletion-vector positions, or none when clean), search the archive through the paimon-ftindex-core reader, and map each returned archive row back to a physical (data file, row position). Archives resolve under the table index directory, matching the primary-key vector read and Java's default layout. - PrimaryKeyFullTextCandidate plus score-descending top_k_by_score with a deterministic tie-break, and finite-score validation. - PrimaryKeyFullTextRead (FAST mode only): search every planned bucket, fuse by score into a global top-k, materialize the winning physical rows, reorder them best-score-first, and append the unified search-score column. - FullTextSearchBuilder: a primary-key branch whose execute_read materializes rows, while execute/execute_scored fail loud (the primary-key path yields physical positions, not global row ids). Dispatch selects the primary-key path only when data evolution is disabled and the queried column is a configured pk-full-text index column.
JunRuiLee
force-pushed
the
feat/pk-fulltext-read
branch
from
July 23, 2026 11:18
8c62634 to
6456e19
Compare
JunRuiLee
marked this pull request as ready for review
July 23, 2026 11:43
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Part of #567 (primary-key full-text + hybrid search on the Rust read side), the third slice after #563 (the
paimon-ftindex-corereader foundation) and #594 (the PK full-text data layer).This adds the primary-key full-text read path: plan splits over
"full-text"index payloads, run bucket-local search through thepaimon-ftindex-corereader with a live-row include filter, map archive row-ids to physical(data file, row position), fuse cross-bucket by score, and materialize the matching rows best-score-first with a__paimon_search_scorecolumn. FAST mode only, mirroring Java (FULL/DETAILfail loud; there is no exact/rerank fallback on the full-text path).Brief change log
PrimaryKeyFullTextScan+PrimaryKeyFullTextSearchSplit: pin a snapshot, scan the index manifest, group payloads by(partition, bucket)via the sharedPkFullTextBucketStatereconciliation (from feat(table): add primary-key full-text read data layer #594), and emit splits. Split invariants mirror JavaPrimaryKeyFullTextSearchSplit(unique data files; each current payload covers ≥1 active source; no double-covered source; covered ∪ uncovered == the active data files); buckets< 0are skipped.PrimaryKeyFullTextBucketSearch: lay each payload's source files out as a cumulative archive (offset_i= running sum of prior source row counts); a source is active iff its data file is in the split; build a live-row include allow-list (union of active source ranges minus deletion-vector positions, each mapped tooffset + local), orNonewhen nothing is inactive/deleted; search the archive through thepaimon-ftindex-corereader (query passed verbatim); map each returned archive row-id back torow_position = row_id − source.offset, failing loud on an inactive/out-of-range row-id or a source/data-file row-count mismatch. Archives are opened under the split's bucket directory (mirroring JavaIndexInDataFileDirPathFactory).PrimaryKeyFullTextCandidate+top_k_by_score: score-descending Top-K with a deterministic tie-break (score desc, then partition bytes, bucket, data file, row position), and finite-score validation (mirroring JavaPrimaryKeySearchPosition).PrimaryKeyFullTextRead(FAST-only): search every planned bucket, fuse by score into a global Top-limit, materialize the winning physical rows via the vector physical-position materializers, reorder them back to best-score order (keyed by full physical position), strip the internal position column, and append__paimon_search_score. Raw full-text scores are carried verbatim — nodistance_to_scoreconversion.FullTextSearchBuilderprimary-key branch:execute_readmaterializes rows;execute/execute_scoredfail loud on the PK path (physical positions, not global row-ids). Dispatch mirrors JavaprimaryKeyFullTextDefinition— the PK path is taken only when data evolution is disabled and the queried column is a configuredpk-full-text.index.columnsentry. The append/global-index path is unchanged.Reuses the primary-key vector physical-position materializers (
PkVectorIndexedSplit/PkVectorIndexedSplitRead/PkVectorPositionRead), which are index-agnostic; the full-text read builds the indexed splits directly with raw scores rather than through the vector distance-to-score path.Tests
(partition, bucket)grouping, current/stale via the bucket state, uncovered files carried, split invariants, bucket< 0skip.include = Nonewhen clean; include subtracts deletion vectors (mapped by offset); row-id → position; fail-loud on inactive/out-of-range row-id and row-count mismatch; empty-include skip.top_k_by_score: score-descending order + deterministic tie-break; cross-bucket fusion; non-finite score rejected.FULL/DETAILfail loud.execute_readmaterializes rows best-score-first with the score column (round-trip against an archive built with the core writer, deletion-vector filtered); PKexecute/execute_scoredfail loud; append-path regression stays green.cargo test -p paimonand--features fulltextgreen;cargo clippy -p paimon --all-targets(+--features fulltext)-D warningsclean;cargo fmt --all --checkclean;cargo build -p paimon-datafusion(Send boundary) passes.API and Format
No storage-format change and no change to existing public read APIs.
FullTextSearchBuildergainsexecute_readfor the primary-key full-text path. The archive-reading code is behind the existingfulltextcargo feature; scan/split/candidate/top-k are plain planning code.Documentation
No documentation changes; module- and item-level doc comments describe the scan, bucket search, ranking, and read.