OVER¶
Status: Native across all four frame shapes, with the gaps enumerated below.
OVER runs over one ascending order (rowtime or, where noted, proctime) and one window group. Each
aggregate reads its own — possibly different — value column of type
bigint/int/smallint/tinyint/double/float (narrow ints and 4-byte float keep the host's narrow result
type rather than being widened). FIRST_VALUE/LAST_VALUE and the window functions
ROW_NUMBER/RANK/DENSE_RANK (no value column, unbounded frame) are admitted alongside the
aggregates below.
Constant NULL arguments are also supported with their declared type, including
MIN(CAST(NULL AS VARCHAR)), MAX(CAST(NULL AS VARCHAR)), and COUNT(CAST(NULL AS VARCHAR)).
They can share a frame with numeric aggregates and COUNT(*); typed NULL Arrow columns are
handled without attempting a numeric conversion.
Running MIN/MAX checkpoints preserve this typed NULL state alongside other aggregates.
Event-time parity fixtures keep the watermark behind the entire input timestamp range until end of input, so file enumeration order cannot introduce accidental late rows. Late-row tests control the input order separately.
Frame shapes¶
Unbounded RANGE … CURRENT ROW and ROWS … CURRENT ROW (running folds)¶
Both retain incremental aggregates per partition across batches. Event-time RANGE folds all rows tied at a timestamp before emitting their shared result. ROWS emits after each row, including separate running results for tied rows in their arrival order. Watermarks release buffered event-time rows in timestamp order; proctime folds eagerly in arrival order.
COUNT(*) counts every row, while COUNT(value) skips NULL values. The planner materializes
one non-null BIGINT constant for row counting and reuses the existing aggregate kernel, frame
state, TTL and checkpoint paths. This works for all four admitted frame shapes, including
mixed aggregates, nullable partition keys and unpartitioned input.
Bounded ROWS BETWEEN n PRECEDING AND CURRENT ROW¶
Recomputed over the row slice — a fixed count of preceding rows plus the current one.
Bounded RANGE BETWEEN INTERVAL n PRECEDING AND CURRENT ROW¶
Recomputed over the rowtime interval — every row within n of the current row's rowtime.
Late event-time rows¶
Unbounded frames admit only timestamps strictly greater than the current watermark. Bounded ROWS and RANGE frames instead compare against that partition's last-fired timestamp, initially zero. Equality is late; an untouched partition can accept a positive timestamp behind the global watermark. Rows tied before their timer fires remain admissible together.
The newest retained frame row preserves the last-fired timestamp through memory snapshots,
RocksDB checkpoints, and backend transitions. A zero-width ROWS frame keeps one marker row,
which is excluded from the next row's aggregate. RANGE eviction uses each partition's own
last timestamp. Its event-time cleanup timer clears the whole partition and resets admission:
Flink's deadline hysteresis registers timestamp + floor(1.5 * interval) + 1 when the existing
deadline precedes timestamp + interval + 1. Those deadlines are checkpointed alongside frames;
pending-only partitions reconstruct them from their buffered arrivals on canonical restore.
Proctime order¶
The running and bounded-ROWS frames are native on proctime as well: arrival order, eager emit, no wall-clock timer needed.
A bounded-RANGE frame over proctime falls back — with processing time materialized as a fixed per-batch timestamp, a wall-clock-interval frame has no meaningful definition.
Gaps¶
The matcher declines:
- Direct
AVGcalls. Flink can lower some SQL AVG forms to supported SUM/COUNT aggregates. - A decimal or other non-numeric value column, except constant NULL arguments.
- A
PARTITION BYkey outside bigint/int/string/boolean/date/timestamp/decimal. - A frame not of the form
… PRECEDING .. CURRENT ROW(aROWS/RANGElower bound that isn't a constant preceding offset or UNBOUNDED PRECEDING). - A bounded-RANGE frame over a proctime order.
Parity, not gaps¶
Flink itself rejects or single-groups these in streaming, so not running them natively matches Flink
rather than falling short of it: more than one window group, decimal bounded frames, FOLLOWING
frames, non-time or descending order, and LAG/LEAD.
Idle-state TTL¶
OVER runs table.exec.state.ttl natively across all four frame shapes, but the mechanics differ
by shape:
- Rowtime frames and the proctime bounded-ROWS frame share a per-key cleanup deadline (the same
scheme as the temporal join): registered on every element, with hysteresis and a
minRetentionTime > 1enablement threshold, checked lazily and swept, clearing the key's accumulator and frame buffer silently. One wrinkle: at the deadline, the rowtime shapes defer while the key still has buffered rows the watermark hasn't folded (the timer re-registers and waits), whereas the proctime bounded-ROWS frame clears its retract frame unconditionally — so that frame can observably restart short. - The proctime unbounded fold instead puts a per-value TTL (
> 0enables, refreshed on last write) directly on its accumulator. An expired key visibly restarts its running fold — and itsROW_NUMBER/RANKnumbering — from zero, exactly as Flink'sNeverReturnExpiredstate does. - The bounded-RANGE rowtime frame takes no retention at all: Flink's own function accepts none,
since event-time frame eviction and cleanup bound state, so
table.exec.state.ttlchanges nothing there.
With that, nothing declines a nonzero retention setting. See Configuration for the TTL flag surface, and window aggregate for the (unaffected — no idle-state TTL applies) window operators.
Running ROWS validation¶
The focused Flink 2.2.1 suite covers COUNT(*) versus nullable COUNT, mixed aggregates, constant arguments, DISTINCT, empty/all-NULL input, multiple partitions, timestamp peers, late admission, and 5,003-row multi-batch input. Operator tests cover TTL expiry and running-state continuation across RocksDB checkpoints and memory/RocksDB backend transitions.
RunningRowsBenchmark measures proctime COUNT(), COUNT(value), SUM, MIN and MAX over one
million rows and 64 keys, parallelism one, with both row/Arrow transposes and a row blackhole sink.
On an Apple M1 Max/JDK 17, the September 16, 2026 release run used two warmups and five interleaved
measurements: median Flink 0.605496 s, native 0.591282 s (1.024x*). The difference is small;
this establishes comparable end-to-end performance for the new coverage, not a substantial
speedup. Keeping OVER columnar also lets it compose with adjacent native operators.
SF_BENCHMARK=true mvn -pl streamfusion-runtime -am test -Pbench -Dtest=RunningRowsBenchmark