Skip to content

fix(core): mark new columns nullable when merging schemas on write - #4833

Merged
ethan-tyler merged 3 commits into
delta-io:mainfrom
Shahzeb-DataEngineer:fix/merge-schema-new-fields-nullable
Oct 10, 2026
Merged

ethan-tyler merged 3 commits into
delta-io:mainfrom
Shahzeb-DataEngineer:fix/merge-schema-new-fields-nullable

Conversation

@Shahzeb-DataEngineer

Copy link
Copy Markdown
Contributor

Description

Appending with schema_mode="merge" added new columns with the nullability declared by the incoming batch. A non-nullable column in the batch became non-nullable in the table schema even though existing rows have no values for it — so a later append of null to that column was rejected by delta-rs's own NOT NULL check, and for fields nested inside an existing struct the table could become unreadable (ArrowTypeError: struct fields don't match).

This change forces genuinely new fields to nullable when merge_arrow_schema preserves new fields (the schema-merge write paths), mirroring Delta Lake Spark, which calls schema.asNullable before merging. merge_delta_struct (used by ADD COLUMN, where nullability is an explicit DDL choice) is intentionally left unchanged.

Closes #4810.

Testing

  • Added unit tests merge_arrow_schema_new_top_level_field_is_nullable and merge_arrow_schema_new_nested_field_is_nullable
  • cargo test -p deltalake-core --lib kernel::schema::cast: 33 passed, 0 failed
  • cargo fmt clean

AI usage disclosure

Per CONTRIBUTING.md: I used an AI coding assistant while working on this fix. I read the issue, traced the write path (operations/write/plan.rs → merge_arrow_schema), reviewed all merge_arrow_* / merge_delta_struct call sites to scope the change so ADD COLUMN semantics are untouched, and verified by compiling the crate and running the unit tests (including the two new ones). Happy to adjust the approach if maintainers prefer the fix applied elsewhere.

Appending with schema_mode="merge" added new columns with their incoming
nullability, so a non-nullable column from the batch became non-nullable in
the table even though existing rows have no values for it. Later appends of
null to that column were then rejected, and for nested structs the table
could become unreadable. New fields merged into an existing table are now
forced nullable, mirroring Delta Spark which calls schema.asNullable before
merging. Adds unit tests for top-level and nested new fields.

Closes delta-io#4810

Signed-off-by: Shahzeb Ali <[email protected]>
Copilot AI balanced review requested due to automatic review settings October 8, 2026 09:44

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added the binding/rust Issues for the Rust crate label Oct 8, 2026
@ethan-tyler ethan-tyler self-assigned this Oct 8, 2026
Shahzeb-DataEngineer and others added 2 commits October 9, 2026 09:56
RecordBatchWriter's merge_and_conform path requires exact type equality
in conform_to_schema, so a newly-added nested struct child (merged as
nullable) failed the write with SchemaMismatch: the batch's StructArray
still declares the child non-nullable.

Teach conform_to_schema to cast columns whose types match up to
nullability to the merged field type (a data no-op), keeping the hard
SchemaMismatch for genuine type changes. Adds regression tests for the
nested and top-level new-field cases on the WriteMode::MergeSchema path.

Signed-off-by: Shahzeb Ali <[email protected]>
@ethan-tyler
ethan-tyler enabled auto-merge (rebase) October 10, 2026 05:46
@codecov

codecov Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.82677% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.96%. Comparing base (17e6c55) to head (4b4cf26).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
crates/core/src/writer/record_batch.rs 80.00% 15 Missing and 2 partials ⚠️
crates/core/src/kernel/schema/cast/merge_schema.rs 97.61% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4833      +/-   ##
==========================================
+ Coverage   81.95%   81.96%   +0.01%     
==========================================
  Files         200      200              
  Lines       74551    74677     +126     
  Branches    74551    74677     +126     
==========================================
+ Hits        61100    61211     +111     
- Misses       9872     9884      +12     
- Partials     3579     3582       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ethan-tyler
ethan-tyler merged commit 8eaf9be into delta-io:main Oct 10, 2026
28 of 32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

binding/rust Issues for the Rust crate

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Bug]: schema_mode="merge" adds a new column as non-nullable to a non-empty table

3 participants