Skip to content

fix(fts): block only overlay-stale rows from phrase queries#7952

Closed
wjones127 wants to merge 2 commits into
lance-format:mainfrom
wjones127:fix/fts-phrase-overlay-drops-siblings
Closed

fix(fts): block only overlay-stale rows from phrase queries#7952
wjones127 wants to merge 2 commits into
lance-format:mainfrom
wjones127:fix/fts-phrase-overlay-drops-siblings

Conversation

@wjones127

Copy link
Copy Markdown
Contributor

Problem

An FTS phrase query excluded every index segment that covered a fragment carrying a data overlay on the indexed column. Because phrase queries have no flat re-evaluation path (plan_match_query re-scores overlaid fragments flat; plan_phrase_query did not), those segments were dropped wholesale — taking with them correct matches that were merely co-located with the overlaid row and had nothing to do with the overlay.

Concretely, overlaying a single row's text could erase every phrase hit in that row's segment. Sequence that triggers it: write rows → merge-insert more → build FTS index (with positions) → overlay one non-matching row → a phrase query that should match sibling rows in the same segment returns nothing.

Fix

Block just the overlay-stale rows from the phrase result via the prefilter's overlay_block mask, and keep every segment searchable — mirroring the scalar (MaterializeIndexExec) and vector (ANNIvfSubIndexExec) index paths, which also block stale rows row-granularly rather than dropping whole segments.

  • PhraseQueryExec gains an overlay_block: Option<RowAddrMask> applied to its prefilter before search.
  • plan_phrase_query computes the overlay-stale rows for the indexed column (collect_overlay_stale_rows_for_segment) and passes them as the block.
  • DatasetPreFilter::set_overlay_block mutates in place (the Arc-held prefilter is finalized just before wait_for_ready).

Semantics are unchanged in the ways that matter: stale hits are still removed, and — as before — a new phrase match on a blocked row's current value only surfaces once compaction folds the overlay into the base (phrase has no flat path to re-score it). The only change is that non-overlaid sibling rows are no longer collateral damage.

Test

test_fts_phrase_overlay_keeps_sibling_matches: a fragment holds three phrase matches plus one non-matching row; overlaying only the non-matching row must not drop the three siblings (nor a fourth match in a second fragment). Fails before the fix (returns []), passes after. All existing overlay-masking and FTS tests still pass.

🤖 Generated with Claude Code

A phrase query excluded every FTS index segment that covered a fragment
carrying an overlay on the indexed column. Phrase queries have no flat
re-evaluation path, so those segments were dropped wholesale — taking
with them correct matches that were merely co-located with the overlaid
row and had nothing to do with the overlay. Overlaying one row could
therefore erase every phrase hit in its segment.

Block just the overlay-stale rows from the phrase result via the
prefilter's `overlay_block` mask (mirroring the scalar and vector index
paths) and keep every segment searchable. Stale hits are still removed;
a new phrase match on a blocked row's current value still only surfaces
after compaction folds the overlay into the base, as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 69dcdeed-d9d4-443a-8ee6-decea223c061

📥 Commits

Reviewing files that changed from the base of the PR and between 93a8fae and 3b8c91c.

📒 Files selected for processing (1)
  • rust/lance/src/dataset/tests/dataset_overlay_index_masking.rs

📝 Walkthrough

Walkthrough

Phrase FTS overlay handling now derives stale row masks and applies them during phrase execution, preserving fresh matches in affected segments. Prefilter and executor APIs support mask propagation, with an end-to-end test covering sibling matches.

Changes

Phrase FTS overlay masking

Layer / File(s) Summary
Propagate overlay masks into phrase execution
rust/lance/src/io/exec/fts.rs, rust/lance/src/index/prefilter.rs
PhraseQueryExec stores and forwards an optional RowAddrMask to DatasetPreFilter during execution.
Build row-level stale masks
rust/lance/src/dataset/scanner.rs
plan_phrase_query computes stale row addresses from overlays and applies the resulting mask while executing loaded segments.
Validate sibling phrase matches
rust/lance/src/dataset/tests/dataset_overlay_index_masking.rs
Adds a phrase-overlay dataset and verifies stale hits are removed without removing sibling matches in the same fragment.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Scanner
  participant PhraseQueryExec
  participant DatasetPreFilter
  Scanner->>Scanner: derive stale row addresses
  Scanner->>PhraseQueryExec: attach overlay block
  PhraseQueryExec->>DatasetPreFilter: set_overlay_block
  DatasetPreFilter-->>PhraseQueryExec: exclude stale phrase rows
  PhraseQueryExec-->>Scanner: return fresh phrase matches
Loading

Possibly related PRs

Suggested labels: A-index

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: phrase queries now block only overlay-stale rows.
Description check ✅ Passed The description accurately matches the overlay-block fix, implementation details, and test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Jul 23, 2026
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.17647% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance/src/dataset/scanner.rs 88.23% 0 Missing and 2 partials ⚠️
rust/lance/src/io/exec/fts.rs 92.85% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@wjones127

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
rust/lance/src/dataset/tests/dataset_overlay_index_masking.rs-818-820 (1)

818-820: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the documented fragment ranges.

With max_rows_per_file: 6, fragment 0 contains ids 0..5 and fragment 1 contains ids 6..11; the current comment incorrectly includes ids 6 and 12.

As per coding guidelines, “Ensure doc comments match actual semantics.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance/src/dataset/tests/dataset_overlay_index_masking.rs` around lines
818 - 820, Correct the documentation comment above the two-fragment text dataset
test to state that fragment 0 contains ids 0..5 and fragment 1 contains ids
6..11, removing the incorrect references to ids 6 and 12 while preserving the
rest of the dataset description.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/lance/src/dataset/tests/dataset_overlay_index_masking.rs`:
- Around line 822-855: The test fixture currently manually constructs an
ArrowSchema and RecordBatch. Replace this construction in the dataset overlay
masking test with the required record_batch!() or gen_batch() builder,
preserving the existing id/text columns and row values.

In `@rust/lance/src/io/exec/fts.rs`:
- Around line 1255-1259: The public overlay-mask APIs need linked, compiling
documentation examples. In rust/lance/src/io/exec/fts.rs lines 1255-1259, update
PhraseQueryExec::with_overlay_block documentation to link RowAddrMask and
demonstrate passing one into phrase-query prefiltering; in
rust/lance/src/index/prefilter.rs lines 241-245, update
DatasetPreFilter::with_overlay_block documentation to link the method and
demonstrate in-place setup before wait_for_ready. Keep both examples
synchronized with their actual signatures.

---

Other comments:
In `@rust/lance/src/dataset/tests/dataset_overlay_index_masking.rs`:
- Around line 818-820: Correct the documentation comment above the two-fragment
text dataset test to state that fragment 0 contains ids 0..5 and fragment 1
contains ids 6..11, removing the incorrect references to ids 6 and 12 while
preserving the rest of the dataset description.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 2aa25a9e-4611-4404-9a73-54ba22c6ebf5

📥 Commits

Reviewing files that changed from the base of the PR and between fb88621 and 93a8fae.

📒 Files selected for processing (4)
  • rust/lance/src/dataset/scanner.rs
  • rust/lance/src/dataset/tests/dataset_overlay_index_masking.rs
  • rust/lance/src/index/prefilter.rs
  • rust/lance/src/io/exec/fts.rs

Comment thread rust/lance/src/dataset/tests/dataset_overlay_index_masking.rs Outdated
Comment thread rust/lance/src/io/exec/fts.rs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wjones127
wjones127 marked this pull request as ready for review July 23, 2026 22:22
@wjones127
wjones127 requested a review from westonpace July 23, 2026 22:22
@wjones127

Copy link
Copy Markdown
Contributor Author

Closing in favor of #7975

@wjones127 wjones127 closed this Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant