fix(scan): prune NOT IN predicates using file stats - #795
Conversation
| PredicateOperator::NotIn => !literals | ||
| .iter() | ||
| .any(|literal| min_value == *literal && max_value == *literal), |
There was a problem hiding this comment.
This can drop matching FLOAT/DOUBLE rows: Datum treats signed zeros as equal while the residual distinguishes them, and Parquet min/max excludes NaNs. For example, [1.0, NaN] NOT IN (1.0, 2.0) is pruned entirely instead of retaining the NaN row.
There was a problem hiding this comment.
Confirmed, and it already bites <> on main: on a file holding [1.0, NaN] (min == max == 1.0, NaN counted as neither bound nor null) <> 1.0 returned [3] while the filter alone returns [2, 3]; on a -0.0 file <> 0.0 returned nothing. datum_cmp is IEEE where the residual is bitwise, and Java's compareLiteral uses Double.compareTo. Both operators now fail open for FLOAT/DOUBLE, so this removes the existing loss rather than extending it. Ordering rules and Eq are untouched.
On the stats rule,
data_leaf_may_matchreturnedtrueforNotInunconditionally, so
col NOT IN (...)never used min/max or the null count toskip anything. That was deliberate — #382 scoped
INpruning and saidNOT INwould keep failing open — and this lifts it.
The rule is the n-ary form of the
NotEqrule above it: skip a file only whensome literal equals both bounds, as Java
NotIn#testdoes. Its null-literal armhas no counterpart because
Datumhas no null variant. Partly null files arestill pruned: a null row satisfies neither
NotInnor the equality that prunesit.
Not gated on
supports_in_min_max_pruning, unlikeIN:NotEqis not gatedeither, and
x NOT IN (5)has to answer likex <> 5— a test pins that. Soevery consumer gains the rule at once (manifest partition stats, key stats,
Parquet row groups and page index, mosaic), not only data-file stats as in #382.
Both operators now fail open for FLOAT and DOUBLE, which fixes a pre-existing
NotEqdefect rather than extending it.datum_cmpcompares floats with IEEEpartial_cmp, so-0.0equals+0.0there while the residual filter tells themapart; and NaN enters neither min/max nor the null count, so a file holding
[1.0, NaN]reportsmin == max == 1.0over two rows. Measured:<> 1.0returned
[3]where the filter alone returns[2, 3], and<> 0.0returnednothing where it returns the
-0.0row. Java is unaffected on the first count —compareLiteralusesDouble.compareTo, which orders-0.0below+0.0.Elsewhere results are unchanged, only how many files are read: on a three-file
table where one file holds a single value,
value NOT IN (20)movesmanifest_entries_pruned_by_data_statsfrom 0 to 1,final_filesfrom 3 to 2.