Skip to content

Conversation

@v0idpwn
Copy link

@v0idpwn v0idpwn commented Oct 16, 2025

In some situations (e.g.: when using enums), sqlx will fire additional queries to fetch data from the database. Currently, these queries use persistent=true, regardless of whether the parent query did. Unfortunately, this means that sqlx can't be used with connection poolers that don't support named prepared statements.

In this patch, I try to propagate the persistent attribute from the parent query to the other queries.

Additionally, this requires a potential re-send of Parse/Describe, as the additional queries will replace the existing one Postgres-side. For example, this is a sequence that could happen (after propagating persistent):

Parse("", "SELECT * FROM mytable")
Describe("")
Sync()
Parse("", "SELECT enumlabel FROM pg_catalog.pg_enum WHERE enumtypid = $1 ORDER BY enumsortorder") Describe("")
Sync()
Bind(...)
Execute(..)
Sync()
Bind(...)
Execute(..)
Sync()

This obviously won't work, as the second Parse message overwrote the unnamed prepared statement. Hence I added a re-preparation of the original query:

Parse("", "SELECT * FROM mytable")
Describe("")
Sync()
Parse("", "SELECT enumlabel FROM pg_catalog.pg_enum WHERE enumtypid = $1 ORDER BY enumsortorder") Describe("")
Sync()
Bind(...)
Execute(..)
Sync()
Parse("", "SELECT * FROM mytable")
Describe("")
Bind(...)
Execute(..)
Sync()

Let me know if I missed any place where this should be done, or if you disagree in the concept.

Is this a breaking change?

I don't believe so.

In some situation (e.g.: when using enums), sqlx will fire additional
queries to fetch data from the database. Currently, these queries
use `persistent=true`, regardless of whether the parent query did.
Unfortunately, this means that sqlx can't be used with connection
poolers that don't support named prepared statements.

In this patch, I try to propagate the `persistent` attribute from
the parent query to the other queries.

Additionally, this requires a potential re-send of `Parse/Describe`,
as the additional queries will replace the existing one Postgres-side.
For example, this is a sequence that could happen:

Parse("", "SELECT * FROM mytable")
Describe("")
Sync()
Parse("", "SELECT enumlabel FROM pg_catalog.pg_enum WHERE enumtypid = $1 ORDER BY enumsortorder")
Describe("")
Sync()
Bind(...)
Execute(..)
Sync()
Bind(...)
Execute(..)
Sync()

This obviously won't work, as the second Parse message overwrote the
unnamed prepared statement. Hence I added a re-preparation of the
original query:

Parse("", "SELECT * FROM mytable")
Describe("")
Sync()
Parse("", "SELECT enumlabel FROM pg_catalog.pg_enum WHERE enumtypid = $1 ORDER BY enumsortorder")
Describe("")
Sync()
Bind(...)
Execute(..)
Sync()
Parse("", "SELECT * FROM mytable")
Describe("")
Bind(...)
Execute(..)
Sync()

Let me know if I missed any place where this should be done, or if
you disagree in the concept.
@abonander
Copy link
Collaborator

This kind of situation is likely better handled by disabling prepared statement caching at the connection level, which can be done by setting the statement cache capacity to zero: https://docs.rs/sqlx/latest/sqlx/postgres/struct.PgConnectOptions.html#method.statement_cache_capacity

That might have the same problem of needing to re-prepare the query, though.

It might be better to use the raw_sql interface to fetch this information so we don't have to prepare the user's query twice.

@v0idpwn
Copy link
Author

v0idpwn commented Oct 22, 2025

Hi, @abonander, thanks for the review.
I tried setting statement_cache_capacity to zero but sqlx continues using named prepared statements, at least on 0.8.6.

@v0idpwn
Copy link
Author

v0idpwn commented Oct 23, 2025

I feel like statement_cache_capacity could set persistent=false at execution time if it's 0, so alongside this PR it would ensure that named prepared statements aren't used ever if statement_cache_capacity == 0

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