Skip to content

fix: call aggregate init when there are no rows to aggregate - #641

Merged
lovasoa merged 2 commits into
sql-js:masterfrom
ybai08:fix/aggregate-init-on-empty-input
Oct 19, 2026
Merged

lovasoa merged 2 commits into
sql-js:masterfrom
ybai08:fix/aggregate-init-on-empty-input

Conversation

@ybai08

@ybai08 ybai08 commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

SQLite never calls xStep for an aggregate over an empty input; it only calls xFinal. create_aggregate initializes the JavaScript state inside the step wrapper, so on an empty input finalize receives undefined instead of the value returned by init:

db.create_aggregate("json_agg", {
    init: () => [],
    step: (state, val) => [...state, val],
    finalize: (state) => JSON.stringify(state),
});
db.exec("SELECT json_agg(x) FROM (SELECT 1 AS x WHERE 0)"); // NULL, expected '[]'

db.create_aggregate("count_rows", { init: () => 0, step: (s, v) => s + 1 });
db.exec("SELECT count_rows(x) FROM (SELECT 1 AS x WHERE 0)"); // NULL, expected 0

A finalize that reads properties of its state (for example state.vals.length with init: () => ({ vals: [] })) throws a TypeError on empty input, so the whole query fails.

The finalize wrapper now calls init when no state exists for the aggregate context yet, so finalize (or the default identity finalize) always receives an initialized state. Aggregates without init still return NULL on empty input, since the default init returns null.

Tests: added cases to test/test_aggregate_functions.js for an empty input with init + finalize, with init only, and without init.

Testing note: Emscripten is not installed on the machine this was prepared on, so the bundles were not rebuilt with npm run build. Instead, the src/api.js text inside the published 1.15.0 debug bundles (sql-wasm-debug.js, sql-wasm-inline-debug.js; identical to src/api.js at 2824d38) was replaced with the patched file (rebased on #640), and the test runner was run against those:

  • before the fix: node test/all.js wasm and node test/all.js wasm-debug -> FAIL test aggregate_functions ("Aggregate over no rows finalizes the value returned by init: null !== '[]'"), Passed:24 Failed:1
  • after the fix: node test/all.js wasm-debug and node test/all.js wasm-inline-debug -> Passed:25 Failed:0
  • npx eslint . passes; node test/builds.js passes

The optimized wasm, wasm-browser and wasm-inline bundles were not rebuilt, so the full npm test was not run with the change.

ybai08 and others added 2 commits October 19, 2026 01:17
SQLite never calls xStep for an aggregate over an empty input, only
xFinal. The JavaScript state was only initialized in the step wrapper,
so finalize received undefined instead of the value returned by init:
`SELECT json_agg(x) FROM t WHERE 0` returned NULL instead of '[]', an
aggregate with `init: () => 0` and no finalize returned NULL instead of
0, and a finalize that reads properties of its state threw a TypeError.

Initialize the state in the finalize wrapper as well when it is missing.
@lovasoa
lovasoa merged commit 77cd027 into sql-js:master Oct 19, 2026
4 checks passed
@lovasoa

lovasoa commented Oct 19, 2026

Copy link
Copy Markdown
Member

Thank you 👍

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants