feat: declare sort orders and surface them to DataFusion for attested files - #391
Conversation
A table's default sort order was stored under the reserved unsorted id and never reached the files: nothing wrote Parquet sorting_columns or the manifest entry's sort_order_id, so readers could not tell sorted files from unsorted ones. - CreateTable keeps the declared write order under its own order id and makes it the default; a fielded order with id 0 is rejected. - TableMetadata::default_sort_order() and TableTransaction::replace_sort_order() declare an order on an existing table (AddSortOrder + SetDefaultSortOrder, guarded by AssertDefaultSortOrderId). - write_sorted_parquet_partitioned() writes rows the caller has sorted by the default order and attests it per file: row-group sorting_columns for identity fields, an iceberg.sort-order-id footer entry, and the DataFile sort_order_id lifted from that footer by parquet_to_datafile. write_parquet_partitioned() keeps writing unattested files.
The provider reported ordering: None for every file, so a scan of files written under a declared sort order still had to be re-sorted and could not feed TopK pushdown. The scan now derives a DataFusion ordering from the table's default sort order and splits data files by whether their manifest entry attests that order id. Attested files go into a scan that declares the ordering (and, when split_file_groups_by_statistics is on, gets regrouped into non-overlapping statistics-ordered groups); unattested files go into a scan that claims nothing. A table holding any unattested file therefore keeps its explicit sort, and only the leading run of identity-transformed sort fields is ever claimed.
|
Self-review notes, recorded so the follow-up is specific rather than remembered. Queued for the next free build slot on our side; happy to fold any of it in if you would rather see it before merge. 1. The two The natural fix is one 2. Those checks scan where a lookup exists. 3. Optional readability nit. None of these change behaviour, and CI is green as it stands. |
|
As always, great work. I also like the approach. Thanks! |
Problem
A table's declared sort order never reached the data it describes, and readers had no way to find out that a file was sorted:
CreateTablestored the declared write order underDEFAULT_SORT_ORDER_ID(0, the spec's reserved "unsorted" id), so a table created with a sort order reported the unsorted order as its default.sorting_columnsor set aDataFile'ssort_order_id, so a sorted file and an unsorted file were indistinguishable in the manifest.datafusion_icebergtherefore reportedordering: Nonefor every file, and DataFusion had to re-sort data that was already sorted on disk, with no TopK or sort pushdown available.Approach
Declare the order, attest it per file, and only claim it for files that carry the attestation.
Declaration (
iceberg-rust)CreateTablekeeps the declared write order under its ownorder_idand makes it the default. A fielded order using the reserved id 0 is rejected.TableMetadata::default_sort_order()andTableTransaction::replace_sort_order()declare an order on an existing table (AddSortOrder+SetDefaultSortOrder, guarded byAssertDefaultSortOrderId).Attestation (
iceberg-rust)write_sorted_parquet_partitioned(): the caller promises the batch stream is sorted by the table's default sort order; every written file records it as per-row-groupsorting_columnsplus aniceberg.sort-order-idfooter entry, andparquet_to_datafilelifts that footer entry into the manifest entry'ssort_order_id.write_parquet_partitioned()is unchanged and keeps writing unattested files, so a producer that cannot guarantee the order stays honest by default.sorting_columnsare all-or-nothing: they are only emitted when every sort field is an identity transform on a top-level primitive column, because a partial list would claim a different (prefix) order than the one the file honors.Consumption (
datafusion_iceberg)output_ordering; unattested files go into a scan that claims nothing. A table holding any unattested file therefore keeps its explicit sort — the failure mode of a false claim is silently mis-ordered results, so the classification is conservative by construction.split_file_groups_by_statisticsis enabled, the attested groups are regrouped into non-overlapping, statistics-ordered groups the way DataFusion's listing table does. Falling back to the partition-shaped groups is always safe, since DataFusion re-validates the declared ordering against each group's file statistics at plan time.Tests
iceberg-rust/tests/sort_order_test.rs(6 tests): a table created with a sort order reports it as default;replace_sort_orderdeclares one on an existing table and rejects the reserved id; a sorted write attests the order on every file (footer plussort_order_id); unsorted writes and tables without a declared order attest nothing.datafusion_iceberg/tests/sort_order_scan.rs(3 tests): a fully attested table declaresoutput_orderingand elides the sort; one unattested file keeps the sort and the results stay exact; attested-but-overlapping files also keep the sort and stay exact, with and withoutsplit_file_groups_by_statistics.All green locally, along with the
make test-*suites.