Filter pushdown on float columns drops rows with NaN or -0.0
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 55/100
Hướng nghiên cứu
Start by tracing scalar_value_to_datum and the scan pushdown path described in the issue, then inspect how InclusiveMetricsEvaluator handles float bounds and predicates. Compare pushed-down results with the MemTable results in the reproduction, including NaN and signed zero cases. Done means the Iceberg table returns the same rows as the MemTable for these filters while retaining safe file pruning where possible.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Describe the bug
Some filters on double and float columns return fewer rows from an Iceberg table than DataFusion returns for the same data. The missing rows are the ones that hold NaN or -0.0. The query succeeds, but those rows aren't in the result.
The two sides compare these values differently:
- DataFusion treats NaN as equal to itself and greater than any other value, and treats -0.0 as equal to 0.0.
SELECT CAST('NaN' AS DOUBLE) > 1.0, CAST('NaN' AS DOUBLE) = CAST('NaN' AS DOUBLE), CAST('-0.0' AS DOUBLE) = 0.0returns true for all three. - iceberg-rust's
InclusiveMetricsEvaluatordecides that a data file holding only NaN can't match>,>=,=orIN, and NaN is left out of a file's upper bound. iceberg-rust also orders floats withtotal_cmp, so to it -0.0 and 0.0 are different values.
scalar_value_to_datum passes float literals through as Datum::double, and the comparison reaches the scan unchanged. So the scan skips files that contain rows DataFusion would match. Those files are never read, and filtering again in DataFusion can't bring the rows back.
To Reproduce
Using the sqllogictest engine, with one INSERT per data file:
CREATE TABLE default.default.t (id INT NOT NULL, x DOUBLE);
INSERT INTO default.default.t VALUES (1, CAST('NaN' AS DOUBLE));
INSERT INTO default.default.t VALUES (2, 0.5), (3, CAST('NaN' AS DOUBLE));
INSERT INTO default.default.t VALUES (4, 2.0);
INSERT INTO default.default.t VALUES (5, CAST('-0.0' AS DOUBLE));
INSERT INTO default.default.t VALUES (6, 0.0);
CREATE TABLE m AS SELECT * FROM default.default.t;
SELECT id FROM <table> WHERE <filter> returns:
| Filter | Iceberg table t |
MemTable copy m |
|---|---|---|
x > 1.0 |
4 | 1, 3, 4 |
x = CAST('NaN' AS DOUBLE) |
none | 1, 3 |
x = 0.0 |
6 | 5, 6 |
x >= 0.0 |
2, 3, 4, 6 | 1, 2, 3, 4, 5, 6 |
The same table returns 1, 3, 4 for x + 0 > 1.0, which isn't pushed down.
Which rows go missing also depends on how they're laid out in files. Row 3 is skipped because its file's upper bound is 0.5. The same NaN in a file with 2.0 comes back.
Expected behavior
t returns the same rows as m.
I can think of two ways to get there:
- Stop pushing down comparisons and
INlists that have a float literal. That's the simplest, but float columns lose file pruning. - Push a looser predicate that still covers every row DataFusion could match, and leave the exact check to DataFusion. For example,
x > cbecomesx > c OR x IS NANandx = NaNbecomesx IS NAN. Zero literals would cover both signs:x = 0.0becomesx = -0.0 OR x = 0.0,x >= 0.0becomesx >= -0.0, andx <= -0.0becomesx <= 0.0.
Let me know which you'd prefer.
Additional context
<,!=,NOT IN,isnan(x)andNOT isnan(x)return the same rows on both sides.NOT (x <= 1.0)hits this too, since it's simplified tox > 1.0before pushdown.REALcolumns are affected when the literal is alsoREAL, for examplex > CAST(1 AS REAL). With aDOUBLEliteral the column gets a cast, and since #91 that comparison isn't pushed down.- iceberg-rust main has the same NaN handling and float ordering, so updating the iceberg-rust dependency won't fix this.
- This is the same kind of problem as #36 and #44: the pushed predicate rejects rows that the filter keeps.
- Ngôn ngữ chính
- Rust
- Star
- 33
- Fork
- 34
- Merge trung bình
- 4 ngày 14 giờ
- Pull request đã merge (30 ngày)
- 10
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của apache/datafusion-iceberg
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
apache/datafusion-iceberg#100 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
apache/datafusion-iceberg#85 · 1 bình luận · 5 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
apache/datafusion-iceberg#84 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 35/100
apache/datafusion-iceberg#75 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Slow CI runnersĐang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 18/100
apache/datafusion-iceberg#74 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của apache/datafusion-iceberg
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
RustCrypto/utils#1597 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Non-`main` default branchesĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
max-sixty/worktrunk#4727 · 7 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
Maintainer thường phản hồi trong vòng 1 ngày
-
follow-up
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 73/100
carrick-sh/carrick#1859 ·
Maintainer thường phản hồi trong vòng 1 ngày