Repository navigation
Conversation
…e predicate Page-index bounds are built with the table field type but the file's physical literal. When the two differ, e.g. a long field read against INT32 pages written before an int -> long promotion, a double field against FLOAT pages, or a UINT_32 column mapped to long, the bounds never compare with the predicate literal. Inequality and IN predicates treated that as "cannot match" and skipped pages holding matching rows. Keep a page whenever its bound cannot be compared with the literal: the inequality check now treats an incomparable bound as a possible match, and IN uses all(lt) / all(gt) instead of !any(ge) / !any(le).
A page holding both 100 and 3_000_000_000 has a raw signed max below its min, which pins the unsigned column order that a later bound widening has to zero-extend rather than sign-extend.
Page bounds written before an int -> long or float -> double promotion are now widened with Datum::promote_to, so inequality and IN predicates can prune these pages instead of keeping them all. UINT_32 bounds are INT32 bits in unsigned order, so they are zero-extended; if the result has min > max the writer used signed order and the page is kept.
Legacy writers may store NaN page bounds for pages holding other values. Datum compares floats with total_cmp, so such a bound, now also produced by widening FLOAT bounds to double, could prune a matching page. Drop both bounds of a FLOAT or DOUBLE page when either is NaN, so the page might match.
comphead
added a commit
to comphead/iceberg-rust
that referenced
this pull request
Oct 26, 2026
A file written before an int -> long, float -> double, or decimal precision promotion keeps its old physical type, while the predicate is bound to the table type. The row filter cast the literal down to the file's Arrow type, so an out-of-range literal became null and dropped every row, and a double literal was rounded to f32. Cast a numeric column to the table type before comparing instead, as RecordBatchTransformer does for the output. Other columns keep the literal cast. Page-index pruning is left to apache#3507 and apache#3521. Part of apache#3122.
comphead
added a commit
to comphead/iceberg-rust
that referenced
this pull request
Oct 27, 2026
Predicates are bound to the table schema, but the row filter evaluated them on batches in the file's physical types. For a file written before an int -> long, float -> double, or decimal precision promotion, try_cast_literal cast the literal down to the file type, so an out-of-range literal became null and dropped every row, and a double literal was rounded to f32. Convert the filter columns to the table schema with a RecordBatchTransformer, as the reader does for its output, and make try_cast_literal fail instead of returning null. Page-index pruning is left to apache#3507 and apache#3521. Part of apache#3122.
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.
Note
Draft, stacked on #3507. This branch includes the #3507 commits and a merge from
main. Only the last two commits are new here:fix(reader): widen promoted page-index bounds to the field typeandfix(reader): ignore NaN float page-index bounds.After #3507 merges, I will rebase this branch onto
mainso that only these two commits remain, rerun the tests below, and mark the PR ready for review. Feedback on the approach is welcome in the meantime; please save detailed review until the PR is ready.Which issue does this PR close?
What changes are included in this PR?
This implements the widening follow-up agreed in #3507. That PR prevents pruning based on incomparable page-index bounds, such as
INT32bounds read as alongfield after anint->longpromotion. This PR widens supported bounds to the field type so inequality andINpredicates can prune these pages.INT32bounds for alongfield andFLOATbounds for adoublefield are widened withDatum::promote_to, added in #3483.UINT_32bounds are stored asINT32bit patterns ordered as unsigned values. The column descriptor’s sort order identifies unsigned bounds, which are zero-extended tolong. If this producesmin > max, as can happen with bounds written in signed order, both bounds are discarded.FLOATorDOUBLEbound is NaN, both bounds are discarded. Legacy writers may store NaN bounds for pages that also contain other values, as documented in the Parquet specification.Datumusestotal_cmpfor floating-point comparisons, so trusting these bounds can prune matching pages. Widening exposes promoteddoublefields to this risk too. #3502 added a related NaN guard for row-group inequality pruning.Discarding bounds prevents pruning based on those bounds; null-count checks and other predicates still apply. Bounds that remain incomparable, such as
INT32bounds for atimestampfield, retain the conservative fallback from #3507.Are these changes tested?
Yes. Evaluator tests in
page_index_evaluator.rsuse metadata from real Parquet files and assert exact row selections. The NaN cases substitute synthetic column indexes.longfield overINT32pages:<,>=,<with a literal abovei32::MAX,>withi32::MAX, and two-valueIN.doublefield overFLOATpages:>,<=, and two-valueIN.longfield overUINT_32pages, including a value abovei32::MAXand a page mixing100and3_000_000_000:=,>,<, andIN.FLOATpages read as nativefloator promoteddouble, andDOUBLEpages read as nativedouble: pages with a NaN minimum or maximum are kept for=,<,>, and two-valueIN.timestampfield overINT32pages: the existing fallback test keeps every page for<,>=,=, andIN.Helper tests also verify that signed-order
UINT_32bounds are discarded, correctly ordered bounds are widened, and both floating-point bounds are discarded if either is NaN while finite bounds are preserved.Mutation checks confirmed that sign-extending
UINT_32bounds, removing themin > maxcheck, and removing the NaN check each cause at least one test to fail.cargo test -p iceberg --lib expr::visitorsandcargo test -p iceberg --lib arrow::passed, as didcargo fmt --all -- --checkandcargo clippy -p iceberg --all-targets -- -D warnings.AI Disclosure
Drafted with Claude Code and reviewed with Codex. Review focus: whether zero-extension and the signed-order check are correct for
UINT_32columns, and whether discarding both bounds when either is NaN is conservative across predicates.