A registered aggregate used as a window function with an empty OVER () clause segfaults.
Reproduction
require 'duckdb'
db = DuckDB::Database.open
con = db.connect
con.query('CREATE TABLE t AS SELECT i FROM generate_series(1, 100) s(i)')
con.register_aggregate_function(
DuckDB::AggregateFunction.create(
name: 'rb_cnt', return_type: :bigint, params: %i[bigint],
init: -> { 0 },
update: ->(state, _v) { state + 1 },
combine: ->(a, b) { (a || 0) + (b || 0) },
finalize: ->(state) { state }
)
)
con.query('SELECT rb_cnt(i) OVER () FROM t').to_a
lib/duckdb/connection.rb:39: [BUG] Segmentation fault at 0x0000000000000000
Exit status 139.
Crash site
duckdb_native.so(update_process_rows+0x12f) ext/duckdb/aggregate_function.c:315
duckdb_native.so(callback_with_gvl+0x11) ext/duckdb/function_executor.c:254
duckdb_native.so(update_callback+0x7b) ext/duckdb/aggregate_function.c:409
libduckdb.so duckdb::WindowConstantAggregatorLocalState::Sink(...)
libduckdb.so duckdb::WindowAggregateExecutor::Sink(...)
libduckdb.so duckdb::WindowLocalSourceState::Sink(...)
A NULL dereference inside update_process_rows, reached specifically through WindowConstantAggregatorLocalState::Sink. DuckDB uses that specialised aggregator when the frame is constant over the whole partition, which OVER () is; the states array or per-row layout it supplies evidently differs from what update_process_rows assumes. Root cause not established.
Scope
Pre-existing and not a regression. Confirmed on both #1444 and its parent commit, so it predates the state-registry work.
Note that a non-empty frame works correctly and is now covered by a test:
SELECT rb_cnt(i) OVER (ORDER BY i ROWS BETWEEN 100 PRECEDING AND 100 FOLLOWING) FROM t -- OK
See test_aggregate_as_window_function_reuses_source_state in test/duckdb_test/aggregate_function_test.rb. The OVER () form takes a different code path in DuckDB and remains uncovered.
A registered aggregate used as a window function with an empty
OVER ()clause segfaults.Reproduction
Exit status 139.
Crash site
A NULL dereference inside
update_process_rows, reached specifically throughWindowConstantAggregatorLocalState::Sink. DuckDB uses that specialised aggregator when the frame is constant over the whole partition, whichOVER ()is; the states array or per-row layout it supplies evidently differs from whatupdate_process_rowsassumes. Root cause not established.Scope
Pre-existing and not a regression. Confirmed on both #1444 and its parent commit, so it predates the state-registry work.
Note that a non-empty frame works correctly and is now covered by a test:
See
test_aggregate_as_window_function_reuses_source_stateintest/duckdb_test/aggregate_function_test.rb. TheOVER ()form takes a different code path in DuckDB and remains uncovered.