Repository navigation
Fix IN expr for NaN - #7378
Merged
Merged
Fix IN expr for NaN#7378
Conversation
sarutak
commented
Aug 22, 2023
| let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?; | ||
|
|
||
| // expression: "a in (0.0, 0.2)" | ||
| // expression: "a in (0.0, 0.1)" |
Member
Author
There was a problem hiding this comment.
Some comments are wrong and it's a tiny issue. So I'll fix them within this PR.
viirya
reviewed
Aug 22, 2023
viirya
reviewed
Aug 22, 2023
| where | ||
| T: ArrayAccessor, | ||
| T::Item: PartialEq + HashValue, | ||
| T::Item: IsEqual + HashValue, |
viirya
reviewed
Aug 22, 2023
viirya
left a comment
Member
There was a problem hiding this comment.
Can we add end-to-end test for this too?
viirya
reviewed
Aug 23, 2023
viirya
reviewed
Aug 23, 2023
| ---- | ||
| 1.2 | ||
| NaN | ||
| NaN |
Member
Author
There was a problem hiding this comment.
It's actually -NaN but just shown as NaN.
I've already noticed this issue and have plan to fix it, but it's a separate issue.
viirya
approved these changes
Aug 23, 2023
This was referenced Oct 2, 2026
comphead
pushed a commit
to comphead/arrow-datafusion
that referenced
this pull request
Oct 22, 2026
…lists (apache#25187) ## Which issue does this PR close? - Part of apache#19241. ## Rationale for this change DataFusion currently rewrites short `IN` lists like `x IN (1, 2)` as `x = 1 OR x = 2`. Evaluating `IN` directly can be faster. This PR keeps the `IN` form for the types below, so short lists can use DataFusion's existing optimized `IN` filters. ## What changes are included in this PR? - Keep two- and three-value `IN` and `NOT IN` lists for `Int32`, `Int64`, `UInt64`, `Float32`, `Float64`, and `FixedSizeBinary` columns with widths of 1, 2, 4, 8, or 16 bytes. The list must contain constant values of the column's type. - Add benchmarks that use normal optimization to compare performance before and after this change. - Add optimizer and SQL logic tests covering list sizes, types, NULLs, and non-constant list values. ## What is the testing strategy for this PR? All 935 optimizer unit tests and all 533 SQL logic files passed, including TPC-H. Formatting, Clippy, and lint checks passed. All 336 benchmark cases passed on both builds. The benchmark bot's ARM [two-value](apache#25187 (comment)) and [three-value](apache#25187 (comment)) results show: - Integer lists take about 32–55% less time when half the rows match or none do. In the extreme case where every row matches the first value, they can take up to about twice as long. - Floating-point and fixed-size binary lists are faster in every tested case, including NULLs and small batches. <details> <summary>Benchmark setup</summary> Both builds use the same 336 cases. The [baseline](apache@31a333d) contains `main` at `06aa131cda0` plus the same benchmark code as the [PR](apache@8aea24b). Queries use normal optimization. Timings cover running the chosen expressions; query planning and table setup happen before timing. </details> ## Are there any user-facing changes? `EXPLAIN` shows `IN` or `NOT IN` for these short lists in place of the expanded `OR` or `AND` comparisons. This performance PR aligns constant two- and three-value `Float32`/`Float64` lists with existing behavior for 4+ values, including the signed-zero bug (`+0.0` versus `-0.0`). That bug [predates this optimization series](apache#7378) and already affects longer lists with constant or column values; fixing it is left to apache#25186. --------- Co-authored-by: Andrew Lamb <[email protected]>
alamb
added a commit
to alamb/datafusion
that referenced
this pull request
Nov 7, 2026
## Which issue does this PR close? - Part of apache#26138 (shared floating-point semantics). - Covers the IN-list regression reported in apache#27121. - Closes apache#27121 - Closes apache#27122 ## Rationale for this change DataFusion equality treats `+0.0` and `-0.0` as equal, but the optimized `IN` filters compare or hash floating-point bit patterns. Because the two zeros have different encodings, an `IN` predicate could therefore return a different result depending on whether the optimizer retained it or rewrote it into equality comparisons. This signed-zero limitation predates the IN-list optimization epic apache#19241: [apache#7378](apache#7378) already used bit-pattern equality for floating-point membership in August 2023. [apache#22835](apache#22835) corrected ordinary comparisons in June 2026 but did not fix `IN` membership. This PR addresses that remaining correctness gap. This PR makes `Float16`, `Float32`, and `Float64` `IN` predicates, including floats inside nested and encoded values, follow the same signed-zero equality as ordinary comparisons. The shared static-filter factory adds the missing zero encoding before choosing a primitive filter. Strategy selection uses the expanded physical non-null list length, so both encodings count toward the existing cutoffs. Generic static filters and non-static lists use the shared `normalize_floats` and recursive `ScalarValue::normalize` APIs before comparison. Distinct NaN payloads remain distinct, preserving the existing bit-equality behavior for NaNs. ## What changes are included in this PR? - Add the missing signed-zero encoding once in the shared static-filter factory, after flattening dictionary lists. - Select branchless, bitmap, or hash-set lookup using the expanded physical non-null key count. - Extend `ScalarValue::normalize` to nested and encoded values, and use `float_semantics::normalize_floats` for arrays in dynamic comparisons and generic static filters. - Prevent structural literal equality from driving IN-list set-operation rewrites for types containing floating-point values. - Preserve null behavior, dictionary inputs, `IN`, and `NOT IN`. ## What is the testing strategy for this PR? SQL regressions in `negative_zero.slt` cover both signed-zero directions, static filters, dynamic scalar and array operands, nested values, IN-list intersection/difference, `IN`, `NOT IN`, and nulls. Focused Rust tests cover dictionary inputs, distinct NaN payloads, encoded scalar metadata, and sliced values. Factory tests exercise both zero signs immediately below and at each floating-point branchless cutoff, including dispatch after adding the missing physical key. SQL tests also check constant nested scalar self-membership; an existing Rust test covers nested dictionary scalar inputs. Validation after rebasing onto `upstream/main` (`4bf32cb46d`): formatting, CI Clippy with its standard feature set, all 111 focused Rust tests, and all 546 sqllogictest files (including Parquet encryption) passed. ## Are there any user-facing changes? Yes. Floating-point `IN` and `NOT IN`, including nested and encoded values, now treat `+0.0` and `-0.0` as equal, consistently with ordinary DataFusion equality. `ScalarValue::normalize` now applies these rules recursively; public API signatures are unchanged. Prepared with AI assistance. --------- Co-authored-by: Andrew Lamb <[email protected]>
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.
Which issue does this PR close?
Closes #7377
Rationale for this change
This PR fixes an issue that
'NaN'::double in ('NaN'::double)is evaluated asfalse, which is inconsistent with the result of'NaN'::double = 'NaN'::double.What changes are included in this PR?
The root cause is that equality is evaluated using
PartialEq::eq. So the fix is usingto_bitsfor equality check for float values like asScalarValue::Float64does.NOTE: This issue doesn't happen when
SimplifiersimplifiesINexpr likeAre these changes tested?
Modified an existing test.
Are there any user-facing changes?
Yes, but this change is for correctness.