Repository navigation
Conversation
b92d957 to
0f76a20
Compare
There was a problem hiding this comment.
🟢 Approval recommended
The focused fix consistently covers all eight affected comparison paths with targeted regression tests and no identified blocking issues.
No open issues 🎉
What changed in this PR
Fixes row filtering on numerically promoted columns, addressing the default-reader portion of #3122. Page-index pruning remains outside this PR’s scope.
Changes:
- Casts numeric columns to the table type before comparisons, preventing literal overflow and rounding from changing filter results.
- Adds regression coverage for all eight comparison paths across integer and floating-point promotions.
| File | Description |
|---|---|
crates/iceberg/src/arrow/reader/row_filter.rs |
Adds promoted-column read tests with exact expected results. |
crates/iceberg/src/arrow/reader/predicate_visitor.rs |
Applies numeric column promotion while preserving nonnumeric literal casting. |
Review effort: Balanced
|
thanks for digging into this! the fix works, but this feels like a workaround to me 😅 I think the root cause is that predicates are bound to the table schema but evaluated against file schema batches. i think we should cast the filter batch to table types once in another option is Let me know what you think! |
|
Thanks @kevinjqliu for the review, it might totally happen that batches’ schema and incoming schema are different, in datafusion, arrow and Comet we have a notion on
|
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.
0f76a20 to
0be54f9
Compare
|
@kevinjqliu please take another look |
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
2 open issues
Review effort: Lite
| let options = CastOptions { | ||
| safe: false, | ||
| ..Default::default() | ||
| }; | ||
| let literal_array = cast_with_options(literal_array, column_type, &options)?; |
| let file_size = std::fs::metadata(&path).unwrap().len(); | ||
| let reader = ArrowReaderBuilder::new(FileIO::new_with_fs(), Runtime::current()).build(); | ||
| for (predicate, expected) in cases { | ||
| let task = FileScanTask::builder() | ||
| .with_file_size_in_bytes(file_size) | ||
| .with_start(0) | ||
| .with_length(0) | ||
| .with_data_file_path(path.clone()) | ||
| .with_data_file_format(DataFileFormat::Parquet) | ||
| .with_schema(schema.clone()) | ||
| .with_project_field_ids(vec![1]) | ||
| .with_predicate(Some(predicate.bind(schema.clone(), true).unwrap())) | ||
| .with_case_sensitive(true) | ||
| .build() |


Which issue does this PR close?
with_row_selection_enabled(true)).What changes are included in this PR?
Predicates are bound to the table schema, but the Arrow row filter evaluated them on batches in the file's types. For a file written before an
int->long,float->double, or decimal precision promotion,try_cast_literalcast the literal down to the file type. An out-of-range literal became null, sox < 2147483648returned no rows andx IN (1, 2147483648)dropped the row wherex = 1. A rounded double madex = 0.1match a stored0.1f32.get_row_filterconverts the filter columns to the table schema with aRecordBatchTransformer, as the reader does for its output. This also coversINT(8)columns read asintandUINT_32columns read aslong.try_cast_literalfails instead of returning null. After the conversion it only bridges representations of one type, such asBinaryandLargeBinary.A batch that already matches the table schema passes through unchanged. Since the transformer converts every mismatch, string filters on files written with
large_string(PyIceberg, for example) now cast the column instead of the literal, about 0.5 ns more per row in a release micro-benchmark.Are these changes tested?
Yes.
test_predicate_on_promoted_columncovers eight predicates onintandfloatfiles read aslonganddouble, one per comparison method. Onmainit fails on the first case.test_predicate_converter_rejects_lossy_literal_castcovers the strict cast.decimal(9,2),INT(8), andUINT_32files, which lose rows onmain.iceberglib suite passes, and clippy and fmt are clean.AI Disclosure
Drafted with Claude Code (Anthropic) and reviewed by the author.