fix: deduplicate row addresses in take to prevent panic#5881
fix: deduplicate row addresses in take to prevent panic#5881wjones127 merged 3 commits intolance-format:mainfrom
Conversation
TakeStream::map_batch passed duplicate row addresses straight through to the encoding layer, which requires strictly increasing indices. Duplicates (e.g. from FTS on List<Utf8> where multiple list elements in the same row match) caused indices_to_ranges to produce overlapping ranges, panicking in BinaryPageScheduler with "attempt to subtract with overflow". Dedup sorted addresses before passing them to fragment readers, then expand the results back to include duplicates. Also tighten the schedule_take debug_assert from <= to < to catch this earlier. Fixes lance-format#5260 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Code ReviewThis PR correctly fixes issue #5260 by deduplicating row addresses before passing them to the encoding layer which requires strictly increasing indices. P1 Issue: Use proper error propagation instead of .unwrap()At line 290 in the updated code, Consider changing to: new_data = arrow_select::take::take_record_batch(&new_data, &expand_indices)
.map_err(|e| DataFusionError::Execution(e.to_string()))?;The same applies to the existing Overall the fix is correct and well-tested. The dedup approach is sound: using the sorted order to efficiently track unique addresses and then expanding results back with duplicates is the right pattern. |
The common path (sorted, no duplicates) now does a single-pass is_sorted_and_unique check and skips all sorting, dedup, and permutation logic. The unsorted/duplicate paths are unified and the expand + inverse permutation are composed into a single take. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
westonpace
left a comment
There was a problem hiding this comment.
This PR is fine to avoid panic but does this mean the search will still output duplicate rows? Is that something we want long term? Or should we make a follow-up?
| if let Some(expand_map) = expand_map { | ||
| batch = arrow_select::take::take_record_batch(&batch, &expand_map)?; | ||
| } |
There was a problem hiding this comment.
Someday it would be nice to coalesce the dataset paths and the TakeExec paths so that we don't have to duplicate this logic everywhere. Not a comment on this PR, just griping.
Yeah I'll make a follow up issue. Not sure what the intended semantics of FTS in a list are. |
Previously, duplicate row addresses (e.g. from FTS on
List<Utf8>wheremultiple list elements in the same row match) were passed to the v2 encoding
layer, which requires strictly increasing indices. This caused
indices_to_rangesto produce overlapping ranges, panicking with "attempt tosubtract with overflow".
Dedup is handled at two sites:
FragmentReader::take_as_batch: This is the common entry point for allcallers that need a single batch (
FileFragment::take_rows,FragmentSession::take_rows,TakeStream::map_batch). Since it alreadycollects into one batch, the expand-after-dedup step is trivial. Handling it
here rather than in the v2 adapter's
take_all_tasksavoids complicationswith the streaming
ReadBatchTaskStreamreturn type. Handling it lower inschedule_takewouldn't work because the encoding layer has no notion ofduplicates by design.
TakeStream::map_batch: Dedup here serves a different purpose — itreduces I/O by avoiding redundant reads of the same row across fragments.
This also optimizes the common path (sorted, unique addresses) to skip all
sorting/dedup/permutation overhead entirely.
Fixes #5260
Test plan
test_take_with_duplicate_row_addrs— sorted duplicate row addressestest_take_with_unsorted_duplicate_row_addrs— unsorted duplicatestest_fragment_take_indices/test_fragment_take_rows— fragment-levelduplicate indices (previously failing on
LanceFileVersion::Stable)🤖 Generated with Claude Code