Repository navigation
Fix map projection when no value leaf matches the table - #3658
Open
Baunsgaard wants to merge 1 commit into
Open
Baunsgaard wants to merge 1 commit into
Baunsgaard wants to merge 1 commit into
Conversation
Parquet can only read a map when both its key and value are projected. When a name-mapped or embedded-id map value is a struct whose leaves match no table field, only the key was selected and the read failed with "Map field must have exactly 2 fields" (apache#3557). - Convert selected leaves one at a time instead of converting a pre-projected schema that can contain a key-only map. - When a map key is read but no value leaf is, also read the value leaves so the value comes back as a struct of NULLs (apache#3437). - Only rescue struct values: a primitive value is matched by position and would be cast to the table type. - Never rescue leaves with an invalid type promotion; fail with DataInvalid if no value leaf is left to read. - Keep rejecting duplicate field ids in the file.
There was a problem hiding this comment.
🟡 Changes recommended
Critical projection issues can silently lose data through duplicate ancestor IDs and allow nested list/map casts to bypass type-promotion validation.
2 open issues
What changed in this PR
This PR addresses #3557 in the Arrow reader: projecting a map fails when none of its value struct’s leaves match the table schema. It aims to preserve map readability so missing struct fields can be returned as NULLs.
Changes:
- Converts selected leaves individually to avoid converting incomplete map schemas.
- Adds map value leaves when needed, excluding leaves rejected by type-promotion checks.
- Adds regression tests for name mapping, unmatched field IDs, and incompatible value types.
The PR description could be shortened by removing the implementation walkthrough and repeated test details.
| File | Description |
|---|---|
crates/iceberg/src/arrow/reader/projection.rs |
Updates leaf conversion and map projection handling, with regression tests. |
Review effort: Balanced
| let Some(iceberg_field) = iceberg_schema_of_task.field_by_id(field_id) else { | ||
| continue; | ||
| }; | ||
| let file_schema = arrow_schema_to_schema(&ArrowSchema::new(vec![field]))?; |
Comment on lines
+394
to
+398
| let value_leaves: Vec<usize> = map | ||
| .value | ||
| .clone() | ||
| .filter(|idx| !type_rejected_leaves.contains(idx)) | ||
| .collect(); |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Which issue does this PR close?
What changes are included in this PR?
Parquet can only read a map when both its key and value are projected. When a map value is a struct and none of its leaves match the table (a name-mapped physical name that is not in the mapping, or embedded ids that are not in the table schema), only the key was selected. Converting that pre-projected schema failed with
Map field must have exactly 2 fields. Iceberg Java reads the same files.type_promotion_is_validare never rescued. If every value leaf is rejected, the read fails withDataInvalidinstead of a Parquet "partial projection" error.Only reads that failed before select extra columns. A map whose value is read but whose key is not still fails as on main.
Selected leaf indices come from Arrow
filter_leaves, while map key/value ranges are computed from the Parquet schema. Those two leaf numberings disagree only when a nestedListView(or similar) appears before the map in the file. That mismatch already mis-projects columns onapache/iceberg-rust maintoday (#3609); open PR #3624 fixes it. This PR does not change that behavior.Related open work:
When the read failed
Typical case:
Here x should result in null, not as it is now crashing.
Are these changes tested?
Yes, in
arrow::reader::projection:test_name_mapped_map_value_with_unmapped_child_reads_null: name-mappedxreads values;old_x(Reading a name-mapped map fails with "Map field must have exactly 2 fields" when none of the map value's leaves are in the file #3557) reads a struct of NULLs.test_map_value_with_unmatched_embedded_id_reads_null: file id 5, table id 6. Also checks that a read projecting onlyidleaves the unprojected map alone.test_map_value_with_invalid_promotion_is_not_cast: int to string is rejected withDataInvalid; with an unmatched sibling the map reads and the rejected leaf is NULL.test_map_primitive_value_with_unmatched_id_is_not_cast: primitive value with a mismatched id errors instead of casting"12"to12.The first three fail without the fix; the last checks that a mismatched primitive value is not added to the projection.
AI Disclosure
Cursor assisted with the implementation, regression tests, and local review. I reviewed the resulting changes.