Skip to content

fix(rowids): reject duplicate values in a SortedArray segment - #9245

Merged
wjones127 merged 1 commit into
lance-format:mainfrom
jackylee-ch:fix/rowid-sorted-array-duplicates
Sep 17, 2026
Merged

wjones127 merged 1 commit into
lance-format:mainfrom
jackylee-ch:fix/rowid-sorted-array-duplicates

Conversation

@jackylee-ch

Copy link
Copy Markdown
Contributor

U64Segment is documented as "a sequence of distinct u64s", and RowIdSequence::try_from_iter rejects repeats on the write path, but the read path did not. RangeWithHoles deserialization used first_non_increasing_pair, which rejects equal neighbours; SortedArray used a separate first_descending_pair that only rejected descending ones. #8259 added the stricter predicate and wired it into one arm of the match only.

A repeated id is representable on disk, since the encoder picks SortedArray whenever it beats a bitmap. SegmentStats::n_holes then subtracts the value count from the slot span, which underflows once a value repeats: a panic where overflow checks are on, and otherwise a hole count large enough to drop live ids instead of reporting a corrupt file.

Use the strict predicate in both arms and drop the weaker duplicate.

Testing

cargo test --release -p lance-table (399 passed), plus cargo fmt and clippy with -D warnings. test_rejects_unsorted_sorted_array gains a duplicate case per width; restoring the old predicate fails those three and the existing RangeWithHoles duplicate case.

`U64Segment` is documented as "a sequence of distinct u64s", and the write path
rejects repeats in `RowIdSequence::try_from_iter`, but the read path did not.
Deserializing a `RangeWithHoles` segment used `first_non_increasing_pair`, which
rejects equal neighbours, while `SortedArray` used a separate
`first_descending_pair` that only rejected strictly descending ones. lance-format#8259 added
the stricter predicate to close exactly this class of gap -- its root cause note
reads "Corrupt metadata could therefore panic or silently report an incorrect
length" -- and wired it into one arm of the match only.

A repeated id is representable on disk: the encoder picks `SortedArray` whenever
it is smaller than a bitmap, so a sparse sequence encodes that way and survives
the protobuf round trip. `SegmentStats::n_holes` then derives the hole count by
subtracting the value count from the slot span, which underflows once a value
repeats -- a panic where overflow checks are on, and a wildly wrong hole count
where they are not, which drops live ids from the rebuilt segment instead of
reporting a corrupt file.

Use the strict predicate for both arms and drop the weaker duplicate, so the two
cannot diverge again. The message follows the `RangeWithHoles` wording; it now
reports decoded values rather than raw offsets, so the assertion matches on the
prefix.

## Testing

`cargo test --release -p lance-table` (399 passed). Extended
`test_rejects_unsorted_sorted_array` with a duplicate case per encoding width;
restoring the strict-descending predicate fails those three plus the existing
`RangeWithHoles` duplicate case. Also `cargo fmt --all -- --check` and
`cargo clippy --release -p lance-table --all-features --all-targets -- -D warnings`.
@github-actions github-actions Bot added the bug Something isn't working label Sep 15, 2026

@lance-gatekeeper lance-gatekeeper 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.

Gate recommendation: approve.

This closes the gap in SortedArray metadata validation: equal neighbours are rejected as corrupt for all three encoded widths, matching the existing strict-order contract used for RangeWithHoles. The focused regression coverage exercises both descending and duplicate inputs.

@lance-gatekeeper lance-gatekeeper Bot added the K-approved Latest Gatekeeper recommendation permits acceptance. label Sep 15, 2026

@wjones127 wjones127 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.

Thank you!

@wjones127
wjones127 merged commit 5977462 into lance-format:main Sep 17, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working K-approved Latest Gatekeeper recommendation permits acceptance.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants