Repository navigation
fix(smj): handle floating-point keys across batch boundaries - #25134
Conversation
Use the existing join-key comparator for floating-point boundary keys, slicing the new batch to the single row being compared. Add regression coverage for mark, semi, and anti joins across input batches. Generated-by: OpenAI Codex
sunchao
left a comment
There was a problem hiding this comment.
Thanks, Jeff. LGTM at 9ea2dea17b53b8ed2f8ea877df3d95604b87f450.
Reviewed with five independent passes covering floating-point equality, boundary state transitions, test coverage, composite keys, and allocation behavior. I found no actionable issues. Reusing JoinKeyComparator keeps boundary checks consistent with the main merge scan, and slicing both sides to one row bounds signed-zero normalization work.
Independent supplemental validation:
- 233 sort-merge tests passed, including all 144 new regression cases.
- The same 144 regression cases failed on the unpatched base: 96 NaN panics and 48 Float16 unsupported-type errors.
- An additional 384-case row oracle passed, covering filters, composite keys, independent null placement, asymmetric/empty input batches, signed zeros, and distinct NaN encodings.
- A helper-level allocation probe confirmed batch-size-independent boundary-comparison costs. This was not an end-to-end performance measurement.
Local validation used isolated base/head checkouts and separate build directories. The configured package source lacked pinned dependencies, so these runs used a reduced workspace with re-resolved dependencies, retaining Arrow 59.3; I did not independently reproduce the exact-lockfile full-workspace run.
At approval time, the extended-test workflow has passed; remaining Rust CI jobs are queued.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #25134 +/- ##
=======================================
Coverage 81.80% 81.80%
=======================================
Files 1130 1130
Lines 417754 417766 +12
Branches 417754 417766 +12
=======================================
+ Hits 341754 341769 +15
+ Misses 55875 55868 -7
- Partials 20125 20129 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…mark joins (apache#26830) ## Which issue does this PR close? Closes apache#26003. ## Rationale for this change `EXISTS`, `IN` and `NOT EXISTS` queries planned as sort-merge joins fail with "Unsupported data type in sort merge join comparator" on timezone-aware timestamp, `Dictionary`, `Decimal256` and `Time32`/`Time64` keys, once a key group runs to the end of a batch. Inner sort-merge join and hash join already work on these keys. ## What changes are included in this PR? The bitwise stream compares keys with `JoinKeyComparator` everywhere except in `keys_match`, the check for whether a key group continues into the next batch. That function still called `compare_join_arrays` for non-float keys, and `compare_join_arrays` only knows a fixed list of types. apache#25134 moved float keys over to `JoinKeyComparator`; this moves the rest. `keys_match` now builds a one-row comparator each time a key group reaches the end of a batch. The scan already rebuilds its comparators on every new batch, so I don't expect a measurable cost. One edge case changes: `compare_join_arrays` treated `DataType::Null` keys as always equal, while `JoinKeyComparator` treats them as unequal under `NullEqualsNothing`. As far as I can tell this can't change any output, because such keys never compare equal in the scan either, so the code paths that call `keys_match` don't run for them. `compare_join_arrays` has no callers left, but it's public in `joins::utils`, so I left it alone. Happy to deprecate it here or in a follow-up if you prefer. ## What is the testing strategy for this PR? - `sort_merge_join.slt`: the reproducer from the issue, plus `batch_size = 2` queries for `Dictionary`, `Decimal256` and `Time64` keys. - `join_key_type_batch_boundaries` in `sort_merge_join/tests.rs`: the same matrix as `join_float_key_batch_boundaries` (join type, null equality, sort direction, 1- and 2-row batches) for the five key types above. I moved the body of the float test into a shared helper so the two don't duplicate it. Like apache#25134, it's a physical-plan test, so batches split at exact rows regardless of what the optimizer does. Without the fix, all of the new cases fail with the error from the issue. The change also covers other types `compare_join_arrays` didn't handle (e.g. `Duration`, `Interval`, `Decimal32`/`Decimal64`, nested types), but I haven't added tests for those. ## Are there any user-facing changes? Those queries now return results instead of an error. No API changes.
Push float comparisons to Iceberg as exact predicates. Arrow and Iceberg order floats by IEEE totalOrder, while PostgreSQL treats -0 and 0 as equal and orders every NaN above every number. The new `predicate::float` rewrites each comparison so it accepts exactly the rows PostgreSQL accepts: - A zero constant gets the sign that puts both zeros on the correct side. - `>` and `>=` also accept `isnan`, and `<` and `<=` reject it. - A double value compared with a real column is rounded to the neighbouring real on the side each operator keeps. The planner costs `>` and `>=` as the OR with `isnan` that they bind to. Query offload now accepts float comparisons, GROUP BY keys and join keys. It no longer rejects float join keys. DataFusion folds -0 into +0 and keeps NaN payloads apart, which is an accepted engine difference. Float ORDER BY stays in PostgreSQL. Comments now record the upstream defects that are not worked around here: - DataFusion 55's float IN-list, and the IN-list dynamic filter built from a small hash-join build side, compare float bits and miss -0 rows (apache/datafusion#25186). - The sort-merge semi/anti/mark fallback panics on a NaN key at a batch boundary (apache/datafusion#25134). - Iceberg filters on files written before a type promotion round the literal down to the old type (apache/iceberg-rust#3122, #3540). SUM(int8) now casts its input to Decimal128(38,0) before DataFusion's native SUM, because the native Int64 SUM wraps on overflow. The sum stays exact and can be compared in HAVING. Remove the now-unused `NumericFromInt64` output codec.
…ies (#25134) (#27353) ## Which issue does this PR close? - Part of #26868 (the 55.3.0 patch release tracking issue). - Backport of #25134 to `branch-55`. #25134 fixed #25133. ## Rationale for this change On 55.2.0, a mark, semi or anti sort-merge join on a floating-point key fails when a key group ends at the end of an input batch: a Float32 or Float64 key that is NaN panics with ``called `Option::unwrap()` on a `None` value``, and a Float16 key returns `Unsupported data type in sort merge join comparator: Float16`. The main merge scan handles the same keys. These are failed queries, which makes this eligible for backport as a correctness fix. #25134 fixed it on `main` (labelled `v56.0.0`); it is on `branch-56` but not on `branch-55`. It was requested for 55.3.0 on #26868 (#26868 (comment)). ## What changes are included in this PR? - A `git cherry-pick -x` of 2abf69d (#25134, by @jeffw13). It applied without conflicts and this PR changes nothing else. - `datafusion/physical-plan/src/joins/sort_merge_join/bitwise_stream.rs`: `keys_match` uses the existing `JoinKeyComparator` for floating-point keys, matching the merge scan's equality (including signed zero), instead of the scalar comparator whose `partial_cmp` panics on NaN. Integer-only comparisons keep the scalar path. - `datafusion/physical-plan/src/joins/sort_merge_join/tests.rs`: `join_float_key_batch_boundaries`, 144 cases (Float16/32/64, left/right mark/semi/anti, both null-equality modes, ascending/descending, one- and two-row input batches). ## Are these changes tested? Yes, by the 144 cases that #25134 added. On this branch (Rust 1.95.0) I ran: - `cargo test -p datafusion-physical-plan --lib`: 1844 passed. - With the change in `bitwise_stream.rs` reverted and the tests kept, `cargo test -p datafusion-physical-plan --lib sort_merge_join` fails exactly the 144 new cases (96 NaN panics and 48 Float16 errors). - `cargo fmt --all -- --check` and `cargo clippy -p datafusion-physical-plan --all-targets --all-features -- -D warnings` pass. ## Are there any user-facing changes? Affected sort-merge joins complete instead of failing at floating-point batch boundaries. No public API change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Jeff Wang <[email protected]> Co-authored-by: Claude Sonnet 5.5 <[email protected]>
…ies (#25134) (#27357) ## Which issue does this PR close? - Part of #23805 (the 54.2.0 patch release tracking issue). - Backport of #25134 to `branch-54`. #25134 fixed #25133. ## Rationale for this change On `branch-54`, a mark, semi or anti sort-merge join on a floating-point key fails when a key group ends at the end of an input batch: a Float32 or Float64 key that is NaN panics with ``called `Option::unwrap()` on a `None` value``, and a Float16 key returns `Unsupported data type in sort merge join comparator: Float16`. The main merge scan handles the same keys. These are failed queries, which makes this eligible for backport as a correctness fix. #25134 fixed it on `main` (labelled `v56.0.0`); it is on `branch-56` but not on `branch-54`. It was requested for `branch-54` on #26868 (#26868 (comment)), "to prevent regressions on upgrade to 54". ## What changes are included in this PR? - A `git cherry-pick -x` of 2abf69d (#25134, by @jeffw13). It applied without conflicts and this PR changes nothing else. - `datafusion/physical-plan/src/joins/sort_merge_join/bitwise_stream.rs`: `keys_match` uses the existing `JoinKeyComparator` for floating-point keys, matching the merge scan's equality (including signed zero), instead of the scalar comparator whose `partial_cmp` panics on NaN. Integer-only comparisons keep the scalar path. - `datafusion/physical-plan/src/joins/sort_merge_join/tests.rs`: `join_float_key_batch_boundaries`, 144 cases (Float16/32/64, left/right mark/semi/anti, both null-equality modes, ascending/descending, one- and two-row input batches). ## Are these changes tested? Yes, by the 144 cases that #25134 added. On this branch (Rust 1.95.0) I ran: - `cargo test -p datafusion-physical-plan --lib`: 1583 passed. - With the change in `bitwise_stream.rs` reverted and the tests kept, `cargo test -p datafusion-physical-plan --lib sort_merge_join` fails exactly the 144 new cases (96 NaN panics and 48 Float16 errors). - `cargo fmt --all -- --check` and `cargo clippy -p datafusion-physical-plan --all-targets --all-features -- -D warnings` pass. ## Are there any user-facing changes? Affected sort-merge joins complete instead of failing at floating-point batch boundaries. No public API change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Jeff Wang <[email protected]> Co-authored-by: Claude Sonnet 5.5 <[email protected]>
Which issue does this PR close?
Closes #25133.
Rationale for this change
Sort-merge mark, semi, and anti joins can fail when floating-point key groups cross input batches. Float32/Float64 keys containing NaN can panic, and Float16 keys can return an unsupported-type error, even though the main merge scan supports these keys.
What changes are included in this PR?
Use the existing
JoinKeyComparatorfor batch-boundary comparisons involving floating-point keys, matching the main merge scan's equality semantics. Slice the right-hand keys to one row before constructing the comparator; the left-hand keys are already single-row slices. This bounds signed-zero normalization to the rows being compared.Integer-only comparisons keep the existing scalar path. There are no memory-accounting, buffering, configuration, dependency, or public-API changes.
What is the testing strategy for this PR?
join_float_key_batch_boundariesindatafusion/physical-plan/src/joins/sort_merge_join/tests.rsexercises actual one- and two-row input batches. Its 144 cases cover Float16/32/64, left/right mark/semi/anti joins, both null-equality modes, and ascending/descending order. Inputs include nulls, infinities, signed zero, unmatched keys, and NaN groups spanning batches. Assertions check exact output row IDs/order, key types, and mark values.A physical-plan test is used because the reproducer needs precise input batch boundaries, independent of SQL optimizer choices.
On unpatched Apache main (
40488988ad596c9b093ad60e1453430d803ce33c) plus only the new tests, all 144 cases fail: 96 NaN panics and 48 Float16 unsupported-type errors.Local validation of
9ea2dea17b53b8ed2f8ea877df3d95604b87f450:cargo fmt --all -- --check,git diff --check,cargo clippy --locked --all-targets --all-features -- -D warnings, and the complete./dev/rust_lint.shsuite, including documentation: passed. The lint suite used the CI-pinned Hawkeye 7.0.0.The initial workspace attempt failed four sort-only RSS runners (
sort_no_mem_limit_runnerandsort_with_mem_limit_{1,2,3}_runner) with a zero baseline RSS. All four passed an isolated serial rerun, and the subsequent full workspace rerun passed without source/test changes. The cause of the initial intermittent failures has not been established.Supplemental standalone validation, not committed to this PR: an independent row-by-row oracle passed 2,592 cases covering asymmetric/empty input batches, filters, composite keys, both null placements and the float/join options above. An allocation probe confirmed the single-row slicing bounds comparator allocation independently of the full input batch size (32 through 1,048,576 rows). These checks supplement, rather than replace, the committed regression tests.
The existing eight-case
sort_merge_joinrelease benchmark completed on the exact upstream base and PR, with an identical harness, compiler and lockfile. Runs were ordered base→PR and PR→base, using 30 samples, one-second warmup and a three-second requested measurement time. Short-run differences were mixed; the left-semi delta reversed from +3.5% to -2.6%. Inner 1-to-1 was initially slower (+9.9% and +3.7%), so it was repeated in three longer, alternating-order pairs (50 samples, eight-second requested measurement time). The PR/base point-estimate differences were -0.17%, +0.26% and -0.84%; Criterion reported change within its noise threshold or no change. The initial slowdown did not reproduce. These are integer-key control workloads on a development machine, not a floating-point speedup benchmark; no speedup or general performance-neutrality claim is made.Upstream test workflows currently require maintainer approval (
action_required); local results above are not a claim that GitHub CI or coverage checks have passed.Are there any user-facing changes?
Affected sort-merge joins can complete instead of failing at floating-point batch boundaries. No public API changes.
AI-assisted contribution (OpenAI Codex).