sqlx icon indicating copy to clipboard operation
sqlx copied to clipboard

fix(postgres): propagate `persistent` to intermediary queries

Open v0idpwn opened this issue 3 months ago • 5 comments

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.

v0idpwn avatar Oct 16 '25 21:10 v0idpwn

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.

abonander avatar Oct 22 '25 17:10 abonander

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 avatar Oct 22 '25 18:10 v0idpwn

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

v0idpwn avatar Oct 23 '25 13:10 v0idpwn

Yeah, sorry if I was a little ambiguous there. I meant to say that statement_cache_capacity=0 should force these statements to be prepared with the unnamed statement but it does not right now. We should fix that.

Admittedly, it's also hard to discover. Maybe we should add an explicit connect option like PgConnectOptions::named_prepared_statements(enabled: bool) to force the use of only unnamed prepared statements when it's disabled.

abonander avatar Oct 28 '25 12:10 abonander

I see. I'll give it a stab :)

v0idpwn avatar Oct 28 '25 18:10 v0idpwn