Skip to content

fix(blob)!: preserve null selections across blob APIs#7903

Merged
Xuanwo merged 4 commits into
mainfrom
xuanwo/preserve-null-blob-selections
Jul 23, 2026
Merged

fix(blob)!: preserve null selections across blob APIs#7903
Xuanwo merged 4 commits into
mainfrom
xuanwo/preserve-null-blob-selections

Conversation

@Xuanwo

@Xuanwo Xuanwo commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Why

Blob selection APIs currently expose an implementation detail of the physical read planner: null descriptors are omitted because they schedule no payload I/O. This makes result cardinality depend on descriptor contents, prevents callers from distinguishing null values from omitted rows, and leaves read_blobs, read_blob_ranges, and take_blobs with inconsistent contracts.

This is a follow-up to #7864 and resolves the remaining null behavior described in #7899.

Contract

All blob selection APIs now return one logical result per selector or range request. Null blobs are represented explicitly, while valid empty blobs and empty ranges remain non-null empty values. The physical planner still avoids payload I/O for nulls; the logical result layer restores their selection positions.

Breaking change

This intentionally changes public return types and observable result cardinality:

  • Rust take_blobs* APIs return Vec<Option<BlobFile>>, and ReadBlob::data / ReadBlobRange::data are Option<Bytes>.
  • Python blob APIs return Optional values for null blobs.
  • Java takeBlobs* lists retain every requested position and may contain null elements.

Callers that relied on null selections being omitted must now handle explicit null results.

BREAKING CHANGE: Blob selection APIs now preserve every selector or request and represent null blob values explicitly instead of omitting them.

BREAKING CHANGE: Blob selection APIs now return one result per selector and represent null blob values explicitly. Rust take_blobs APIs return Vec<Option<BlobFile>>, read result payloads are Option<Bytes>, Python returns Optional values, and Java lists may contain null elements.
@coderabbitai

coderabbitai Bot commented Jul 22, 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: 78548bea-d980-4f34-b6b5-f4527d89676d

📥 Commits

Reviewing files that changed from the base of the PR and between 65a44ca and b6717b1.

📒 Files selected for processing (3)
  • docs/src/guide/blob.md
  • java/src/main/java/org/lance/Dataset.java
  • python/python/lance/dataset.py

📝 Walkthrough

Walkthrough

Blob selection and range-read APIs now preserve null entries in their original selection positions, distinguish null blobs from valid empty blobs, and expose optional results consistently across Rust, Python, and Java bindings with updated tests and documentation.

Changes

Nullable blob result pipeline

Layer / File(s) Summary
Rust selection contracts and totalization
rust/lance/src/dataset.rs, rust/lance/src/dataset/blob.rs
Blob handles and payloads now use optional values; selection and range streams reinsert null results by row or request index while preserving ordering behavior.
Python and Java bindings
python/src/dataset.rs, python/python/lance/..., java/lance-jni/..., java/src/main/...
Bindings expose optional blob handles or payloads, converting missing Rust values to Python None or Java null and documenting the one-result-per-selection contract.
Tests, fixtures, and documentation
rust/lance/src/dataset/blob.rs, python/python/tests/test_blob.py, java/src/test/..., docs/src/guide/blob.md, rust/lance/src/dataset/optimize.rs
Coverage adds deterministic null blobs and validates null preservation, empty payloads, duplicate and reordered selections, all-null columns, nested blobs, and compaction handling.

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Dataset
  participant BlobSelection
  participant BlobStorage
  Caller->>Dataset: select blob rows or ranges
  Dataset->>BlobSelection: collect selected rows and null positions
  BlobSelection->>BlobStorage: read non-null payloads
  BlobStorage-->>BlobSelection: payload results
  BlobSelection-->>Dataset: merge payloads with None entries
  Dataset-->>Caller: one result per row or request
Loading

Possibly related PRs

  • lance-format/lance#7864: Also modifies read_blob_ranges across Rust, Python, Java, and tests for null handling and request identity.

Suggested reviewers: bubblecal, wjones127

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: preserving null selections across blob APIs.
Description check ✅ Passed The description matches the PR's null-handling blob API changes and breaking contract updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch xuanwo/preserve-null-blob-selections

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 A-python Python bindings A-java Java bindings + JNI A-docs Documentation bug Something isn't working breaking-change labels Jul 22, 2026
@Xuanwo
Xuanwo marked this pull request as ready for review July 22, 2026 07:10

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

Claude Code Review

Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.

Tip: disable this comment in your organization's Code Review settings.

@Xuanwo
Xuanwo requested review from westonpace and wjones127 July 22, 2026 07:10

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

Note

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

🟡 Other comments (2)
java/src/main/java/org/lance/Dataset.java-1628-1628 (1)

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

Add a linked null-safe usage example.

Document {@link BlobFile} and show callers checking for null elements before reading or closing them.

As per coding guidelines, “Document all public APIs with examples and links to relevant structs and methods; keep examples synchronized with actual signatures.”

Also applies to: 1646-1646

🤖 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 `@java/src/main/java/org/lance/Dataset.java` at line 1628, Update the public
API documentation around the row-id blob retrieval method and its return
description to link to BlobFile and include a usage example using the actual
method signature; demonstrate checking each returned element for null before
reading from or closing it.

Source: Coding guidelines

docs/src/guide/blob.md-226-230 (1)

226-230: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Synchronize the table and examples with nullable results.

The new text says results can be None, but the table still declares List[Tuple[int, bytes]] and List[BlobFile]; the take_blobs example also dereferences blobs[0] unconditionally. Use optional types and show a null check.

As per coding guidelines, “Document all public APIs with examples and links to relevant structs and methods; keep examples synchronized with actual signatures.”

🤖 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 `@docs/src/guide/blob.md` around lines 226 - 230, Update the blob selection API
table and examples to represent nullable results: change the relevant return
types to optional element types, and revise the take_blobs example to check
whether blobs[0] is None before dereferencing it. Keep the documented
cardinality and distinction between null and empty blobs unchanged.

Source: Coding guidelines

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

2190-2274: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider deduplicating the selection bounds/duplicate-index validation.

collect_null_selection_values and into_optional_blob_files both re-implement the same "check selection_index in bounds, error if slot already filled" pattern (and totalize_blob_selection_stream repeats a third variant of the bounds check). This is copy-pasted invariant-checking logic across three call sites with duplicated error message formatting. A shared helper (e.g. fn checked_slot_mut<T>(slots: &mut [T], selection_index: usize, selection_count: usize) -> Result<&mut T>) would remove the duplication and guarantee consistent error text.

Separately, this new selection/null-tracking and stream-totalization logic (~150 lines: collect_null_selection_values, totalize_blob_selection_stream, related helpers) adds a non-trivial scheduling/merging component inline into an already very large file (7500+ lines). As per coding guidelines, "Extract substantial new logic such as bin packing or scheduling into dedicated submodules instead of inlining it into large files." Moving this to a dedicated blob/selection.rs (or similar) submodule would improve discoverability and keep blob.rs focused.

Also applies to: 2670-2690

🤖 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/blob.rs` around lines 2190 - 2274, Deduplicate
selection-index validation across collect_null_selection_values,
into_optional_blob_files, and totalize_blob_selection_stream by introducing and
reusing a shared checked-slot helper with consistent bounds and duplicate
errors. Move the selection/null-tracking and stream-totalization helpers into a
dedicated blob selection submodule, updating blob.rs call sites and
visibility/imports while preserving existing ordering and error behavior.

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.

Other comments:
In `@docs/src/guide/blob.md`:
- Around line 226-230: Update the blob selection API table and examples to
represent nullable results: change the relevant return types to optional element
types, and revise the take_blobs example to check whether blobs[0] is None
before dereferencing it. Keep the documented cardinality and distinction between
null and empty blobs unchanged.

In `@java/src/main/java/org/lance/Dataset.java`:
- Line 1628: Update the public API documentation around the row-id blob
retrieval method and its return description to link to BlobFile and include a
usage example using the actual method signature; demonstrate checking each
returned element for null before reading from or closing it.

---

Nitpick comments:
In `@rust/lance/src/dataset/blob.rs`:
- Around line 2190-2274: Deduplicate selection-index validation across
collect_null_selection_values, into_optional_blob_files, and
totalize_blob_selection_stream by introducing and reusing a shared checked-slot
helper with consistent bounds and duplicate errors. Move the
selection/null-tracking and stream-totalization helpers into a dedicated blob
selection submodule, updating blob.rs call sites and visibility/imports while
preserving existing ordering and error behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: fe3f05fe-5449-4e15-9490-2502a14a9899

📥 Commits

Reviewing files that changed from the base of the PR and between ecfded9 and 4b8a003.

📒 Files selected for processing (12)
  • docs/src/guide/blob.md
  • java/lance-jni/src/blocking_blob.rs
  • java/src/main/java/org/lance/Dataset.java
  • java/src/test/java/org/lance/DatasetTest.java
  • java/src/test/java/org/lance/TestUtils.java
  • python/python/lance/dataset.py
  • python/python/lance/lance/__init__.pyi
  • python/python/tests/test_blob.py
  • python/src/dataset.rs
  • rust/lance/src/dataset.rs
  • rust/lance/src/dataset/blob.rs
  • rust/lance/src/dataset/optimize.rs

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

Caution

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

⚠️ Outside diff range comments (2)
rust/lance/src/dataset.rs (2)

1737-1773: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add compiling examples for the changed take_blobs* APIs.

These breaking nullable-return semantics are documented, but take_blobs, take_blobs_by_addresses, and take_blobs_by_indices still lack usage examples demonstrating None versus a present zero-size blob.

As per coding guidelines, **/*.{rs,md,rst}: Document all public APIs with examples and links to relevant structs and methods; keep examples synchronized with actual signatures.

🤖 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.rs` around lines 1737 - 1773, Add compiling
documentation examples to the public methods take_blobs,
take_blobs_by_addresses, and take_blobs_by_indices, using their current
signatures and return types. Each example must demonstrate handling None for a
null blob versus Some(BlobFile) with size zero for an empty blob, and include
relevant links to BlobFile and the corresponding row-ID, row-address, or
row-index method.

Source: Coding guidelines


2929-2946: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add regression tests for positional filtering.

This fix changes the alignment contract by filtering ids and addresses together and rejecting mismatched lengths. Cover live/deleted IDs, the no-index fallback, reordered or duplicate inputs, and the mismatch error—including both the error variant and count-bearing message.

As per coding guidelines, every bugfix and feature must have corresponding tests, and tests should assert both the error variant and message content.

🤖 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.rs` around lines 2929 - 2946, The positional filtering
change in the surrounding dataset method lacks regression coverage. Add tests
covering live and deleted IDs, the no-index fallback, reordered and duplicate
inputs, and mismatched ID/address lengths; for the mismatch case, assert both
the expected error variant and a message containing the relevant counts, while
verifying successful cases preserve positional alignment.

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.

Outside diff comments:
In `@rust/lance/src/dataset.rs`:
- Around line 1737-1773: Add compiling documentation examples to the public
methods take_blobs, take_blobs_by_addresses, and take_blobs_by_indices, using
their current signatures and return types. Each example must demonstrate
handling None for a null blob versus Some(BlobFile) with size zero for an empty
blob, and include relevant links to BlobFile and the corresponding row-ID,
row-address, or row-index method.
- Around line 2929-2946: The positional filtering change in the surrounding
dataset method lacks regression coverage. Add tests covering live and deleted
IDs, the no-index fallback, reordered and duplicate inputs, and mismatched
ID/address lengths; for the mismatch case, assert both the expected error
variant and a message containing the relevant counts, while verifying successful
cases preserve positional alignment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 8d26e396-aeb9-43d9-b955-a603e453126e

📥 Commits

Reviewing files that changed from the base of the PR and between 4b8a003 and e40b28c.

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

@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.20690% with 48 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance/src/dataset/blob.rs 87.20% 35 Missing and 8 partials ⚠️
rust/lance/src/dataset/optimize.rs 55.55% 2 Missing and 2 partials ⚠️
rust/lance/src/dataset.rs 66.66% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@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: 1

Note

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

Caution

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

⚠️ Outside diff range comments (1)
rust/lance/src/dataset.rs (1)

1756-1760: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Preserve the existing public Rust API or add a deprecation path.

Changing take_blobs* return types to Vec<Option<BlobFile>> is source-breaking for callers that treat the result as a non-option vector. Add nullable replacement APIs while retaining and deprecating the old methods, or document the sanctioned major-version exception and migration tests, per the public API rule.

Also applies to: 1789-1793, 1816-1820

🤖 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.rs` around lines 1756 - 1760, Preserve the existing
public return types of the `take_blobs` methods and add separate nullable
replacement APIs returning `Vec<Option<BlobFile>>`; retain the original methods
as deprecated wrappers that preserve their current non-optional behavior. Apply
this consistently to all `take_blobs*` variants identified in the diff, and
route shared logic through the new APIs without breaking existing callers.

Source: Coding guidelines

🟡 Other comments (1)
java/src/main/java/org/lance/Dataset.java-1627-1635 (1)

1627-1635: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the takeBlobs example self-contained.

rowId is never declared, so the copied example does not compile.

Proposed fix
 <pre>{`@code`
+long rowId = 42L;
 List<BlobFile> blobs = dataset.takeBlobs(List.of(rowId), "images");
🤖 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 `@java/src/main/java/org/lance/Dataset.java` around lines 1627 - 1635, Update
the Javadoc example for Dataset.takeBlobs to declare and initialize rowId before
passing it to takeBlobs, using a type and value consistent with the method’s
expected row identifier so the example compiles as a standalone snippet.
🤖 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 `@java/src/main/java/org/lance/Dataset.java`:
- Around line 1638-1640: Update the takeBlobs documentation to identify rowIds
strictly as stable logical _rowid values, not physical _rowaddr addresses, and
clarify that callers needing addresses must use takeBlobsByAddresses. Add
JNI-side validation for rejected _rowaddr inputs if this binding currently
accepts them.

---

Outside diff comments:
In `@rust/lance/src/dataset.rs`:
- Around line 1756-1760: Preserve the existing public return types of the
`take_blobs` methods and add separate nullable replacement APIs returning
`Vec<Option<BlobFile>>`; retain the original methods as deprecated wrappers that
preserve their current non-optional behavior. Apply this consistently to all
`take_blobs*` variants identified in the diff, and route shared logic through
the new APIs without breaking existing callers.

---

Other comments:
In `@java/src/main/java/org/lance/Dataset.java`:
- Around line 1627-1635: Update the Javadoc example for Dataset.takeBlobs to
declare and initialize rowId before passing it to takeBlobs, using a type and
value consistent with the method’s expected row identifier so the example
compiles as a standalone snippet.
🪄 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: 74b9fc55-46cf-490f-930c-bbab28cc82b9

📥 Commits

Reviewing files that changed from the base of the PR and between e40b28c and 65a44ca.

📒 Files selected for processing (3)
  • docs/src/guide/blob.md
  • java/src/main/java/org/lance/Dataset.java
  • rust/lance/src/dataset.rs

Comment thread java/src/main/java/org/lance/Dataset.java Outdated

@westonpace westonpace left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like this behavior a lot better

Comment thread docs/src/guide/blob.md Outdated
Comment thread python/python/lance/dataset.py Outdated
Comment thread python/python/lance/dataset.py Outdated
Comment thread rust/lance/src/dataset/blob.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-docs Documentation A-java Java bindings + JNI A-python Python bindings breaking-change bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants