Skip to content

fix: restore blanks for empty batches and split oversized writes in updater#7318

Open
zhangyang0418 wants to merge 1 commit into
lance-format:mainfrom
zhangyang0418:fix/add-blanks-empty-batch
Open

fix: restore blanks for empty batches and split oversized writes in updater#7318
zhangyang0418 wants to merge 1 commit into
lance-format:mainfrom
zhangyang0418:fix/add-blanks-empty-batch

Conversation

@zhangyang0418

Copy link
Copy Markdown
Contributor

What

Follow-up to #7233. Fixes two more failures that occur when a fully deleted batch sits in the middle of a fragment and columns are added/merged (e.g. via Fragment::merge_columns + LanceOperation::Merge).

Root cause

After #7233 handled the 0-row batch at a read boundary, two related issues remain in updater.rs:

  1. add_blanks rejects empty batches. DeletionRestorer walks the deletion vector and may need to insert blanks for deleted rows that land on a batch whose live rows are all elsewhere, producing an empty input batch with non-empty offsets. add_blanks returned NotSupported ("Missing too many rows in merge, run compaction to materialize deletions first") in that case.
  2. Restored batches can exceed batch_size. DeletionRestorer greedily appends trailing deleted rows to the preceding batch, so the restored batch can be larger than the requested batch_size, producing oversized output file batches.

Fix

  1. add_blanks now builds null rows via new_null_array for an empty input batch instead of erroring.
  2. Updater::update slices the restored batch back down to the write batch size before writing.

Tests

  • add_blanks unit test on an empty batch.
  • add_columns Rust test with a middle fully-deleted batch.
  • Python regression test exercising merge_columns + commit over a dataset with a deletion file.

…pdater

Following the fix for lance-format#7232, two further issues surface when a fully deleted
batch sits in the *middle* of a fragment and columns are added/merged (e.g. via
`Fragment::merge_columns` + `LanceOperation::Merge`):

- `add_blanks` returned `NotSupported` ("Missing too many rows in merge, run
  compaction to materialize deletions first") whenever the input batch was empty
  but deleted offsets still needed restoring. It now builds null rows with
  `new_null_array`, so blanks are restored regardless of whether the input batch
  is empty.
- When restoring deleted rows the `DeletionRestorer` greedily appends trailing
  deleted rows to the preceding batch, which can grow it past the requested
  `batch_size`. `Updater::update` now slices the restored batch back down to the
  write batch size before writing, keeping output file batches bounded.

Adds Rust unit tests (`add_blanks` on an empty batch, `add_columns` with a
middle fully-deleted batch) and a Python regression test exercising
`merge_columns` + commit over a dataset with a deletion file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added A-python Python bindings bug Something isn't working labels Jun 17, 2026
@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.41558% with 12 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance/src/dataset/schema_evolution.rs 80.95% 2 Missing and 6 partials ⚠️
rust/lance/src/dataset/updater.rs 88.57% 1 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

.schema()
.fields()
.iter()
.map(|field| new_null_array(field.data_type(), batch_offsets.len()))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Restoring an empty batch with null placeholders still fails when the first read batch is fully deleted and the added column is non-nullable, so valid add/merge-column operations can error before returning any visible rows.

// SPDX-FileCopyrightText: Copyright The Lance Authors

use arrow_array::{RecordBatch, UInt32Array};
use arrow_array::{new_null_array, RecordBatch, UInt32Array};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The new import order is not rustfmt-formatted, so the format and lint jobs fail before the PR reaches the rest of the Rust checks.

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

Labels

A-python Python bindings bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants