feat: support list blob scans#7664
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
# Conflicts: # rust/lance-core/src/datatypes/schema.rs
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughBlob V2 handling validates descriptors more strictly, supports binary views for nested list fields, preserves descriptor nulls, and materializes binary payloads during filtered, unfiltered, and take execution paths. ChangesBlob V2 binary flow
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant ScanPlanner
participant FragmentReader
participant BlobV2ReadContext
participant BlobMaterializer
ScanPlanner->>FragmentReader: select descriptor schema and row addresses
FragmentReader->>BlobMaterializer: provide decoded descriptor batch
BlobMaterializer->>BlobV2ReadContext: resolve descriptor payload locations
BlobV2ReadContext->>BlobMaterializer: return payload bytes
BlobMaterializer->>ScanPlanner: return public binary batch
Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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-core/src/datatypes/field.rs`:
- Around line 603-611: In the relevant field conversion logic, replace the bare
unwrap on LogicalType::try_from(&DataType::LargeBinary) with expect containing a
clear reason, and replace the duplicated "packed" and "lance-encoding:packed"
metadata removals with iteration over the existing PACKED_KEYS constant used by
is_packed_struct.
In `@rust/lance/src/dataset/blob.rs`:
- Around line 2338-2361: Update the blob-v2 filtered-read materialization around
materialize_blob_v2_binary_array and FilteredReadExec schema handling so
requested system columns (_rowid, _row_last_updated_at_version, and
_row_created_at_version) are preserved alongside _rowaddr. Ensure
projection.to_bare_schema() does not drop them, and that the produced columns
and output schema match FilteredReadExec::schema() for every requested system
column.
🪄 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: 02fd15aa-ede3-45ce-859c-38adabfb62c3
📒 Files selected for processing (6)
rust/lance-core/src/datatypes/field.rsrust/lance-core/src/datatypes/schema.rsrust/lance-encoding/src/encodings/logical/blob.rsrust/lance/src/dataset/blob.rsrust/lance/src/io/exec/filtered_read.rsrust/lance/src/io/exec/scan.rs
| for (idx, row_addr) in row_addrs.iter().copied().enumerate() { | ||
| if descriptions.is_null(idx) || columns.kinds.is_null(idx) { | ||
| builder.append_null(); | ||
| continue; | ||
| } | ||
|
|
||
| match kind { | ||
| let kind = BlobKind::try_from(columns.kinds.value(idx))?; | ||
| if matches!(kind, BlobKind::Inline) | ||
| && columns.positions.value(idx) == 0 | ||
| && columns.sizes.value(idx) == 0 | ||
| { | ||
| builder.append_value([]); | ||
| continue; | ||
| } | ||
|
|
||
| let entry = read_context | ||
| .collect_entry(&columns, idx, idx, row_addr) | ||
| .await?; | ||
| if let Some(entry) = entry { | ||
| let data = entry.file.read().await?; | ||
| builder.append_value(data.as_ref()); |
There was a problem hiding this comment.
Blob payloads are awaited one at a time here, so a list with N remote blobs needs roughly N sequential round trips and BlobSource cannot coalesce the reads. Filtered scans also pay this cost before applying the residual filter.
lance 9.0.0-rc.1 picked up lance-format/lance#7664, which stopped the blob v2 encoder from emitting a redundant rep/def validity layer. PrimitiveStructuralEncoder already derives validity via extract_validity, so v8 pushed two layers where the packed-struct decoder reads one -- it consumed the bogus AllValidItem at index 0 and the null was gone. The practical effect is that lance <= 8 never round-tripped a null blob at all: it wrote the null away, and every reader (v8 included) saw a non-null zero-filled descriptor. Verified by writing a null blob with lance 8 and reading it back with both versions -- identical zero-filled result, so v9 changes nothing for existing data and there is no read-side compatibility break. Only new writes differ, and they are now correct. So these assertions were encoding the bug: they checked the descriptor was zero-filled, which is precisely the signature of the dropped null, while the test names promised the null was preserved. Assert the null descriptor instead, and add the value-level null check the names always implied but could not pass before. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Summary
This adds scan-based support for
List<Blob>and nested blob leaves such asStruct<List<Blob>>.Descriptor scans preserve the original Arrow nesting and expose blob leaves as descriptors without loading payload bytes.
BlobHandling::AllBinarymaterializes only blob leaves to binary values while keeping the surroundingList/Structlayout intact.This keeps the existing one-blob-per-row APIs unchanged and avoids adding a public random-access API for list blob values.
Summary by CodeRabbit