Add nested struct schema evolution support for Map types - #23914
Conversation
- Adapt recursive Map key/value structures. - Implement shared planner/runtime validation. - Preserve offsets and nulls; require matching sorted flags. - Enforce Arrow Map entry/key invariants. - Handle nullable additions, extras, null/all-null maps, and incompatible/non-nullable rejections. - Add Parquet end-to-end regression tests.
…etadata internally - Updated cast_map_column to accept &MapArray and reduce redundant arguments - Extracted a shared error assertion helper for planner/runtime components
- Map key/value children now matched positionally while preserving target technical names. - Implemented safe key evolution with no source key-field removal, injective primitive widening, nullable additions, and unchanged key types for sorted Maps. - Compacted hidden null-parents and sliced unreachable entries before casting. - Updated public rustdoc to document Map evolution semantics. - Expanded Parquet test coverage for differing physical/logical entry names. - Added planner/runtime regressions tests for uniqueness, sortedness, nulls, slices, and unsafe casts.
- Allow sorted targets to be set to false. - Reject transitioning from sorted=false to sorted=true. - Maintain requirement for unchanged key types in sorted targets. - Allow downgraded unsorted targets to utilize existing safe key evolution. - Update public Rust documentation. - Add planner/runtime parity and value/key assertions.
- Reused validated target Map children and eliminated duplicate validation and map_entry_fields. - Implemented common planner/runtime error assertion helper. - Removed redundant sortedness assertions that are already covered by exact type equality. - Public APIs remain unchanged.
…structs at planner boundary - Added regression test for empty key struct to ensure nullable-only target key field is rejected in both planner and runtime.
…chema helpers - Added `test_map_value_struct_incompatible_schema_evolution_rejected` for end-to-end negative testing of Parquet. - Refactored shared map fixture and schema helper functions for improved clarity and reusability.
- Removed unused `let _ =` in nested_struct.rs - Added local name lookup/set for map key struct validation in nested_struct.rs - Extracted shared map E2E setup helper in expr_adapter.rs - Added nullable/non-nullable schema wrapper helpers, avoiding bool at call sites in expr_adapter.rs
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #23914 +/- ##
==========================================
+ Coverage 81.27% 82.31% +1.03%
==========================================
Files 1116 1117 +1
Lines 395017 415524 +20507
Branches 395017 415524 +20507
==========================================
+ Hits 321055 342032 +20977
- Misses 55166 55208 +42
+ Partials 18796 18284 -512 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- Updated the sorted to unsorted test to enforce rejection. - Clarified documentation for requires_nested_struct_cast Map behavior. - Extracted compact_map_entries() with explanation for identity take. - Extracted is_injective_map_key_cast() and added documentation for conservative policy.
… cast_map_column and rewording to “specialized Map casting path”
|
@TheBuilderJR |
|
yep lgtm. thanks @kosiew |
adriangb
left a comment
There was a problem hiding this comment.
Overall looks great!
I think this requires an upgrade guide entry since it's a change in behavior.
Could we add / move some tests to SLTs in https://github.com/apache/datafusion/blob/main/datafusion/sqllogictest/test_files/map.slt
|
This may be a bug: |
…and prevent panic on invalid casts - Change `StructArray::new` to `try_new(...)` in struct and map casts, using `?` to propagate errors. - Safe invalid casts now return Arrow/DataFusion errors instead of panicking. - Add regression tests for non‑null struct field and map value.
- Added Map-vs-List-family rationale comment.
… non‑injective same‑width signed targets, fix UInt32::MAX → Int64 runtime regression, and handle UNION SLT regression with result‑type assertion
…n adaptation for structs, add regression tests, update rustdoc - Semantic Map key/value inspection no longer triggers nested path for technical entries - Plain Maps → Arrow cast/validation - Struct-containing Maps retain specialized adaptation - Add unit + map.slt INSERT/duplicate/null-key regression tests - Update rustdoc
…ible map regressions; keep array/schema‑layout cases as Rust unit and Parquet integration tests
|
@kosiew ill let you decide when this is ready to merge |
2dc4db2 to
e2c55a5
Compare
|
🚀 |
…tation Adds two `MemoryStream` regression tests requested during review of the apache#24394 backport: - Dense Union with nullable declared children and non-nullable runtime children, exercising the Union reconstruction path at the producer boundary and asserting preserved type IDs and child values. - `Map<Utf8, Struct<v>>` where the runtime nested field is non-nullable and the declared nested field is nullable. This passes on branch-55 without a `DataType::Map` arm in `cast_column`: `Schema::contains` accepts the stricter shape and Arrow's generic Map cast recursively casts the value Struct and rebuilds it with the target fields. No backport of the Map prerequisite from apache#23914 is required. Both assert `emitted_batch.schema() == stream.schema()`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Which issue does this PR close?
Closes #20835
This is the last of a series of PR to close #20835
Rationale for this change
Map schema adaptation did not have explicit recursive evolution support when Map keys or values contain nested Structs.
This PR defines and implements Map evolution semantics so nested Struct fields can evolve consistently with existing Struct schema adaptation while preserving Map key identity. Both Map keys and values may evolve when they recursively contain Structs. Struct fields are matched by case-sensitive name, nullable target fields may be added as
NULL, and source-only value fields may be omitted.Because Map keys determine lookup identity, key evolution is more restrictive: source key Struct fields cannot be removed, primitive key casts are limited to known injective widenings, and sorted Maps require an unchanged key type. Source and target sorted flags must also match.
What changes are included in this PR?
Adds Map handling to the central nested Struct compatibility and casting paths.
Matches Map entry key/value children by semantic position rather than their technical entry field names, while continuing to match nested Struct fields by name.
Supports recursive Struct evolution in both Map keys and Map values.
Restricts Map key evolution to preserve key identity:
Requires source and target Map sorted flags to match.
Validates Map entry invariants, including non-nullable entries and keys.
Compacts sliced Maps and entries hidden by null parent rows before recursively casting their children, so unreachable entries do not affect casts.
Uses
StructArray::try_newwhen rebuilding Struct arrays so invalid nullability introduced by safe casts is reported as an error.Keeps Maps without semantic Struct children on Arrow's existing Map cast path.
Adds upgrade documentation describing the Map nested Struct evolution semantics and migration considerations.
Are these changes tested?
Yes. The patch adds unit, integration, and SQL logic coverage for the new behavior, including:
Int32→Int64andUInt32→Int64Map key casts.requires_nested_struct_castbehavior for Maps.UInt32/Int32key coercion toInt64.Are there any user-facing changes?
Yes. Maps whose keys or values recursively contain Structs now support schema evolution through DataFusion's nested adaptation path.
Nested Struct fields are matched by case-sensitive name. Nullable target fields can be added and populated with
NULL; incompatible changes and missing non-nullable target fields are rejected. Map key evolution has additional restrictions to preserve key identity, and sorted Maps require an unchanged key type and matching sorted flag.Ordinary Maps without semantic Struct children continue to use Arrow's existing Map cast behavior.
The upgrade guide for 55.0.0 documents these semantics and the migration considerations.
LLM-generated code disclosure
This PR includes LLM-generated code and comments. All LLM-generated content has been manually reviewed.