Skip to content

fix: validate merge schema field bindings - #7703

Merged
Xuanwo merged 5 commits into
lance-format:mainfrom
wkalt:ticket/oss-1424/merge-schema-validation
Aug 17, 2026
Merged

fix: validate merge schema field bindings#7703
Xuanwo merged 5 commits into
lance-format:mainfrom
wkalt:ticket/oss-1424/merge-schema-validation

Conversation

@wkalt

@wkalt wkalt commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

External Operation::Merge commits install a caller-supplied schema. Without validating that schema against the current manifest, a renumbered or reused field ID can silently rebind a live column to data from another column. The same risk exists when a shared field ID changes its logical type, nullability, storage encoding, or dictionary while old base or overlay files remain.

Validate field bindings before accepting a merge commit. Existing field IDs must keep the same field path, new IDs must be greater than Manifest::max_field_id(), and semantic binding changes are rejected whenever any old field-bearing file is retained.

Complete physical rewrites remain supported: a binding change is allowed only when every old base and overlay file carrying the field is replaced and every proposed fragment materializes the field in a base data file. Field drops and metadata-only updates remain legal.

Fixes #7700

@github-actions github-actions Bot added the bug Something isn't working label Jul 9, 2026
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0f7835e8-6b34-4eb9-8231-37c32874d42e

📥 Commits

Reviewing files that changed from the base of the PR and between 79772a0 and cc9f74c.

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

📝 Walkthrough

Walkthrough

Adds MERGE schema validation in validate_operation to reject field-id remaps and dropped-id reuse, and expands Tokio tests to cover rejection and acceptance cases around dropped-column datasets.

Changes

Merge schema validation

Layer / File(s) Summary
Merge schema validation logic and wiring
rust/lance/src/dataset/transaction.rs
Adds merge_schema_valid(manifest, new_schema) and calls it from validate_operation for Operation::Merge before fragment/schema checks.
Merge validation tests
rust/lance/src/dataset/transaction.rs
Adds NewColumnTransform usage and Tokio tests covering renumbered field ids, dropped-id reuse, nested path remaps, id-preserving metadata edits, and projected field drops.

Estimated code review effort: 4 (Complex) | ~45 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 summarizes the main change: adding Merge schema field-id validation.
Linked Issues check ✅ Passed The changes implement the requested Merge schema checks for path-preserving ids, new-id bounds, and allowed field drops.
Out of Scope Changes check ✅ Passed The added tests and helper support directly relate to the Merge field-id validation fix and do not appear unrelated.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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
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

🤖 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/dataset/transaction.rs`:
- Around line 4192-4219: The merge-related tests are using in-body loops for
input-only variations and only asserting on error strings, so refactor
test_merge_rejects_renumbered_field_ids,
test_merge_rejects_dropped_field_id_reuse,
test_merge_allows_id_preserving_schema_change, and
test_merge_allows_dropping_field into rstest #[case::...] cases for each input
set, and keep each case independent. For the rejection paths, assert the
returned Error variant from commit_merge (for example Error::InvalidInput) in
addition to checking the expected message text, rather than relying only on
err.to_string().contains(...).
🪄 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: aa3eb425-c5c3-453d-a14c-beb547288fa1

📥 Commits

Reviewing files that changed from the base of the PR and between e96a091 and 79772a0.

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

Comment thread rust/lance/src/dataset/transaction.rs
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

wkalt and others added 2 commits July 9, 2026 13:24
An external Operation::Merge commit installed the caller-supplied schema
without any field id validation. A schema whose field ids were
renumbered (e.g. by a lossy round-trip through a format that does not
store field ids, after a column drop + add) silently rebinds column
data: readers resolve name -> schema id -> DataFile::fields position, so
a live column re-points at a dropped column's bytes.

Add merge_schema_valid to validate_operation's Merge arm:

- Every field id that already exists in the dataset schema must resolve
  to the same field path in the new schema.
- Every field id new to the schema must be greater than
  Manifest::max_field_id(), so the id of a dropped field (still
  referenced by data files) is never reused for a different column.

Dropping fields by omitting them from the merge schema remains legal,
and internal callers (Dataset::merge, add_columns) already assign new
ids above the manifest max, so id-preserving merges are unaffected.

Fixes lance-format#7700
… validation tests

Convert the in-body for loops in test_merge_rejects_renumbered_field_ids,
test_merge_allows_id_preserving_schema_change, and
test_merge_allows_dropping_field to rstest #[case::...] cases so each
input fails independently, and assert Error::InvalidInput on every
rejection path in addition to the message content.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@wkalt
wkalt force-pushed the ticket/oss-1424/merge-schema-validation branch from 79772a0 to cc9f74c Compare July 9, 2026 20:35

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

🤖 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/dataset/transaction.rs`:
- Around line 4201-4202: Replace the temp-directory setup in the new transaction
tests with plain in-memory dataset URIs using memory://, following the
repository test guideline; update the affected test cases around
dataset_with_dropped_column and the other listed scenarios to stop constructing
TempStrDir or any unique filesystem-backed paths, and ensure the helper calls
and assertions still work with in-memory datasets.
- Around line 4243-4244: The test-only inline imports in the transaction tests
should be moved out of the function body and added to the existing module-level
import block. Update the relevant test module around the `StructArray` and
`Fields` usage so the imports are grouped with the other `use` statements at the
top of the file, following the same pattern used elsewhere in `transaction.rs`.
- Line 1864: The next-manifest version calculation in the transaction flow
should not use plain arithmetic because `m.version + 1` can wrap at `u64::MAX`
and produce invalid metadata. Update the logic around the current manifest
handling in the transaction code to use a checked increment when computing the
next version, and return an error if the version would overflow instead of
continuing. Use the existing manifest/version path in the transaction
implementation to locate the change.
- Line 4161: The test helper in transaction.rs is using
RecordBatch::column_by_name, but test code should access columns with batch
indexing instead. Update the helper to use batch["column_name"] style access in
place of the current column_by_name call, keeping the change local to the
test-only logic around the batch variable so it matches the coding guidelines.
- Around line 3892-3907: The merge validation in merge_schema_valid only checks
for field id/path remaps, so shared fields can still change type or nullability
and slip through. Update the shared-field comparison in
rust/lance/src/dataset/transaction.rs to compare each field found in both
prior_schema and new_schema by id, and reject any semantic changes (at least
data type and nullability, and any other field metadata that must remain stable)
before allowing the merge to proceed.
- Around line 3918-3919: The validation error in the transaction flow can
overflow when computing the next field id from max_field_id + 1. Update the
logic in transaction.rs around the max_field_id validation to use checked_add
when deriving the next id, and if max_field_id is i32::MAX return an
exhaustion-style error instead of formatting an overflowing value. Keep the
change localized to the max_field_id validation/error construction path.
🪄 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: 0f7835e8-6b34-4eb9-8231-37c32874d42e

📥 Commits

Reviewing files that changed from the base of the PR and between 79772a0 and cc9f74c.

📒 Files selected for processing (1)
  • rust/lance/src/dataset/transaction.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

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 6

🤖 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/dataset/transaction.rs`:
- Around line 4201-4202: Replace the temp-directory setup in the new transaction
tests with plain in-memory dataset URIs using memory://, following the
repository test guideline; update the affected test cases around
dataset_with_dropped_column and the other listed scenarios to stop constructing
TempStrDir or any unique filesystem-backed paths, and ensure the helper calls
and assertions still work with in-memory datasets.
- Around line 4243-4244: The test-only inline imports in the transaction tests
should be moved out of the function body and added to the existing module-level
import block. Update the relevant test module around the `StructArray` and
`Fields` usage so the imports are grouped with the other `use` statements at the
top of the file, following the same pattern used elsewhere in `transaction.rs`.
- Line 1864: The next-manifest version calculation in the transaction flow
should not use plain arithmetic because `m.version + 1` can wrap at `u64::MAX`
and produce invalid metadata. Update the logic around the current manifest
handling in the transaction code to use a checked increment when computing the
next version, and return an error if the version would overflow instead of
continuing. Use the existing manifest/version path in the transaction
implementation to locate the change.
- Line 4161: The test helper in transaction.rs is using
RecordBatch::column_by_name, but test code should access columns with batch
indexing instead. Update the helper to use batch["column_name"] style access in
place of the current column_by_name call, keeping the change local to the
test-only logic around the batch variable so it matches the coding guidelines.
- Around line 3892-3907: The merge validation in merge_schema_valid only checks
for field id/path remaps, so shared fields can still change type or nullability
and slip through. Update the shared-field comparison in
rust/lance/src/dataset/transaction.rs to compare each field found in both
prior_schema and new_schema by id, and reject any semantic changes (at least
data type and nullability, and any other field metadata that must remain stable)
before allowing the merge to proceed.
- Around line 3918-3919: The validation error in the transaction flow can
overflow when computing the next field id from max_field_id + 1. Update the
logic in transaction.rs around the max_field_id validation to use checked_add
when deriving the next id, and if max_field_id is i32::MAX return an
exhaustion-style error instead of formatting an overflowing value. Keep the
change localized to the max_field_id validation/error construction path.
🪄 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: 0f7835e8-6b34-4eb9-8231-37c32874d42e

📥 Commits

Reviewing files that changed from the base of the PR and between 79772a0 and cc9f74c.

📒 Files selected for processing (1)
  • rust/lance/src/dataset/transaction.rs
🛑 Comments failed to post (6)
rust/lance/src/dataset/transaction.rs (6)

1864-1864: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1848,1880p' rust/lance/src/dataset/transaction.rs

Repository: lance-format/lance

Length of output: 1569


🏁 Script executed:

sed -n '2288,2365p' rust/lance/src/dataset/transaction.rs

Repository: lance-format/lance

Length of output: 3696


🏁 Script executed:

sed -n '3700,4360p' rust/lance/src/dataset/transaction.rs

Repository: lance-format/lance

Length of output: 25316


🏁 Script executed:

rg -n "max_field_id|merge_schema_valid|checked_add|version \+ 1|manifest.version" rust/lance/src/dataset/transaction.rs

Repository: lance-format/lance

Length of output: 1102


🏁 Script executed:

rg -n "struct Manifest|pub version:|version: .*u64|version: .*i32|type Version" rust/lance/src/dataset rust/lance/src -g '!**/target/**'

Repository: lance-format/lance

Length of output: 16358


🏁 Script executed:

sed -n '1,220p' rust/lance/src/dataset/manifest.rs

Repository: lance-format/lance

Length of output: 234


🏁 Script executed:

sed -n '1,120p' rust/lance/src/dataset/transaction.rs

Repository: lance-format/lance

Length of output: 5710


Use checked_add for the next manifest version. m.version + 1 can wrap at u64::MAX, which would write an invalid version into manifest/WAL metadata. Return an error 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/dataset/transaction.rs` at line 1864, The next-manifest
version calculation in the transaction flow should not use plain arithmetic
because `m.version + 1` can wrap at `u64::MAX` and produce invalid metadata.
Update the logic around the current manifest handling in the transaction code to
use a checked increment when computing the next version, and return an error if
the version would overflow instead of continuing. Use the existing
manifest/version path in the transaction implementation to locate the change.

Source: Coding guidelines


3892-3907: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether downstream validation already rejects type/nullability changes for shared ids.
rg -nP 'fn schema_fragments_valid|schema_fragments_valid\(|field_by_id|DataType|nullable' rust/lance/src/dataset/transaction.rs rust/lance-core/src/datatypes/schema.rs

Repository: lance-format/lance

Length of output: 29945


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the merge/schema validation logic around the cited code and the shared-id validator.
sed -n '3753,3925p' rust/lance/src/dataset/transaction.rs

printf '\n--- schema.rs relevant validation ---\n'
sed -n '860,980p' rust/lance-core/src/datatypes/schema.rs

Repository: lance-format/lance

Length of output: 11153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the merge flow around schema validation and how the new schema is applied.
sed -n '3600,3925p' rust/lance/src/dataset/transaction.rs

printf '\n--- any schema comparison hooks in transaction.rs ---\n'
rg -n 'compare_fields|compare_with_options|SchemaCompareOptions|allow_missing_if_nullable|allow_subschema|merge_schema_valid' rust/lance/src/dataset/transaction.rs rust/lance-core/src/datatypes/schema.rs

Repository: lance-format/lance

Length of output: 16356


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect field comparison semantics to see what counts as a schema change.
sed -n '1,260p' rust/lance-core/src/datatypes/field.rs

printf '\n--- compare-related uses ---\n'
rg -n 'compare_with_options|metadata|nullable|data_type' rust/lance-core/src/datatypes/field.rs rust/lance-core/src/datatypes/schema.rs

Repository: lance-format/lance

Length of output: 31608


Reject shared-id schema changes that alter field semantics. In rust/lance/src/dataset/transaction.rs:3889-3907, merge_schema_valid() only blocks id/path remaps, so a merge can keep the same field id and path while changing type or nullability and still pass. Compare shared fields by id before accepting the new schema.

🤖 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/transaction.rs` around lines 3892 - 3907, The merge
validation in merge_schema_valid only checks for field id/path remaps, so shared
fields can still change type or nullability and slip through. Update the
shared-field comparison in rust/lance/src/dataset/transaction.rs to compare each
field found in both prior_schema and new_schema by id, and reject any semantic
changes (at least data type and nullability, and any other field metadata that
must remain stable) before allowing the merge to proceed.

3918-3919: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '3890,3945p' rust/lance/src/dataset/transaction.rs

Repository: lance-format/lance

Length of output: 2446


🏁 Script executed:

rg -n "fn max_field_id|max_field_id\(" rust/lance/src rust/lance-core/src rust/lance-table/src

Repository: lance-format/lance

Length of output: 3703


🏁 Script executed:

rg -n "field.id|struct Field|type .*FieldId|i32::MAX|max_field_id" rust/lance/src rust/lance-core/src rust/lance-table/src

Repository: lance-format/lance

Length of output: 50374


🏁 Script executed:

sed -n '400,440p' rust/lance-table/src/format/manifest.rs

Repository: lance-format/lance

Length of output: 1717


🏁 Script executed:

sed -n '650,710p' rust/lance-core/src/datatypes/schema.rs

Repository: lance-format/lance

Length of output: 2536


🏁 Script executed:

sed -n '1,120p' rust/lance-core/src/datatypes/field.rs

Repository: lance-format/lance

Length of output: 4464


🏁 Script executed:

sed -n '1,220p' rust/lance-core/src/datatypes/field.rs | rg -n "set_id|max_id|id:"

Repository: lance-format/lance

Length of output: 205


🏁 Script executed:

sed -n '720,780p' rust/lance/src/dataset/schema_evolution.rs

Repository: lance-format/lance

Length of output: 2566


Avoid overflowing max_field_id + 1 in the validation error.

If manifest.max_field_id() is i32::MAX, this + 1 panics in debug and wraps in release while formatting the error. Compute the next id with checked_add and return an exhaustion error 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/dataset/transaction.rs` around lines 3918 - 3919, The
validation error in the transaction flow can overflow when computing the next
field id from max_field_id + 1. Update the logic in transaction.rs around the
max_field_id validation to use checked_add when deriving the next id, and if
max_field_id is i32::MAX return an exhaustion-style error instead of formatting
an overflowing value. Keep the change localized to the max_field_id
validation/error construction path.

Source: Coding guidelines


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

Use batch indexing in tests.

This new test helper uses column_by_name; test code should use batch[...] for RecordBatch column access.

As per coding guidelines: "Use column_by_name() for RecordBatch column access in production code; use batch["column_name"] in tests."

Proposed fix
-            let col = batch.column_by_name(name).unwrap();
+            let col = &batch[*name];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            let col = &batch[*name];
🤖 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/transaction.rs` at line 4161, The test helper in
transaction.rs is using RecordBatch::column_by_name, but test code should access
columns with batch indexing instead. Update the helper to use
batch["column_name"] style access in place of the current column_by_name call,
keeping the change local to the test-only logic around the batch variable so it
matches the coding guidelines.

Source: Coding guidelines


4201-4202: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use memory:// for these tests.

The new tests allocate temp directories; repository guidance prefers plain in-memory URIs for tests.

As per coding guidelines: "Use plain "memory://" URIs in tests; no atomic counters or unique suffixes are needed."

Example fix
-        let test_dir = TempStrDir::default();
-        let dataset = dataset_with_dropped_column(test_dir.as_str(), dropped).await;
+        let dataset = dataset_with_dropped_column("memory://", dropped).await;

Also applies to: 4218-4219, 4246-4247, 4297-4298, 4330-4331

🤖 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/transaction.rs` around lines 4201 - 4202, Replace the
temp-directory setup in the new transaction tests with plain in-memory dataset
URIs using memory://, following the repository test guideline; update the
affected test cases around dataset_with_dropped_column and the other listed
scenarios to stop constructing TempStrDir or any unique filesystem-backed paths,
and ensure the helper calls and assertions still work with in-memory datasets.

Source: Coding guidelines


4243-4244: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move test imports to the module import block.

These inline use statements should be hoisted next to the other test imports.

As per coding guidelines: "Place use imports at the top of the file, not inline within function bodies."

Proposed fix
-    use arrow_array::{Int32Array, RecordBatch, RecordBatchIterator};
-    use arrow_schema::{DataType, Field as ArrowField, Schema as ArrowSchema};
+    use arrow_array::{Int32Array, RecordBatch, RecordBatchIterator, StructArray};
+    use arrow_schema::{DataType, Field as ArrowField, Fields, Schema as ArrowSchema};
...
-        use arrow_array::StructArray;
-        use arrow_schema::Fields;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.


🤖 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/transaction.rs` around lines 4243 - 4244, The
test-only inline imports in the transaction tests should be moved out of the
function body and added to the existing module-level import block. Update the
relevant test module around the `StructArray` and `Fields` usage so the imports
are grouped with the other `use` statements at the top of the file, following
the same pattern used elsewhere in `transaction.rs`.

Source: Coding guidelines

Comment thread rust/lance/src/dataset/transaction.rs Outdated

// Remap errors first: a renumbered schema usually violates both clauses.
for field in new_schema.fields_pre_order() {
if prior_schema.field_by_id(field.id).is_none() {

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.

This only validates ID-to-path bindings for IDs that already exist. It does not validate path-to-ID or the schema semantics attached to a reused ID. For example, a(id=0) can be submitted as a(id=max+1) with unchanged fragments; validation passes, the manifest installs the new ID, and reads synthesize NULL because the files still contain field 0. The same ID and path can also change Int32 to Float32 or nullable to non-nullable, bypassing the existing schema-evolution checks and reinterpreting old files. These cases still permit silent data loss or corruption through Merge.

}
}

let max_field_id = manifest.max_field_id();

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.

On current main, Manifest::max_field_id() scans the schema and base fragment files but not DataOverlayFile.data_file.fields, while Project retains overlays. If an overlay-only field is dropped, the high-water mark can fall, this check can accept the old ID for a new column, and the retained overlay values become bound to that new column. Overlay datasets therefore remain vulnerable to field-ID reuse.

@Xuanwo
Xuanwo requested a review from lance-community August 10, 2026 17:13
@lance-gatekeeper
lance-gatekeeper Bot removed the request for review from lance-community August 10, 2026 17:13
@Xuanwo Xuanwo changed the title fix: validate schema field ids in Merge operation commits fix: validate merge schema field bindings Aug 17, 2026
@Xuanwo
Xuanwo merged commit d99968a into lance-format:main Aug 17, 2026
44 of 46 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Merge commit accepts a schema with renumbered field ids and silently remaps column data

2 participants