Skip to content

fix(prepared_statements): follow the client's resolved pooler mode - #1538

Open
ewhauser wants to merge 2 commits into
pgdogdev:mainfrom
ewhauser:fix/prepared-statements-follow-cluster-pooler-mode
Open

ewhauser wants to merge 2 commits into
pgdogdev:mainfrom
ewhauser:fix/prepared-statements-follow-cluster-pooler-mode

Conversation

@ewhauser

Copy link
Copy Markdown

ConfigAndUsers::prepared_statements() only looked at the [general] pooler mode. With [general] pooler_mode = "session" and a user or database overridden to "transaction", prepared statement tracking was disabled for that user even though its pool multiplexes server connections. Under read/write split a named Parse (routed to a replica) and its Bind (routed to the primary when untracked) landed on different servers, so drivers with statement caches (pgx, npgsql, etc.) failed with prepared statement "..." does not exist (SQLSTATE 26000).

Add ConfigAndUsers::prepared_statements_for(pooler_mode) and have the client and mirror derive the level from the pooler mode of the cluster they are actually bound to, while still reading the level itself from live config on every request as before. Admin clients have no cluster and keep the [general] behaviour.

Fixes #1536

`ConfigAndUsers::prepared_statements()` only looked at the `[general]`
pooler mode. With `[general] pooler_mode = "session"` and a user or
database overridden to `"transaction"`, prepared statement tracking was
disabled for that user even though its pool multiplexes server
connections. Under read/write split a named Parse (routed to a replica)
and its Bind (routed to the primary when untracked) landed on different
servers, so drivers with statement caches (pgx, npgsql, etc.) failed with
`prepared statement "..." does not exist` (SQLSTATE 26000).

Add `ConfigAndUsers::prepared_statements_for(pooler_mode)` and have the
client and mirror derive the level from the pooler mode of the cluster
they are actually bound to, while still reading the level itself from
live config on every request as before. Admin clients have no cluster
and keep the `[general]` behaviour.

Fixes pgdogdev#1536
@CLAassistant

CLAassistant commented Sep 14, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…ients

`prepared_statements_for(Session)` forced tracking off per cluster,
which broke `test_session_multiple_statements_in_transaction`: a
session-pooled user on a schema-sharded cluster sends a named Parse +
Bind once psycopg auto-prepares, and without a tracked statement the
router has no SQL to route the Bind, so it lands on the wrong shard
("cannot switch shards in a direct-to-shard transaction").

Only multiplexing pools (transaction and statement mode) override the
`[general]` default; session-pooled clients keep the existing
`[general]`-derived behaviour, so nothing changes for them relative to
main.
Comment thread pgdog-config/src/core.rs
/// Prepared statements level for clients using the `[general]` pooler mode.
///
/// Prefer [`Self::prepared_statements_for`] with the pooler mode the client's
/// pool actually runs in: users and databases can override `pooler_mode`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this function re-use prepared_statements_for? Seems cleaner / less duplication.

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

Labels

None yet

Projects

None yet

3 participants