Skip to content

fix(live): deallocate the total count statement for windowed queries - #1108

Open
askalf wants to merge 3 commits into
electric-sql:mainfrom
askalf:fix/live-windowed-deallocate-total-count
Open

askalf wants to merge 3 commits into
electric-sql:mainfrom
askalf:fix/live-windowed-deallocate-total-count

Conversation

@askalf

@askalf askalf commented Sep 14, 2026

Copy link
Copy Markdown

Fixes #1111.

Summary

  • A windowed live.query (one given both offset and limit) prepares two statements, live_query_<id>_get(int, int) and live_query_<id>_get_total_count, but the unsubscribe teardown only ran DEALLOCATE live_query_<id>_get. The count statement survived every teardown while its backing view was dropped, so it dangled permanently and accumulated for the lifetime of the database (one more per subscribe/unsubscribe cycle of a paginated live query).
  • Fix: one line in the teardown, guarded by the existing isWindowed flag so non-windowed queries are untouched (a DEALLOCATE of a never-prepared name would error).
  • Tests in packages/pglite/tests/live.test.ts: a windowed round-trip asserting two live_query_* prepared statements while subscribed and none after unsubscribe, plus non-windowed and repeated-windowed teardown cases so the guard can't regress either way. Changeset added (patch).
$ npx vitest run tests/live.test.ts -t 'deallocates all prepared statements' --typecheck.enabled=false --pool=forks --poolOptions.forks.singleFork
   # base src/live/index.ts (fix reverted), rebuilt:
   × esm import > live > deallocates all prepared statements for a windowed query on unsubscribe
     → expected [ Array(1) ] to deeply equal []
   × cjs import > live > deallocates all prepared statements for a windowed query on unsubscribe
     → expected [ Array(1) ] to deeply equal []
      Tests  2 failed | 38 skipped (40)

   # this branch's src/live/index.ts, rebuilt:
 ✓ |pglite| tests/live.test.ts  (40 tests | 38 skipped) 18482ms
      Tests  2 passed | 38 skipped (40)

$ npx vitest run tests/live.test.ts --typecheck.enabled=false --pool=forks --poolOptions.forks.singleFork
      Tests  40 passed (40)

The survivor is dangling, not just leaked: EXECUTE live_query_<id>_get_total_count after unsubscribe fails with relation "live_query_<id>_view" does not exist.

Decisions

  • Mirror the setup in the teardown. isWindowed is the flag the prepare path already uses to decide whether the count statement exists, so the teardown now deallocates exactly what was prepared. No new state, no new helper.
  • Not DEALLOCATE unconditionally (errors on non-windowed queries, where the name was never prepared) and not DEALLOCATE ALL (would take out the caller's own prepared statements).

How the tests were run: tests/live.test.ts imports ../dist/live/index.js, so dist/live/index.{js,cjs} was rebuilt with esbuild from this branch's src/live/index.ts (same options as tsup.config.ts), against the published @electric-sql/pglite WASM artefacts, on Node 24. Not run here: the full pnpm build (needs the postgres-pglite submodule and Emscripten) and eslint (needs the workspace install); prettier --check is clean and the diff adds no types, exports or imports. CI here should cover both.

AI assistance: the bug was found and the fix and tests were drafted with AI tooling in my workflow; the test runs above were executed as pasted. I'm responsible for the change and will handle review feedback.

A windowed live query (one given an offset and limit) prepares both
live_query_<id>_get and live_query_<id>_get_total_count, but unsubscribe
only deallocated the former. The count statement outlived every teardown
while its backing view was dropped, so it was left permanently dangling
and accumulated for the lifetime of the database.
…down

Add regression coverage around the windowed total count deallocation: a
non-windowed query must still tear down cleanly without a total count
statement, a window of offset 0 / limit 0 is still a window, repeated
subscribe/unsubscribe cycles must not accumulate statements, and
unsubscribing one windowed query must leave a concurrent one working.
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.

[BUG]: windowed live.query leaks its total-count prepared statement on unsubscribe

1 participant