Skip to content

fix(index): correct offset→row-id translation for overlay masking under deletions#7918

Open
wjones127 wants to merge 1 commit into
mainfrom
will/oss-1325-fix-deletion-rowid-translate
Open

fix(index): correct offset→row-id translation for overlay masking under deletions#7918
wjones127 wants to merge 1 commit into
mainfrom
will/oss-1325-fix-deletion-rowid-translate

Conversation

@wjones127

@wjones127 wjones127 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #7549 (now merged).

Under stable row ids, a scalar (BTREE) index query could return wrong results when a fragment had both a deletion and an overlay on the indexed column, if the deleted row's physical offset was below the overlaid row's offset in the same fragment.

translate_addr_treemap_to_row_ids maps a stale overlay row's physical offset to its stable row id when building the overlay block/take set. It advanced a RowIdSequence::iter() cursor only for non-deleted offsets, assuming the sequence yields ids for live rows only. It does not: the sequence keeps one id per physical row, and deletions are tracked separately by the deletion vector without compacting the sequence (RowIdIndex maps ids to start_address + position and filters deletions separately). A deletion at an offset below a stale offset therefore desynced the cursor, mapping the stale offset to the wrong row id — so the overlay block masked the wrong row, the stale index hit leaked, and the row's new overlay value was never surfaced.

Fix: index the sequence directly by physical offset (as row_addrs_to_row_ids already does), skipping deleted offsets. This is also cheaper — O(stale offsets) instead of O(physical rows) — and errors (rather than silently dropping) if a selected offset has no sequence entry, since a silent drop would let a stale hit escape masking.

Only affects datasets with stable row ids; without them, addresses are row ids and no translation happens.

Regression test test_btree_overlay_stale_row_with_prior_deletion (parametrized over stable_row_ids) overlays fragment 1 and deletes a row below the stale offset. Fails before the fix on the stable-row-id case (age = 80 leaks the stale hit), passes after; the non-stable case is a control that stays green.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Updated stable row ID translation to process selected physical offsets directly, skip deleted offsets, and report missing sequence entries as corruption. Added an end-to-end BTree overlay masking regression test covering stale, updated, retained, and deleted rows.

Stable row ID overlay masking

Layer / File(s) Summary
Direct physical offset translation
rust/lance/src/dataset/rowids.rs
Documentation and partial address translation now treat sequence positions as physical offsets, filter deletions separately, and validate direct sequence lookups.
Overlay masking regression coverage
rust/lance/src/dataset/tests/dataset_overlay_index_masking.rs
Adds a parameterized end-to-end test for stale overlay values, re-evaluated new values, retained indexed rows, and deleted rows.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • lance-format/lance#7549: Introduced the overlay-stale index masking behavior validated by this translation update and regression test.

Suggested labels: A-index

Suggested reviewers: xuanwo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly summarizes the main fix: offset-to-row-id translation for overlay masking under deletions.
Description check ✅ Passed The description directly matches the changeset and explains the bug, fix, and regression test.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch will/oss-1325-fix-deletion-rowid-translate

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

@github-actions github-actions Bot added the bug Something isn't working label Jul 22, 2026
Base automatically changed from will/oss-1325-indexes-mask-data-overlay-files-correctly to main July 22, 2026 17:48
…er deletions

`translate_addr_treemap_to_row_ids` mapped a stale overlay row's physical
offset to its stable row id by advancing a `RowIdSequence::iter()` cursor only
for non-deleted offsets, assuming the sequence yields ids for live rows only.
It does not: the sequence keeps one id per physical row, and deletions are
tracked separately by the deletion vector without compacting the sequence
(see `RowIdIndex`, which maps ids to `start_address + position` and filters
deletions separately). A deletion at an offset below a stale offset in the same
fragment therefore desynced the cursor, mapping the stale offset to the wrong
row id: the overlay block masked the wrong row, so the stale BTREE index hit
leaked and the row's new overlay value was never surfaced.

Index the sequence directly by physical offset (as `row_addrs_to_row_ids`
already does), skipping deleted offsets. This is also cheaper — O(stale
offsets) instead of O(physical rows).

Only affects datasets with stable row ids; without stable row ids addresses
are row ids and no translation happens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wjones127
wjones127 force-pushed the will/oss-1325-fix-deletion-rowid-translate branch from c9d2bb2 to a064bad Compare July 22, 2026 21:15
@wjones127

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 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.

🧹 Nitpick comments (1)
rust/lance/src/dataset/rowids.rs (1)

119-142: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Correctly fixes the offset→row-id desync; missing test for the new corruption guard.

The direct sequence.get(physical_offset) lookup correctly replaces the old cursor-based iteration, matching the PR's regression test in dataset_overlay_index_masking.rs. However, the new ok_or_else branch (Line 135-141) that errors on a missing sequence entry is itself new behavior (previously such offsets would presumably fall through silently) and has no test exercising it — e.g., a scenario where a stale offset from an index result no longer maps to any entry in the current RowIdSequence.

As per coding guidelines, "Every bugfix and feature must have corresponding tests."

🤖 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/rowids.rs` around lines 119 - 142, The corruption
guard in the offset-to-row-id mapping needs a regression test for offsets beyond
the current RowIdSequence. Extend the relevant row-id masking test coverage
around the sequence lookup in the fragment processing flow to construct a stale
physical offset with no corresponding sequence entry, then assert the operation
returns the expected internal error and preserves the reported fragment, offset,
and sequence-length details.

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.

Nitpick comments:
In `@rust/lance/src/dataset/rowids.rs`:
- Around line 119-142: The corruption guard in the offset-to-row-id mapping
needs a regression test for offsets beyond the current RowIdSequence. Extend the
relevant row-id masking test coverage around the sequence lookup in the fragment
processing flow to construct a stale physical offset with no corresponding
sequence entry, then assert the operation returns the expected internal error
and preserves the reported fragment, offset, and sequence-length details.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 8e00c289-c424-4399-a206-237717db49b8

📥 Commits

Reviewing files that changed from the base of the PR and between 5d0ab43 and a064bad.

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

@wjones127
wjones127 marked this pull request as ready for review July 22, 2026 21:27
@wjones127
wjones127 requested a review from westonpace July 22, 2026 21:27
@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 28.57143% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance/src/dataset/rowids.rs 28.57% 3 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

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