feat: add support for _pos metadata column#2746
Conversation
mbutrovich
left a comment
There was a problem hiding this comment.
I like this approach a lot. Delegating to the parquet reader's RowNumber virtual column is essentially what Java does with SupportsRowPosition / PositionReader, and it gets correct absolute file positions through row-group pruning and row selection without us hand-rolling a counter. The virtual_fields plus PassThrough wiring is clean. My only real ask is around tests that exercise _pos where it's actually load-bearing. The current tests read contiguous rows, which would pass even if selection/deletes mis-numbered positions:
_poswith row selection that genuinely prunes rows;_poswith a delete file applied;_poson a split task (start/lengthnot equal to the whole file);_posinterleaved with other metadata columns.
Curious whether any of these already pass as-is. If so, even better, and they'd lock in the behavior.
| .table | ||
| .scan() | ||
| .select(["x", RESERVED_COL_NAME_POS]) | ||
| .with_row_selection_enabled(true) |
There was a problem hiding this comment.
With no filter, all 1024 rows are read contiguously, so this passes whether or not _pos survives selection correctly. The interesting case, and the reason to use the reader's row-number column rather than a counter, is a predicate that prunes row groups/rows, where _pos must still report absolute file positions (for example non-contiguous like [0,1,2, 500,501,...]). Could we add that? It's the test that proves the design.
| assert!(result.is_ok(), "Scan timed out - deadlock detected"); | ||
| } | ||
|
|
||
| #[tokio::test] |
There was a problem hiding this comment.
The spec defines _pos as the ordinal position in the source data file (https://iceberg.apache.org/spec/#reserved-field-ids). Two behaviors I'd love a test for, next to the ones you added here: (a) applying a delete filter doesn't renumber _pos; (b) a task scanning only [start, start+length) still emits absolute positions, matching Java's findStartingRowPos. Both look reachable with the existing TableTestFixture. Do these hold today?
|
|
||
| let project_pos = task.project_field_ids().contains(&RESERVED_FIELD_ID_POS); | ||
|
|
||
| let arrow_metadata = if project_pos { |
There was a problem hiding this comment.
There are now up to three sequential ArrowReaderMetadata::try_new rebuilds per file (field-ID schema, INT96 coercion, virtual columns), each re-deriving from parquet metadata. Could the final ArrowReaderOptions (schema plus virtual columns) be assembled once and try_new called a single time? Minor and not blocking; it just compounds with the two existing passes.
There was a problem hiding this comment.
+1, the code is getting harder to follow. I have reviewed this part code several times in the past two weeks but still need to refresh my memory every time I see it :)
I think we can create a tracking issue and have AI check if there is any opportunity to refactor it
| arrow_metadata | ||
| }; | ||
|
|
||
| let project_pos = task.project_field_ids().contains(&RESERVED_FIELD_ID_POS); |
There was a problem hiding this comment.
Reusing RowNumber instead of a manual counter is the right, idiomatic call. It inherits the parquet reader's correctness for selection and row-group boundaries and matches Java's SupportsRowPosition semantics. Nice.
bfa0ae3 to
e16e307
Compare
e16e307 to
3450222
Compare
mbutrovich
left a comment
There was a problem hiding this comment.
LGTM. Delegating _pos to the Parquet reader's RowNumber virtual column is the right call: it inherits the reader's correctness through row-group pruning and row selection, and matches Java's SupportsRowPosition / PositionReader semantics rather than hand-rolling a counter.
The tests requested in the prior round have all landed and prove the design point — test_pos_across_row_groups_via_table_scan, test_pos_reads_only_middle_row_group_via_filter, and test_pos_with_positional_deletes_via_table_scan confirm absolute file positions survive pruning and are not renumbered by deletes. Production footprint is small (~107 lines); the rest is tests.
No blocking comments. The downstream Comet PR (#4752), which exercises this against Iceberg-Java-written data, is green including the Iceberg 1.11 Spark SQL suite.
Merge order
Suggest #2746 → #2695 → #2668. #2746 is independent (virtual-column path; doesn't touch the constant-map logic the other two rewrite), so it can go first cleanly.
|
Thank you for your suggestions and review @mbutrovich |
|
@CTTY if you could review it when you have time, thanks again for your help. |
CTTY
left a comment
There was a problem hiding this comment.
LGTM! just some minor comments
There was a problem hiding this comment.
Since we added some good behavior changes/fixes to this function, can we update the comment here to say that we now explicitly handle reordered schema and mark them as different?
Also I think it would nice to have a test for this function
There was a problem hiding this comment.
I'll add another test for it.
There was a problem hiding this comment.
Added a test for reordered schema (it will lead to SchemaComparison::NameChangesOnly, which is incorrect) and updated comment accordingly.
|
|
||
| let project_pos = task.project_field_ids().contains(&RESERVED_FIELD_ID_POS); | ||
|
|
||
| let arrow_metadata = if project_pos { |
There was a problem hiding this comment.
+1, the code is getting harder to follow. I have reviewed this part code several times in the past two weeks but still need to refresh my memory every time I see it :)
I think we can create a tracking issue and have AI check if there is any opportunity to refactor it
|
@CTTY I created a tracking issue and will refactor the |
CTTY
left a comment
There was a problem hiding this comment.
LGTM, thanks for the contribution!
Which issue does this PR close?
What changes are included in this PR?
_posmetadata column by delegating it to the Parquet reader'sRowNumbervirtual column, inspired by feat: Plumb Parquet virtual columns (row_number) through TableSchema and ParquetOpener datafusion#22026.compare_schemasfunction b/c_posfield id exists in bothsource_schemaandtarget_schemaand the original implementation might result inSchemaComparison::NameChangesOnly, which produces aRecordBatchwith correct content but incorrect column order.Are these changes tested?
Sample plan