Skip to content

feat: support merge_insert with blob encoding column#7615

Open
nyl3532016 wants to merge 5 commits into
lance-format:mainfrom
nyl3532016:main
Open

feat: support merge_insert with blob encoding column#7615
nyl3532016 wants to merge 5 commits into
lance-format:mainfrom
nyl3532016:main

Conversation

@nyl3532016

@nyl3532016 nyl3532016 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

merge_insert directly invokes session_ctx.read_lance_unordered without specifying Blob column handling, which defaults to BlobsDescriptions. Consequently, DataFusion retrieves Blob data as a Struct type description when scanning the original dataset, causing a crash with "LargeBinary vs Struct schema mismatch!" because the table's logical schema expects LargeBinary.
Interestingly, this only happens if the input data for merge_insert lacks Blob columns; if Blob columns are present in the input, column pruning is triggered during the original data read, bypassing the Blob columns and masking the bug.

Summary by CodeRabbit

  • New Features
    • Added configurable blob handling for scans via an optional blob_handling setting on the table provider.
    • Introduced a builder method to set blob handling when creating a provider with ordering.
  • Bug Fixes
    • Improved merge-insert/upsert planning/execution for datasets with nullable blob-typed columns, preserving existing blob values and correctly nulling missing blobs on inserts.
  • Deprecations
    • Deprecated the unordered read API in favor of the ordered read API.
  • Tests
    • Added coverage for merge-insert/upsert with nullable blob columns.

@github-actions github-actions Bot added the enhancement New feature or request label Jul 3, 2026
@nyl3532016 nyl3532016 force-pushed the main branch 2 times, most recently from 34389ac to 28641eb Compare July 4, 2026 00:05
@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.11321% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance/src/dataset/write/merge_insert.rs 98.01% 0 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@nyl3532016

Copy link
Copy Markdown
Contributor Author

@jackye1995 @wjones127 Hi, this pull request is similar to #5903. please help take a look.

@wjones127 wjones127 self-requested a review July 6, 2026 23:38
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds optional blob handling to LanceTableProvider, deprecates the unordered Lance read helper, and updates merge insert planning to use the blob-aware provider with a new blob-column test.

Changes

Blob handling support

Layer / File(s) Summary
LanceTableProvider blob handling wiring
rust/lance/src/datafusion/dataframe.rs
Adds a blob_handling field, a with_blob_handling(...) builder method, and applies it conditionally in TableProvider::scan before projection/filter/limit.
Deprecate unordered Lance read
rust/lance/src/datafusion/dataframe.rs
Marks read_lance_unordered(...) as deprecated and points callers to read_lance(...).
merge_insert uses blob-aware scan
rust/lance/src/dataset/write/merge_insert.rs
Changes the target scan to use LanceTableProvider::new_with_ordering(...).with_blob_handling(BlobHandling::AllBinary) and adds test_merge_insert_with_blob covering blob retention and null insertion.

Sequence Diagram(s)

sequenceDiagram
  participant MergeInsertJob
  participant LanceTableProvider
  participant DatasetScan
  MergeInsertJob->>LanceTableProvider: build ordered target scan
  LanceTableProvider->>DatasetScan: set BlobHandling::AllBinary
  DatasetScan-->>MergeInsertJob: provide blob-aware target rows for merge planning
Loading

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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: adding merge_insert support for datasets with blob-encoded columns.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% 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 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: 4

🤖 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/src/datafusion/dataframe.rs`:
- Around line 80-83: Add a doc comment for the public builder method
DataFrame::with_blob_handling so it documents the semantic effect on blob column
materialization during scans, the valid BlobHandling values, and the impact on
the returned DataFrame; place the documentation directly above
with_blob_handling and make sure it satisfies the public API docs requirement,
including a brief usage example if appropriate.
- Line 180: The removal of read_lance_unordered from SessionContextExt is a
breaking public API change because the trait is exported through the crate root;
keep the method available for now and mark it deprecated instead of deleting it
outright. Update the SessionContextExt trait in dataframe.rs to preserve a
compatibility shim, and if needed route it to the new ordered/alternative path
so downstream callers can migrate before the method is removed in a major
release.

In `@rust/lance/src/dataset/write/merge_insert.rs`:
- Around line 1661-1670: The target scan in merge_insert currently forces
LanceTableProvider::new_with_ordering(...).with_blob_handling(BlobHandling::AllBinary),
which materializes every blob column into memory. Update this path so blob
materialization is not always forced during the join/rewrite, and instead only
enable AllBinary when it is strictly needed for the schema mismatch fix or make
it configurable/lazy via the merge_insert scan setup around
session_ctx.read_table(provider). Keep the ordering/provider behavior intact
while reducing memory pressure for blob-heavy datasets.
- Around line 11525-11528: The comment in the merge insert test path is stale
and should be updated to reflect the current successful behavior. In
merge_insert.rs, remove the outdated “will crash” wording near
execute_reader/new_reader, and inline the execute_reader(new_reader).await
unwrap instead of keeping the temporary result binding that only exists for the
comment. Keep the remaining comment forward-looking and aligned with the actual
semantics of job.execute_reader and the partial update flow.
🪄 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: 86948e8c-be7d-4ee3-a921-2a3d50e10568

📥 Commits

Reviewing files that changed from the base of the PR and between d217a02 and c485eb4.

📒 Files selected for processing (2)
  • rust/lance/src/datafusion/dataframe.rs
  • rust/lance/src/dataset/write/merge_insert.rs

Comment thread rust/lance/src/datafusion/dataframe.rs
Comment thread rust/lance/src/datafusion/dataframe.rs
Comment thread rust/lance/src/dataset/write/merge_insert.rs
Comment thread rust/lance/src/dataset/write/merge_insert.rs Outdated
nyl3532016 and others added 3 commits July 9, 2026 17:48
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@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/src/datafusion/dataframe.rs (1)

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

Document blob_handling field semantics.

blob_handling: Option<lance_core::datatypes::BlobHandling> has no doc comment explaining what None vs Some(...) means for scan behavior (defaults to dataset's own policy vs override).

📝 Suggested doc
+    /// Overrides how blob columns are materialized during `scan`.
+    /// `None` uses the underlying dataset's default blob-handling policy.
     blob_handling: Option<lance_core::datatypes::BlobHandling>,

As per coding guidelines: "Document the semantic meaning of both present and absent states for Option<T> fields."

🤖 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/datafusion/dataframe.rs` at line 45, Add a doc comment for the
`blob_handling` field in `DataFrame` explaining the meaning of both states:
`None` means scans use the dataset’s configured blob handling policy, while
`Some(...)` overrides that policy for the scan. Keep the comment near
`blob_handling` so its semantics are clear alongside the `DataFrame` definition.

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/datafusion/dataframe.rs`:
- Line 45: Add a doc comment for the `blob_handling` field in `DataFrame`
explaining the meaning of both states: `None` means scans use the dataset’s
configured blob handling policy, while `Some(...)` overrides that policy for the
scan. Keep the comment near `blob_handling` so its semantics are clear alongside
the `DataFrame` definition.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 717e49c7-edf4-4c97-a374-fb3cc7091c68

📥 Commits

Reviewing files that changed from the base of the PR and between 1174054 and be12e42.

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

@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/src/datafusion/dataframe.rs (1)

186-196: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove #[allow(dead_code)] here read_lance_unordered is a required trait signature, so the suppression is unnecessary; keep only #[deprecated(note = "use read_lance instead")].

🤖 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/datafusion/dataframe.rs` around lines 186 - 196, The
`read_lance_unordered` trait method in `DataFrame` is a required signature, so
the `#[allow(dead_code)]` suppression is unnecessary. Remove that attribute from
the `read_lance_unordered` declaration and keep the existing `#[deprecated(note
= "use read_lance instead")]` annotation unchanged.

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/datafusion/dataframe.rs`:
- Around line 186-196: The `read_lance_unordered` trait method in `DataFrame` is
a required signature, so the `#[allow(dead_code)]` suppression is unnecessary.
Remove that attribute from the `read_lance_unordered` declaration and keep the
existing `#[deprecated(note = "use read_lance instead")]` annotation unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7b1ceabd-2f9c-4c35-b45f-1166e94a1c5f

📥 Commits

Reviewing files that changed from the base of the PR and between be12e42 and e4ac3e4.

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

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

The overall fix looks good. There's two things I'd like to see addressed before we merge.

Comment on lines +11460 to +11461
#[tokio::test]
async fn test_merge_insert_with_blob() {

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.

suggestion: could we parametrize this by data_storage_version and also test V2_2? That contains the new blob V2, which I think we should try to cover in this test.

Comment on lines +188 to +189
#[allow(dead_code)]
#[deprecated(note = "use read_lance instead")]

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.

issue(blocking): is this dead code? This is a private method, so if it's truly dead we should delete it.

@FANNG1

FANNG1 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

1. Does reading the target with AllBinary pull the whole blob column into memory?

In create_plan the target scan is the build side of the join, and every dataset column missing from the source is pulled in from the target. On the path this PR fixes, that seems to mean the entire blob column gets materialized and buffered, which would raise the OOM risk on large blob tables. update.rs:284 bounds its read with a filter first; I don't see an equivalent bound here.

Is that reading correct, or is there something limiting it that I'm missing?

2. Is blob v2 work as expected?

Could the test be extended to V2_2 with a blob v2 struct column? In particular I'd be interested in whether an external URI blob survives merge_insert unchanged, or whether it gets ingested into a local copy.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants