Generated execute() ignores the prepared statement: queries without RETURNING are re-prepared on every call

Aperta
#333 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
76/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
postgresql, rust

Direzione di ricerca

Read the no-row-type else branch in src/codegen/queries.rs around line 493, then compare how one/opt handle prepared statements in codegen/client.rs. Check the generated bind behavior for both sync and async clients, including statement caching. Done means generated execute helpers use the prepared statement or cache consistently, and a regression test covers queries without RETURNING.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Summary

For a query without a result set (no RETURNING), the generated bind ignores the prepared Statement it holds (self.1) and calls client.execute(self.0, …) with the SQL text. tokio-postgres then prepares an unnamed statement on every call, so every write costs an extra Parse/Describe round trip. The generated one/opt/all/iter helpers do use self.1 or C::stmt_cache(); only execute does not.

Where

src/codegen/queries.rs, the else branch for queries without a row type (line 493 on main at f454a7e):

client.execute(self.0, &[#(#params_wrap,)*])#fn_await

Reproduced with 1.0.1 (crates.io) and on main f454a7e.

Repro

--! touch_item (id)
UPDATE item SET updated_at = now() WHERE id = :id;

cornucopia schema schema.sql -q queries --async true --sync false. The generated TouchItemStmt::bind calls client.execute(self.0, &[id]). Executing it N times against a server with log_statement = all (or pg_stat_statements) shows N parses of an unnamed statement. A --! … : ()-style query that goes through one() does not.

Impact

Each write statement costs one extra round trip (Parse + Describe of an unnamed statement) and defeats the client-side statement cache. On a write-heavy API (deadpool-postgres behind PgBouncer in transaction mode) we patch the generated code as below. We have not measured this change on its own.

Workaround we use

After generation, a script rewrites client.execute(self.0, (any whitespace, because the output is wrapped over lines) to a helper appended to client/async_.rs:

pub async fn execute<C: GenericClient>(
    client: &C,
    query: &str,
    cached: Option<&Statement>,
    params: &[&(dyn ToSql + Sync)],
) -> Result<u64, Error> {
    if let Some(cached) = cached {
        client.execute(cached, params).await
    } else if C::stmt_cache() {
        let cached = client.prepare(query).await?;
        client.execute(&cached, params).await
    } else {
        client.execute(query, params).await
    }
}

That mirrors what the one/opt helpers in codegen/client.rs already do. Generating bind through a helper like this (sync and async) would fix it. Happy to open a PR if that shape is acceptable.

Lingua principale
Rust
Stelle
1.2k
Fork
77
Merge medio
5g 12h
PR unite (30g)
1

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di cornucopia-rs/cornucopia

Tutte le issue di cornucopia-rs/cornucopia

Issue simili

Altre issue su Rust

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.