-
Notifications
You must be signed in to change notification settings - Fork 532
feat: add support for _spec_id metadata column
#2695
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
0a25d45
Support selecting metadata column: _spec_id
hsiang-c bcc3b6d
Fix CI
hsiang-c 69e8714
style: inline w/ debug format
hsiang-c cf26ad1
Partition spec is always present
hsiang-c aee857e
Test partition evolution
hsiang-c edaaf79
Filter field ids which are not present in a data file
hsiang-c c8ea392
Fix typo checks
hsiang-c b9f8af5
Merge branch 'main' into meta_col_spec_id
hsiang-c 24508e2
Fix CI
hsiang-c 5987a95
refactor: simplify control flow
hsiang-c bfd0977
Make sure we read from Parquet, not partition values
hsiang-c 84cc13c
Merge branch 'main' into meta_col_spec_id
hsiang-c 61de279
Fail _spec_id projection when partition spec is None
hsiang-c df42b7a
Merge branch 'main' into meta_col_spec_id
hsiang-c dd53930
Address feedback
hsiang-c File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -359,49 +359,29 @@ impl RecordBatchTransformer { | |
| let fields: Result<Vec<_>> = projected_iceberg_field_ids | ||
| .iter() | ||
| .map(|field_id| { | ||
| // Check if this is a constant field | ||
| if constant_fields.contains_key(field_id) { | ||
| // For metadata/virtual fields (like _file), get name from metadata_columns | ||
| // For partition fields, get name from schema (they exist in schema) | ||
| if let Ok(iceberg_field) = get_metadata_field(*field_id) { | ||
| // This is a metadata/virtual field - convert Iceberg field to Arrow | ||
| let datum = constant_fields.get(field_id).ok_or(Error::new( | ||
| ErrorKind::Unexpected, | ||
| "constant field not found", | ||
| ))?; | ||
| let arrow_type = datum_to_arrow_type_with_ree(datum); | ||
| let arrow_field = | ||
| Field::new(&iceberg_field.name, arrow_type, !iceberg_field.required) | ||
| .with_metadata(HashMap::from([( | ||
| PARQUET_FIELD_ID_META_KEY.to_string(), | ||
| iceberg_field.id.to_string(), | ||
| )])); | ||
| Ok(Arc::new(arrow_field)) | ||
| } else { | ||
| // This is a partition constant field (exists in schema but uses constant value) | ||
| let field = &field_id_to_mapped_schema_map | ||
| .get(field_id) | ||
| .ok_or(Error::new(ErrorKind::Unexpected, "field not found"))? | ||
| .0; | ||
| let datum = constant_fields.get(field_id).ok_or(Error::new( | ||
| ErrorKind::Unexpected, | ||
| "constant field not found", | ||
| ))?; | ||
| let arrow_type = datum_to_arrow_type_with_ree(datum); | ||
| // Use the type from constant_fields (REE for constants) | ||
| let constant_field = | ||
| Field::new(field.name(), arrow_type, field.is_nullable()) | ||
| .with_metadata(field.metadata().clone()); | ||
| Ok(Arc::new(constant_field)) | ||
| } | ||
| } else { | ||
| // Regular field - use schema as-is | ||
| Ok(field_id_to_mapped_schema_map | ||
| .get(field_id) | ||
| .ok_or(Error::new(ErrorKind::Unexpected, "field not found"))? | ||
| .0 | ||
| .clone()) | ||
| // Metadata/virtual fields (like _file, _spec_id) don't exist in the table | ||
| // schema, so build their Arrow field from the metadata column definition and | ||
| // the pre-computed constant's type. | ||
| if let Some(datum) = constant_fields.get(field_id) | ||
| && let Ok(iceberg_field) = get_metadata_field(*field_id) | ||
| { | ||
| let arrow_type = datum_to_arrow_type_with_ree(datum); | ||
| let arrow_field = | ||
| Field::new(&iceberg_field.name, arrow_type, !iceberg_field.required) | ||
| .with_metadata(HashMap::from([( | ||
| PARQUET_FIELD_ID_META_KEY.to_string(), | ||
| iceberg_field.id.to_string(), | ||
| )])); | ||
| return Ok(Arc::new(arrow_field)); | ||
| } | ||
|
|
||
| // Regular fields and identity-partitioned constant fields both exist in the | ||
| // table schema, so use the mapped Arrow field as-is. | ||
| Ok(field_id_to_mapped_schema_map | ||
| .get(field_id) | ||
| .ok_or(Error::new(ErrorKind::Unexpected, "field not found"))? | ||
| .0 | ||
| .clone()) | ||
| }) | ||
| .collect(); | ||
|
|
||
|
|
@@ -481,16 +461,39 @@ impl RecordBatchTransformer { | |
| projected_iceberg_field_ids | ||
| .iter() | ||
| .map(|field_id| { | ||
| // Check if this is a constant field (metadata/virtual or identity-partitioned) | ||
| // Constant fields always use their pre-computed constant values, regardless of whether | ||
| // they exist in the Parquet file. This is per Iceberg spec rule #1: partition metadata | ||
| // is authoritative and should be preferred over file data. | ||
| // Check if this is a constant field (metadata/virtual or identity-partitioned). | ||
| // | ||
| // Metadata/virtual fields (like _file, _spec_id) never exist in the data file, | ||
| // so they always use their pre-computed constant value. | ||
| // | ||
| // For identity-partitioned fields, the Iceberg spec's "Column Projection" rules | ||
| // only apply to "field ids which are not present in a data file". When the column | ||
| // IS present in the Parquet file, it must be read from the file; the partition | ||
| // metadata constant is only a fallback for when the column is absent (e.g. add_files). | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch |
||
| if let Some(datum) = constant_fields.get(field_id) { | ||
| let arrow_type = datum_to_arrow_type_with_ree(datum); | ||
| return Ok(ColumnSource::Add { | ||
| value: Some(datum.literal().clone()), | ||
| target_type: arrow_type, | ||
| }); | ||
| let is_metadata_field = get_metadata_field(*field_id).is_ok(); | ||
| let present_in_file = field_id_to_source_schema_map.contains_key(field_id); | ||
|
|
||
| if is_metadata_field || !present_in_file { | ||
| let arrow_type = if is_metadata_field { | ||
| datum_to_arrow_type_with_ree(datum) | ||
| } else { | ||
| field_id_to_mapped_schema_map | ||
| .get(field_id) | ||
| .ok_or(Error::new( | ||
| ErrorKind::Unexpected, | ||
| "could not find field in schema", | ||
| ))? | ||
| .0 | ||
| .data_type() | ||
| .clone() | ||
| }; | ||
|
|
||
| return Ok(ColumnSource::Add { | ||
| value: Some(datum.literal().clone()), | ||
| target_type: arrow_type, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| let (target_field, _) = | ||
|
|
||
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice refactoring!