Skip to content

Window sum, bit_* and bool_* don't return NULL after all non-NULL values leave the frame #27595

Open

Description

@yuhao-su

Problem

Once every non-NULL value has left a window frame, sum, bit_and, bit_or, bit_xor, bool_and and bool_or return 0, -1, false or true instead of NULL. It happens whether the frame is left with only NULLs or left empty, with ROWS and RANGE frames, in batch queries, streaming over window and EOWC over window.

Reproduced on main (a6100ed):

select ts, v, sum(v) over (order by ts rows between current row and current row) as s
from (values (0, 5), (10, null::int)) as t(ts, v)
order by ts;
 ts | v | s
----+---+---
  0 | 5 | 5
 10 |   | 0

PostgreSQL 14 returns NULL at ts = 10. The other functions on the same frame:

at ts = 10 RisingWave PostgreSQL
sum of int4, int8, decimal, float8, interval 0, 00:00:00 NULL
bit_and(v) -1 NULL
bit_or(v), bit_xor(v) 0 NULL
bool_or(b), bool_and(b) after true, false false, true NULL

A frame left empty behaves the same, for example sum(v) over (order by ts rows between 1 following and 1 following) on the last row, or a RANGE frame between 1 preceding and 1 preceding over a gap. avg, var_pop, stddev_samp and count are correct, and so is a frame that never held a non-NULL value.

Streaming has the same results:

create table t (p int, ts int, v int);
create materialized view mv as select p, ts, v,
  sum(v) over (partition by p order by ts rows between current row and current row) as s
from t;
insert into t values (1, 0, 5), (1, 10, null);

mv has s = 0 at ts = 10, and so does the EMIT ON WINDOW CLOSE version on an append-only table with a watermark on ts.

Cause

For a retractable aggregate, AggImpl::Incremental in src/expr/impl/src/window_function/aggregate.rs keeps one state, and accumulates or retracts the values that enter or leave the frame. These states don't count their non-NULL inputs: sum keeps the sum, and the retractable bit_* and bool_* keep counts of set bits or of true or false values. Once every non-NULL value is retracted, the state looks like that of non-NULL values that cancel out, so the result is not NULL.

The GROUP BY form of this bug was fixed in the planner by #27429, which pairs these aggregates with a count. Window function calls don't go through that rewrite (see #27429 (comment)).

Possible fix: count the non-NULL inputs next to the incremental state, and output NULL when the count is 0. Aggregate window states are not persisted (WindowStateSnapshot only covers the numbering functions), so this changes no stored format.

Activity

  1. added this to the release-3.1 milestone on Oct 21, 2026
  2. yashaggarwal85d commented on Oct 22, 2026

    @yashaggarwal85d
    Contributor

    I’d like to work on this. The fix keeps a count of non-NULL inputs alongside the incremental window state and outputs NULL when it reaches 0, for the same aggregates LogicalAgg::guard_empty_input_with_count guards (#27429). I have it ready with a unit test and an over-window SLT for batch and streaming.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type/bugType: Bug. Only for issues.

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions