Skip to content

fix(postgres): use unnamed prepared statements so poolers work - #86

Merged
oesukam merged 1 commit into
mainfrom
fix/pooler-unnamed-statements
Aug 4, 2026
Merged

fix(postgres): use unnamed prepared statements so poolers work#86
oesukam merged 1 commit into
mainfrom
fix/pooler-unnamed-statements

Conversation

@oesukam

@oesukam oesukam commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Why #85 wasn't enough

#85 set statement_cache_capacity(0), but the user still hit prepared statement "sqlx_s_1" does not exist through their PgBouncer after building locally.

Root cause (from sqlx 0.8 source, executor.rs): the cache capacity only disables statement reuse. sqlx still creates a named statement (sqlx_s_N) per query, because Query::persistent defaults to true. Named statements don't survive a transaction pooler — the next query lands on a different backend where the name isn't defined.

The real fix

Set .persistent(false) on every query → sqlx uses the unnamed prepared statement (per-execution), which works through PgBouncer / Supabase pooler / RDS Proxy.

Coverage:

  • lib.rs: bind_params() (both the SQL editor and JS db.query route through stream/execute → here) + the direct SELECT 1 / pg_backend_pid / pg_cancel_backend / SET statement_timeout calls.
  • introspect.rs: all 8 schema queries — these run during connect, which is exactly why Connect failed.
  • object_ops.rs: both.

JS scripting needs no separate change (it goes through the same driver methods).

Verified

  • cargo check + fmt + clippy clean.
  • Audited: every sqlx::query* in the crate now uses an unnamed statement.

Follow-up to statement_cache_capacity(0) (#85), which was NOT enough:
that only disables statement *reuse* — sqlx still creates a *named*
prepared statement (sqlx_s_N) per query by default (persistent: true).
Named statements don't survive a transaction pooler, so queries still
failed with 'prepared statement "sqlx_s_1" does not exist'.

Set .persistent(false) on every query so sqlx uses the UNNAMED statement,
which is per-execution and survives PgBouncer / Supabase pooler / RDS
Proxy. Covered:
- lib.rs: bind_params() (SQL editor + JS db.query both route here) plus
  the direct SELECT 1 / pg_backend_pid / pg_cancel_backend / SET calls
- introspect.rs: all 8 schema queries (these run during connect, which
  is why Connect specifically failed)
- object_ops.rs: both

JS scripting needs no separate change — db.query goes through
DbConnection::stream/execute → bind_params.
@oesukam
oesukam merged commit 04ad514 into main Aug 4, 2026
6 checks passed
@oesukam
oesukam deleted the fix/pooler-unnamed-statements branch August 4, 2026 15:55
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.

1 participant