cool9850311 opened a new pull request, #26485:
URL: https://github.com/apache/datafusion/pull/26485
## Which issue does this PR close?
- Closes #26470.
## Rationale for this change
`bit_xor` over a sliding window frame returns `0` instead of `NULL` once
every non-NULL value has left the frame:
```sql
SELECT x,
bit_xor(x) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW) AS
sliding,
bit_xor(x) OVER (PARTITION BY i) AS
whole
FROM (VALUES (1, 5), (2, NULL), (3, 6)) t(i, x)
ORDER BY i;
+------+---------+-------+
| x | sliding | whole |
+------+---------+-------+
| 5 | 5 | 5 |
| NULL | 0 | NULL | <-- sliding should be NULL, as `whole` is
| 6 | 6 | 6 |
+------+---------+-------+
```
An aggregate with no non-NULL input returns NULL, and `bit_xor` is
documented as computing "the bitwise exclusive OR of all non-null input
values". The `0` is the XOR identity leaking out of the accumulator's internal
state. DuckDB returns NULL here.
The cause is that `BitXorAccumulator` keeps a single `Option<T::Native>`
whose `None` doubles as "has not seen a non-NULL value yet". `retract_batch`
can undo the value, since XOR is its own inverse, but it can never move the
state back from `Some` to `None`. So after the last non-NULL value leaves the
frame the accumulator is left holding `Some(0)`. Over a whole partition the
same aggregate is correct, because that path never retracts.
## What changes are included in this PR?
`BitXorAccumulator` now tracks the number of non-NULL values currently
accumulated and returns NULL when it is zero, the same way `avg` does (#22139).
`retract_batch` no longer delegates to `update_batch`, since the count has to
move the other way.
A genuine zero is preserved: `bit_xor` of `[5, 5]` still returns `0`.
`state_fields` is deliberately unchanged, so the state schema and its
serialization are untouched. The count never has to be serialized: `state()`
already yields NULL when there is no non-NULL input, and `merge_batch` goes
through `update_batch`, which adds nothing for a NULL state and one for a
non-NULL state.
`bit_and`, `bit_or` and `bit_xor(DISTINCT ..)` do not implement
`retract_batch`, so they cannot reach this state machine and are unaffected.
## What is the testing strategy for this PR?
Two layers:
- Four `sqllogictest` cases in
`datafusion/sqllogictest/test_files/window.slt`, placed next to the analogous
`avg` regression tests: the issue's own reproduction (the sliding frame
compared against the whole partition), a narrow frame that drains to empty on
every row, all-NULL input, and a case pinning down that a legitimate `0`
survives and that NULL follows it.
- Two accumulator unit tests in
`datafusion/functions-aggregate/src/bit_and_or_xor.rs`: the retract-to-empty
state machine, including that retracting NULLs does not move the count and that
the accumulator recovers when a non-NULL value re-enters, and the
legitimate-zero case.
I checked the tests fail without the fix: reverting the change to the
accumulator and keeping only the new tests turns three of the four slt blocks
red, each reporting `0` where NULL is expected. The all-NULL block passes
either way, so it is a guard rather than a reproduction.
I also verified the partial/final merge path directly, since `GROUP BY` uses
`PrimitiveGroupsAccumulator` and does not exercise this accumulator's
`state()`/`merge_batch()`: a partial over `[5, 5]` emits `0` as its state and
the final phase returns `0`, while merging only NULL states returns NULL.
The full test suite passes (`cargo test --profile ci --workspace --lib
--tests --bins` with the extended feature set), as do `cargo fmt --all` and
`ci/scripts/rust_clippy.sh`.
## Are there any user-facing changes?
Yes, a bug fix: `bit_xor` over a sliding window frame that contains no
non-NULL values now returns NULL instead of `0`. No public API changes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]