Repository navigation
Fix scans dropping NaN rows for IsNaN, NotEqualTo and NotIn - #4346
rahulsmahadev wants to merge 2 commits into
Conversation
Parquet min/max statistics exclude NaN, so pyarrow could prune a row group whose non-NaN values are all equal and drop the NaN rows that these predicates match on float and double columns. Push only the conjuncts that cannot hide NaN rows to the scanner, and filter each batch with the full expression when a NaN-sensitive predicate is present. Co-authored-by: Isaac <no-reply@databricks.com>
|
@kevinjqliu @GayathriSrividya could you take a look when you have a chance? It changes which filters |
Sure, will do check and update |
| """ | ||
| if isinstance(expr, (And, Or)): | ||
| return _has_nan_sensitive_predicate(expr.left, negated) or _has_nan_sensitive_predicate(expr.right, negated) | ||
| if isinstance(expr, Not): |
There was a problem hiding this comment.
I'm not sure NOT belongs here. The metrics evaluator applies rewrite_not, so not x < 6.0 is evaluated as x >= 6.0 at the manifest level and a file with upper bound 5.0 is skipped before we get here. With this change, the NaN row comes back for not x < 6.0 only when the file has another row group that survives, and x >= 6.0 never returns it. Could we instead apply rewrite_not to the bound filter before expression_to_pyarrow, in a separate PR? Then NOT is never NaN-sensitive and the negated flag can go.
There was a problem hiding this comment.
Agreed, removed the NOT handling and the negated flag in 191387d, so NOT is not treated as NaN-sensitive here. I'll put the rewrite_not change before expression_to_pyarrow in a separate PR.
There was a problem hiding this comment.
Opened #4350 for the rewrite_not change.
GayathriSrividya
left a comment
There was a problem hiding this comment.
I agree with Fokko's feedback on the logic and pulling the NOT handling out.
- One additional thing to include when you update the tests for the next iteration: we should add coverage for FloatType (float32).
- The NaN-sensitive predicate detection explicitly handles both FloatType and DoubleType, but the current regression tests in tests/io/test_pyarrow.py (around line 4229 in the test schema) only exercise DoubleType.
- Once Fokko's structural comments are addressed and the float32 tests are in, this looks good to me!
NOT is left to a follow-up that applies rewrite_not before the Arrow conversion, as the metrics evaluators do. Run the regression cases on float and double columns, and link the upstream Arrow issue. Co-authored-by: Isaac <no-reply@databricks.com>
|
@GayathriSrividya thanks, the regression test now runs every case on both a float32 ( |
Thanks, Rahul! The float32 coverage looks good. |
Rationale for this change
Scans drop NaN rows for predicates that NaN satisfies:
IsNaN,NotEqualToandNotIn. Parquet statistics exclude NaN, so a row group whose non-NaN values are all equal reports min == max. pyarrow prunes that row group foris_nan(x), which is false for the value 7.0, and the scan never returns the NaN row:The trigger is a float or double row group with no nulls whose non-NaN values are all equal. Nulls in the same row group avoid it.
This change keeps NaN-sensitive predicates away from Parquet pruning.
_task_to_record_batchespushes down only the top-level AND conjuncts that cannot hide NaN rows. A conjunct is NaN-sensitive if it isIsNaN,NotEqualTo, orNotInon a float or double column. When one is present, each batch is filtered with the full expression after the read, as already happens for scans with positional deletes. NOT is left to a follow-up that appliesrewrite_notbefore the Arrow conversion, as the metrics evaluators do. Upstream issue: apache/arrow#51491.Trade-off: NaN-sensitive conjuncts lose Parquet row-group pruning, and other conjuncts keep it. Java's row-group filter takes the same conservative view: for
notEqandnotInit never drops a row group, and forisNaNit drops one only when the column is all null (ParquetMetricsRowGroupFilter).Are these changes tested?
Yes.
test_task_to_record_batches_keeps_nan_rows_for_nan_sensitive_filtersintests/io/test_pyarrow.pycoversIsNaN,NotEqualTo,NotIn, AND, OR andNotNaN, each on a float and a double column.test_scan_nan_row_with_equal_finite_statisticsintests/catalog/test_catalog_behaviors.pyruns the public scan path on thememory,sql, andsql_without_rowcountcatalogs. Both fail without the fix and pass with it.Are there any user-facing changes?
Bug fix only: scans return the NaN rows they previously dropped. No API changes.
This pull request and its description were written by Isaac.