Skip to content

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

Description

@askalf

Describe the bug

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 unsubscribe only runs DEALLOCATE live_query_<id>_get. The count statement survives every teardown while its backing view live_query_<id>_view is dropped, so it is left permanently dangling and one more accumulates per subscribe/unsubscribe cycle for the lifetime of the database.

To Reproduce

import { PGlite } from '@electric-sql/pglite'
import { live } from '@electric-sql/pglite/live'

const pg = await PGlite.create({ extensions: { live } })
await pg.exec(`CREATE TABLE t (id serial PRIMARY KEY); INSERT INTO t DEFAULT VALUES;`)

const { unsubscribe } = await pg.live.query({
  query: 'SELECT * FROM t',
  offset: 0,
  limit: 10,
  callback: () => {},
})

const before = await pg.query(`SELECT name FROM pg_prepared_statements WHERE name LIKE 'live_query_%'`)
// before.rows: live_query_<id>_get and live_query_<id>_get_total_count

await unsubscribe()

const after = await pg.query(`SELECT name FROM pg_prepared_statements WHERE name LIKE 'live_query_%'`)
// expected: []
// actual:   [{ name: 'live_query_<id>_get_total_count' }]

The survivor is dangling, not merely leaked: EXECUTE live_query_<id>_get_total_count after unsubscribe fails because the view it selects from is gone. A non-windowed query (no offset/limit) tears down cleanly, since it never prepares the count statement.

The executable form of this is the test added in #1108 (packages/pglite/tests/live.test.ts, "deallocates all prepared statements for a windowed query on unsubscribe"), which fails on main with expected [ Array(1) ] to deeply equal [] and passes with the fix.

Logs

error: relation "live_query_<id>_view" does not exist

(from EXECUTE live_query_<id>_get_total_count after unsubscribe)

Details

  • PGlite version: 0.5.8 (main at the time of filing)
  • using any extensions? which ones? live
  • OS version: Linux x64
  • node, bun, deno or browser version: Node 24

Additional context

Cause is in packages/pglite/src/live/index.ts: the unsubscribe teardown deallocates live_query_<id>_get unconditionally but never the total-count statement, even though the prepare path guards its creation on the same isWindowed flag. Mirroring that guard in the teardown is the fix in #1108.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions