Skip to content

fix(bloomfilter): bound read/write to prevent BinaryArray i32 offset overflow#7753

Open
zhangyue19921010 wants to merge 6 commits into
lance-format:mainfrom
zhangyue19921010:fix-bloomfilter-overfilter
Open

fix(bloomfilter): bound read/write to prevent BinaryArray i32 offset overflow#7753
zhangyue19921010 wants to merge 6 commits into
lance-format:mainfrom
zhangyue19921010:fix-bloomfilter-overfilter

Conversation

@zhangyue19921010

@zhangyue19921010 zhangyue19921010 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

When create bloom filter index on a column with more than 1.07B rows, we might ran into arrow byte array offset overflow error. This pr fix it by writing index in batch.

Summary by CodeRabbit

  • Bug Fixes

    • Improved Bloom Filter index load/write to enforce a maximum serialized payload size, preventing offset overflow and oversized reads.
    • Added validation to reject bloom-filter records whose payload spans exceed the allowed limit.
    • Large bloom-filter indexes are now processed via bounded chunking for safer, more reliable reloads.
  • Compatibility

    • Preserves prior behavior when optional null-row information is not provided.
    • Chunked writes continue to persist and reload null-row bitmap state correctly when present.

@github-actions github-actions Bot added A-index Vector index, linalg, tokenizer bug Something isn't working labels Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The Bloom-filter scalar index now reads and writes serialized bloom-filter data in size-bounded record-batch chunks, rejects oversized payloads, preserves optional null-row metadata, and tests these behaviors.

Bloom-filter payload chunking

Layer / File(s) Summary
Bounded chunked loading
rust/lance-index/src/scalar/bloomfilter.rs
Loading computes bounded read sizes, streams record batches, validates payload lengths, and deserializes zone statistics incrementally.
Bounded batch writing
rust/lance-index/src/scalar/bloomfilter.rs
BloomFilterBatchWriter creates multiple bounded record batches and preserves optional null-row bitmap metadata.
Size-boundary validation
rust/lance-index/src/scalar/bloomfilter.rs
Tests cover bounded reads, oversized payload rejection, chunked round trips, null bitmap preservation, and oversized writes.

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

Sequence Diagram(s)

sequenceDiagram
  participant BloomFilterIndex
  participant read_range_stream
  participant try_from_serialized
  participant BloomFilterBatchWriter
  BloomFilterIndex->>read_range_stream: read bounded serialized chunks
  read_range_stream-->>BloomFilterIndex: record-batch chunks
  BloomFilterIndex->>try_from_serialized: deserialize chunks with max_array_length
  try_from_serialized-->>BloomFilterIndex: zone statistics
  BloomFilterBatchWriter->>BloomFilterIndex: flush bounded record batches
Loading

Suggested reviewers: westonpace, wjones127, haochengliu

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: bounding Bloom filter read/write paths to avoid BinaryArray i32 offset overflow.
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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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-index/src/scalar/bloomfilter.rs`:
- Line 52: Document MAX_BLOOMFILTER_ARRAY_LENGTH with the rationale for
reserving a 1 MiB safety margin below the i32 limit. Add rustdoc for the public
write_index API, including an example that demonstrates linking IndexStore and
IndexFile.
- Around line 2379-2434: Update test_bloomfilter_chunked_write_and_load to build
nullable data containing null values, ensuring the chunked write triggers
add_global_buffer across multiple flushes. After loading, assert the preserved
null_rows metadata or verify equivalent IsNull query results while retaining the
existing chunk and zone assertions.
- Around line 2387-2399: Replace the manual Int32 schema, array, and RecordBatch
construction in the test setup with the standard gen_batch() or record_batch!()
builder, preserving the existing VALUE_COLUMN_NAME data and row count. Apply the
same change to the repeated setup near the second referenced test block, while
leaving the stream and add_row_addr flow unchanged.
- Around line 2367-2375: Update the related BloomFilterIndex error tests,
including the cases around read_batch_size, to assert that the returned error
matches the expected InvalidInput variant before checking its message content.
Preserve the existing “exceeds max supported batch bytes” message assertions
while adding variant-level validation for each applicable case.
- Line 142: Update the zones allocation in the surrounding bloom-filter
construction logic to use Vec::with_capacity() with the known row count instead
of Vec::new(), while preserving the existing element type and subsequent
population behavior.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 46b2c204-8d4b-4a88-9d8b-bc30c306c2d2

📥 Commits

Reviewing files that changed from the base of the PR and between f0fcb81 and dc4a1f1.

📒 Files selected for processing (1)
  • rust/lance-index/src/scalar/bloomfilter.rs

Comment thread rust/lance-index/src/scalar/bloomfilter.rs
Comment thread rust/lance-index/src/scalar/bloomfilter.rs Outdated
Comment thread rust/lance-index/src/scalar/bloomfilter.rs
Comment thread rust/lance-index/src/scalar/bloomfilter.rs
Comment thread rust/lance-index/src/scalar/bloomfilter.rs
@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.15068% with 20 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance-index/src/scalar/bloomfilter.rs 93.15% 5 Missing and 15 partials ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/lance-index/src/scalar/bloomfilter.rs (1)

112-161: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

read_batch_size is computed eagerly even when the index has zero rows.

load_with_max_array_length calls Self::read_batch_size(number_of_items, probability, max_array_length) (Line 143-144) before checking whether index_file.num_rows() is 0. If a builder was configured with number_of_items/probability large enough that a single serialized filter would exceed max_array_length, read_batch_size returns an error (per test_bloomfilter_read_batch_size_rejects_oversized_filter). For a genuinely empty index (no zones were ever written, e.g. an empty input stream), this needlessly fails load even though no bloom-filter bytes need to be read at all.

🐛 Proposed fix
+        if index_file.num_rows() == 0 {
+            return Ok(Arc::new(Self {
+                zones: Vec::new(),
+                number_of_items,
+                probability,
+                null_rows,
+            }));
+        }
+
         let read_batch_size =
             Self::read_batch_size(number_of_items, probability, max_array_length)?;

         let mut zones = Vec::with_capacity(index_file.num_rows());
🤖 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-index/src/scalar/bloomfilter.rs` around lines 112 - 161, Update
load_with_max_array_length to handle an index with zero rows before calling
Self::read_batch_size. Return a valid BloomFilter with empty zones while still
loading applicable metadata and null_rows, and preserve the existing batch-size
validation and reading path for non-empty indexes.
🤖 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.

Outside diff comments:
In `@rust/lance-index/src/scalar/bloomfilter.rs`:
- Around line 112-161: Update load_with_max_array_length to handle an index with
zero rows before calling Self::read_batch_size. Return a valid BloomFilter with
empty zones while still loading applicable metadata and null_rows, and preserve
the existing batch-size validation and reading path for non-empty indexes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 166b7639-4a9a-43ce-a736-e4cb757bd9a2

📥 Commits

Reviewing files that changed from the base of the PR and between b370d07 and dd282df.

📒 Files selected for processing (1)
  • rust/lance-index/src/scalar/bloomfilter.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-index Vector index, linalg, tokenizer bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant