-
Notifications
You must be signed in to change notification settings - Fork 358
feat(core): Add support for _file column
#1824
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
base: main
Are you sure you want to change the base?
Conversation
crates/iceberg/src/scan/mod.rs
Outdated
| /// // Select regular columns along with the file path | ||
| /// let scan = table | ||
| /// .scan() | ||
| /// .select(["id", "name", RESERVED_COL_NAME_FILE]) |
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.
How do we ask for _file column without having to explicitly list all the other columns? E.g. get me all columns + _file. There should be some shortcut for this.
_file column_file column
…erg-rust into feature/gb/file-column
liurenjie1024
left a comment
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.
Thanks @gbrgr for this pr. But I think we need to rethink how to compute the _file, _pos metadata column. While it's somehow trivial to compute _file, it's non trivial to compute _pos efficient, since when we read parquet files, we have filtered out some row groups. I think the best way is to push reading these two columns to arrow-rs.
crates/iceberg/src/arrow/reader.rs
Outdated
| pub(crate) const RESERVED_FIELD_ID_FILE: i32 = 2147483646; | ||
|
|
||
| /// Column name for the file path metadata column per Iceberg spec | ||
| pub(crate) const RESERVED_COL_NAME_FILE: &str = "_file"; |
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.
I think we should add a metadata_columns module similar to what java did: https://github.com/apache/iceberg/blob/bb32b90c4ad63f037f0bda197cc53fb08c886934/core/src/main/java/org/apache/iceberg/MetadataColumns.java#L2
@liurenjie1024 I agree for How do we go forward with rethinking this, what would be the action items for us? |
…erg-rust into feature/gb/file-column
liurenjie1024
left a comment
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.
Thanks @gbrgr for this pr, I left some comments to improve.
crates/iceberg/src/scan/mod.rs
Outdated
| /// # Ok(()) | ||
| /// # } | ||
| /// ``` | ||
| pub const RESERVED_COL_NAME_FILE: &str = RESERVED_COL_NAME_FILE_INTERNAL; |
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.
We will have more metadata columns, so I would prefert to put these definition in sth like metadata_columns module.
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.
Added a new module
crates/iceberg/src/scan/mod.rs
Outdated
| if let Some(column_names) = self.column_names.as_ref() { | ||
| for column_name in column_names { | ||
| // Skip reserved columns that don't exist in the schema | ||
| if column_name == RESERVED_COL_NAME_FILE_INTERNAL { |
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.
We should have sth like is_metadata_column_name() in metadata_columns module, and useis_metadata_column_name so that we could avoid such changes when we add more metadata columns.
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.
Done in the new module
crates/iceberg/src/arrow/reader.rs
Outdated
|
|
||
| /// Helper function to add a `_file` column to a RecordBatch at a specific position. | ||
| /// Takes the array, field to add, and position where to insert. | ||
| fn create_file_field_at_position( |
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.
I think this approach is not extensible. I prefer what's similar in this pr:
- Add
constant_mapforArrowReader - Add another variant of
RecordBatchTransformerto handle constant field like_file
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.
I sketched an approach in the record batch transformer, I took the path of the transformer just having a constant_map stored which can be populated by the reader.
Hi, @vustef I also agree that we should put |
liurenjie1024
left a comment
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.
Thanks @gbrgr for this pr, generally LGTM! Please help to resolve conflicts.
| projected_iceberg_field_ids: Vec<i32>, | ||
| // Pre-computed constant field information: field_id -> (arrow_type, value) | ||
| // Avoids duplicate lookups and type conversions during batch processing | ||
| constant_fields: HashMap<i32, (DataType, PrimitiveLiteral)>, |
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.
We have Datum type exactly for DataType + PrimitiveLiteral.
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.
I think Datum is rather PrimitiveType + PrimitiveLiteral, but here we have the Arrow DataType.
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.
What's the benefit of using arrow's DataType here?
| use crate::{Error, ErrorKind, Result}; | ||
|
|
||
| /// Reserved field ID for the file path (_file) column per Iceberg spec | ||
| pub const RESERVED_FIELD_ID_FILE: i32 = 2147483646; |
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.
Please help to create an issue for porting all fields of MetadataColumns here.
|
|
||
| /// Reserved column name for the file path metadata column | ||
| pub const RESERVED_COL_NAME_FILE: &str = "_file"; | ||
|
|
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.
Please create a lazy field for FILE_PATH, you can take an example here:
| static STATUS: Lazy<NestedFieldRef> = { |
Also please don't expose the static field directly, use a method to expose the field reference.
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.
I created an Arrow lazy field (not an Iceberg field) in metadata_columns.rs
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.
We should not create arrow field. Please remember that iceberg tries to be engine independent, and such core abstractions should use iceberg's own abastractions.
| let vals: Vec<Option<f64>> = vec![None; num_rows]; | ||
| Arc::new(Float64Array::from(vals)) | ||
| } | ||
| (DataType::RunEndEncoded(_, _), Some(PrimitiveLiteral::String(value))) => { |
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.
Should we in general encode constant columns as REE? Or should we make this custom per field? For the file path it definitely makes sense to run-end encode.
+1. I can't come up with a reason why we don't do this.
|
@liurenjie1024 I now resolved the merge conflicts that stem from PR #1821:
|
…erg-rust into feature/gb/file-column
| // Helper to create REE type with the given values type | ||
| // Note: values field is nullable as Arrow expects this when building the | ||
| // final Arrow schema with `RunArray::try_new`. | ||
| let make_ree = |values_type: DataType| -> DataType { |
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.
I'd limit REEs only to this method, others are not really related to the PR
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.
Done
|
@vustef I changed the PR in the sense that default values are not REE encoded anymore, but only constant fields that come from added metadata fields + partition data. @liurenjie1024 let us know whether that is OK. If REE is desired in general for all constant columns, I guess it is better to make a follow-up PR to keep changesets smaller. |
|
@liurenjie1024 just a friendly ping on this for the new round of your feedback, if you have time. |
|
|
||
| /// Reserved column name for the file path metadata column | ||
| pub const RESERVED_COL_NAME_FILE: &str = "_file"; | ||
|
|
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.
We should not create arrow field. Please remember that iceberg tries to be engine independent, and such core abstractions should use iceberg's own abastractions.
| /// The Arrow Field definition for the metadata column, or an error if not a metadata field | ||
| pub fn get_metadata_field(field_id: i32) -> Result<Arc<Field>> { | ||
| match field_id { | ||
| RESERVED_FIELD_ID_FILE => Ok(Arc::clone(file_field())), |
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.
This is fine for now, but I'm thinking maybe a better approach is to have a static map for this so that we don't need to repeat such pattern matching in different places.
| /// # Ok(()) | ||
| /// # } | ||
| /// ``` | ||
| pub fn with_file_column(mut self) -> Self { |
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.
It's odd to add this function, user could just select("_file")
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.
Gerald was saying the same, and we test both. But I suggested this because without this function users need to list all the other columns they want to query, while this is like select *, _file. Let us know if we should remove it though
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.
I don't think this is a good addition. In general, the scan api is a low level api, users could provide other easy wrappers around it.
| projected_iceberg_field_ids: Vec<i32>, | ||
| // Pre-computed constant field information: field_id -> (arrow_type, value) | ||
| // Avoids duplicate lookups and type conversions during batch processing | ||
| constant_fields: HashMap<i32, (DataType, PrimitiveLiteral)>, |
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.
What's the benefit of using arrow's DataType here?
| /// * `field_id` - The field ID to associate with the constant | ||
| /// * `value` - The constant value for this field | ||
| pub(crate) fn with_constant(mut self, field_id: i32, value: PrimitiveLiteral) -> Result<Self> { | ||
| let arrow_type = RecordBatchTransformer::primitive_literal_to_arrow_type(&value)?; |
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.
The primitive_literal_to_arrow_type doesn't make sense to me. PrimitiveLiteral is designed to be a literal without type, and we should not guess its type.
|
|
||
| // Add partition constants to constant_fields (compute REE types from literals) | ||
| for (field_id, value) in partition_constants { | ||
| let arrow_type = RecordBatchTransformer::primitive_literal_to_arrow_type(&value)?; |
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.
Ditto.
| .ok_or(Error::new(ErrorKind::Unexpected, "field not found"))? | ||
| .0 | ||
| .clone()) | ||
| // Check if this is a constant field (virtual or partition) |
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.
| // Check if this is a constant field (virtual or partition) | |
| // Check if this is a constant field |
Which issue does this PR close?
_filemetadata column #1766.What changes are included in this PR?
Integrates virtual field handling for the
_filemetadata column intoRecordBatchTransformerusing a pre-computed constants map, eliminating post-processing and duplicate lookups.Key Changes
New
metadata_columns.rsmodule: Centralized utilities for metadata columnsRESERVED_FIELD_ID_FILE,RESERVED_COL_NAME_FILEget_metadata_column_name(),get_metadata_field_id(),is_metadata_field(),is_metadata_column_name()Enhanced
RecordBatchTransformer:constant_fields: HashMap<i32, (DataType, PrimitiveLiteral)>- pre-computed during initializationwith_constant()method - computes Arrow type once during setupDataType::RunEndEncodedfor constant strings (memory efficient)Simplified
reader.rs:project_field_ids(including virtual) to RecordBatchTransformerwith_constant()call to register_filecolumnUpdated
scan/mod.rs:is_metadata_column_name()andget_metadata_field_id()instead of hardcoded checksAre these changes tested?
Yes, comprehensive tests have been added to verify the functionality:
New Tests (7 tests added)
Table Scan API Tests (7 tests)
test_select_with_file_column- Verifies basic functionality of selecting_filewith regular columnstest_select_file_column_position- Verifies column ordering is preservedtest_select_file_column_only- Tests selecting only the_filecolumntest_file_column_with_multiple_files- Tests multiple data files scenariotest_file_column_at_start- Tests_fileat position 0test_file_column_at_end- Tests_fileat the last positiontest_select_with_repeated_column_names- Tests repeated column selection