diff --git a/Cargo.lock b/Cargo.lock index a23433e762..c96208fd7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -490,7 +490,7 @@ dependencies = [ "getrandom 0.2.17", "instant", "pin-project-lite", - "rand", + "rand 0.8.5", "tokio", ] @@ -2385,7 +2385,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -2517,6 +2517,35 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf416e4cb72756655126f7dd7bb0af49c674f4c1b9903e80c009e0c37e552e6" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.17", + "tracing", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f644aa9e5e31d11896e024305d7e3c98a88884d9f8919dbf37a9991bc47a4b" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand 0.9.4", + "thiserror 2.0.17", +] + [[package]] name = "overload" version = "0.1.1" @@ -2565,7 +2594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2576,7 +2605,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2872,8 +2901,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -2883,7 +2922,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -2895,13 +2944,22 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "rand_xoshiro" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -3052,7 +3110,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "subtle", "zeroize", @@ -3068,7 +3126,7 @@ dependencies = [ "borsh", "bytes", "num-traits", - "rand", + "rand 0.8.5", "rkyv", "serde", "serde_json", @@ -3416,7 +3474,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -3499,7 +3557,7 @@ dependencies = [ "hex", "libsqlite3-sys", "paste", - "rand", + "rand 0.8.5", "rand_xoshiro", "serde", "serde_json", @@ -3573,6 +3631,7 @@ dependencies = [ "mac_address", "memchr", "native-tls", + "opentelemetry", "percent-encoding", "pin-project-lite", "rust_decimal", @@ -3590,6 +3649,7 @@ dependencies = [ "tokio-stream", "toml 0.8.16", "tracing", + "tracing-opentelemetry", "url", "uuid", "webpki-roots", @@ -3614,7 +3674,7 @@ dependencies = [ "axum", "dotenvy", "http-body-util", - "rand", + "rand 0.8.5", "regex", "serde", "serde_json", @@ -3691,7 +3751,7 @@ version = "0.9.0-alpha.1" dependencies = [ "color-eyre", "dotenvy", - "rand", + "rand 0.8.5", "rust_decimal", "sqlx", "sqlx-example-postgres-multi-database-accounts", @@ -3706,7 +3766,7 @@ version = "0.1.0" dependencies = [ "argon2 0.5.3", "password-hash 0.5.0", - "rand", + "rand 0.8.5", "serde", "sqlx", "thiserror 1.0.40", @@ -3732,7 +3792,7 @@ version = "0.9.0-alpha.1" dependencies = [ "color-eyre", "dotenvy", - "rand", + "rand 0.8.5", "rust_decimal", "sqlx", "sqlx-example-postgres-multi-tenant-accounts", @@ -3747,7 +3807,7 @@ version = "0.1.0" dependencies = [ "argon2 0.5.3", "password-hash 0.5.0", - "rand", + "rand 0.8.5", "serde", "sqlx", "thiserror 1.0.40", @@ -3905,7 +3965,7 @@ dependencies = [ "generic-array", "log", "percent-encoding", - "rand", + "rand 0.8.5", "rsa", "rust_decimal", "serde", @@ -3947,7 +4007,7 @@ dependencies = [ "md-5", "memchr", "num-bigint", - "rand", + "rand 0.8.5", "rust_decimal", "serde", "serde_json", @@ -4409,11 +4469,10 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -4462,6 +4521,22 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcf5959f39507d0d04d6413119c04f33b623f4f951ebcbdddddfad2d0623a9c" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "tracing", + "tracing-core", + "tracing-subscriber", + "web-time", +] + [[package]] name = "tracing-subscriber" version = "0.3.19" @@ -4732,6 +4807,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 961dc3cc84..6dd21592f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,14 @@ derive = ["sqlx-macros/derive"] macros = ["derive", "sqlx-macros/macros", "sqlx-core/offline", "sqlx-mysql?/offline", "sqlx-postgres?/offline", "sqlx-sqlite?/offline"] migrate = ["sqlx-core/migrate", "sqlx-macros?/migrate", "sqlx-mysql?/migrate", "sqlx-postgres?/migrate", "sqlx-sqlite?/migrate"] +# Append sqlcommenter trailing comments to outgoing queries on backends that +# support it (PostgreSQL, MySQL — SQLite has no managed observability +# consumer). Pulls trace context from OTel's globally configured propagator +# (typically W3C TraceContext via `tracing-opentelemetry`). When a comment +# is appended, the backend executor routes through the non-cached prepare +# path so the statement cache isn't polluted with one-shot entries. +sqlcommenter = ["sqlx-core/sqlcommenter", "sqlx-mysql?/sqlcommenter", "sqlx-postgres?/sqlcommenter"] + # Enable parsing of `sqlx.toml` for configuring macros and migrations. sqlx-toml = ["sqlx-core/sqlx-toml", "sqlx-macros?/sqlx-toml", "sqlx-sqlite?/sqlx-toml"] diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index 3133e34b07..d6c5631d15 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -45,6 +45,19 @@ sqlx-toml = ["serde", "toml/parse"] _unstable-doc = ["sqlx-toml"] +# Append a sqlcommenter-style trailing comment to each query sent to the +# database, sourcing trace context from OTel's globally configured +# `TextMapPropagator` (typically W3C TraceContext, yielding `traceparent` and +# `tracestate`). Lets server-side observability tools (Cloud SQL Insights, +# AlloyDB Insights, etc.) correlate query stats with upstream traces. +# Requires the user's app to have a propagator registered (otherwise no +# comment is emitted). +# +# Note: when a comment is appended, the backend executor routes through the +# non-cached prepare path so we don't fill the statement cache with one-shot +# entries. Same wire round-trips, no cache pollution. +sqlcommenter = ["dep:opentelemetry", "dep:tracing-opentelemetry"] + [dependencies] # Runtimes async-global-executor = { workspace = true, optional = true } @@ -96,6 +109,8 @@ sha2 = { version = "0.10.0", default-features = false, optional = true } #sqlformat = "0.2.0" tokio-stream = { version = "0.1.8", features = ["fs"], optional = true } tracing = { version = "0.1.37", features = ["log"] } +opentelemetry = { version = "0.30", default-features = false, features = ["trace"], optional = true } +tracing-opentelemetry = { version = "0.31", default-features = false, optional = true } smallvec = "1.13.1" url = { version = "2.2.2" } bstr = { version = "1.0.1", default-features = false, features = ["std"], optional = true } diff --git a/sqlx-core/src/lib.rs b/sqlx-core/src/lib.rs index 494c41e9bf..937b003380 100644 --- a/sqlx-core/src/lib.rs +++ b/sqlx-core/src/lib.rs @@ -73,6 +73,7 @@ pub mod query_as; pub mod query_builder; pub mod query_scalar; pub mod sql_str; +pub mod sqlcommenter; pub mod raw_sql; pub mod row; diff --git a/sqlx-core/src/sqlcommenter.rs b/sqlx-core/src/sqlcommenter.rs new file mode 100644 index 0000000000..86f4a1957d --- /dev/null +++ b/sqlx-core/src/sqlcommenter.rs @@ -0,0 +1,119 @@ +//! [sqlcommenter](https://google.github.io/sqlcommenter/) trailing-comment +//! generation for queries. +//! +//! When the `sqlcommenter` feature is enabled, backend executors call +//! [`maybe_append_comment`] to append a +//! `/*key1='value1',key2='value2'*/` comment to outgoing SQL so server-side +//! observability tools (Cloud SQL Insights, AlloyDB Insights, pg_stat_statements +//! consumers that preserve comments) can correlate query stats with the +//! upstream OTel trace. +//! +//! Implementation: we delegate the *what* to inject to OTel's globally +//! configured [`TextMapPropagator`](opentelemetry::propagation::TextMapPropagator). +//! Most apps install the W3C TraceContext propagator (yielding `traceparent` +//! and `tracestate`), which is exactly what the sqlcommenter spec calls for — +//! but if you've configured B3, Jaeger, a composite, or a custom propagator, +//! we hand off to that and use whatever keys/values it produces. Each +//! key/value pair is URL-encoded per RFC 3986 unreserved set, matching the +//! reference Python implementation. +//! +//! The trace context is read from the supplied tracing span via +//! `tracing_opentelemetry::OpenTelemetrySpanExt::context()`, which extracts +//! the OTel context the `tracing-opentelemetry` layer stashed on the span +//! when it was created. `opentelemetry::Context::current()` would *not* work +//! here: the layer doesn't attach the OTel context on span enter, so the +//! thread-local current context is unrelated to the active tracing span. +//! If no OTel layer is installed, no propagator is registered, or the span +//! has no valid trace context, this returns `None` and the query goes out +//! unmodified. + +use std::borrow::Cow; + +/// Builds the trailing sqlcommenter comment for a query whose +/// `QueryLogger` span is `span`, or `None` if there's no trace context to +/// embed. +/// +/// The comment is a no-op for the database engine (an SQL comment) but is +/// extracted by trace-aware observability tools. +#[cfg(feature = "sqlcommenter")] +pub fn comment_for_span(span: &tracing::Span) -> Option { + use opentelemetry::propagation::Injector; + use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; + + // RFC 3986 unreserved set: alphanumeric plus `-`, `_`, `.`, `~`. Matches the + // sqlcommenter reference (Python) implementation, which uses urllib's + // `quote` defaults. Anything else — including `*` (would close the comment + // when adjacent to `/`), `'` (would break the quoted value), and `,`/`=` + // (the separators in our output) — gets percent-encoded. + const ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC + .remove(b'-') + .remove(b'_') + .remove(b'.') + .remove(b'~'); + + /// Streams propagator output directly into the sqlcommenter wire format. + /// The opening `"/*"` is written on the first `set` call (so an empty + /// buffer unambiguously means "propagator emitted nothing"); subsequent + /// calls prepend `","`. The caller appends `"*/"` to close the comment. + #[derive(Default)] + struct SqlcommenterCarrier { + out: String, + } + + impl Injector for SqlcommenterCarrier { + fn set(&mut self, key: &str, value: String) { + if self.out.is_empty() { + // Pre-size to a conservative typical W3C TraceContext payload + // (`traceparent` ~70 chars plus `tracestate` and wrapping). + self.out.reserve(96); + self.out.push_str("/*"); + } else { + self.out.push(','); + } + self.out.extend(utf8_percent_encode(key, ENCODE_SET)); + self.out.push_str("='"); + self.out.extend(utf8_percent_encode(&value, ENCODE_SET)); + self.out.push('\''); + } + } + + // Pull the OTel context off the supplied tracing span directly via + // `tracing-opentelemetry`'s `SpanExt`. This downcasts the subscriber to + // find the OTel layer and reads the `OtelData` extension the layer + // stashed on the span at `on_new_span` — no current-context attach + // required. Returns a default (empty) context if no OTel layer is + // installed or the span has no OTel data, which we handle below via the + // empty-carrier check. + use tracing_opentelemetry::OpenTelemetrySpanExt as _; + let cx = span.context(); + let mut carrier = SqlcommenterCarrier::default(); + opentelemetry::global::get_text_map_propagator(|propagator| { + propagator.inject_context(&cx, &mut carrier); + }); + + if carrier.out.is_empty() { + // Propagator emitted nothing (no registered propagator, or no valid + // trace context in the current OTel context). + return None; + } + + carrier.out.push_str("*/"); + Some(carrier.out) +} + +/// Feature-disabled stub: always returns `None` so [`maybe_append_comment`] +/// is a zero-cost no-op when the user hasn't opted into sqlcommenter. +#[cfg(not(feature = "sqlcommenter"))] +pub fn comment_for_span(_span: &tracing::Span) -> Option { + None +} + +/// Appends [`comment_for_span`]'s output to `sql` if a trace context exists. +/// Returns the original `sql` borrowed otherwise so the no-context (and +/// feature-disabled) fast path allocates nothing. +pub fn maybe_append_comment<'a>(sql: &'a str, span: &tracing::Span) -> Cow<'a, str> { + match comment_for_span(span) { + Some(comment) => Cow::Owned(format!("{sql} {comment}")), + None => Cow::Borrowed(sql), + } +} diff --git a/sqlx-mysql/Cargo.toml b/sqlx-mysql/Cargo.toml index 519cf735e8..2ec19cf191 100644 --- a/sqlx-mysql/Cargo.toml +++ b/sqlx-mysql/Cargo.toml @@ -16,6 +16,7 @@ any = ["sqlx-core/any"] offline = ["sqlx-core/offline", "serde/derive", "bitflags/serde"] migrate = ["sqlx-core/migrate"] rsa = ["dep:rand", "dep:rsa"] +sqlcommenter = ["sqlx-core/sqlcommenter"] # Type Integration features bigdecimal = ["dep:bigdecimal", "sqlx-core/bigdecimal"] diff --git a/sqlx-mysql/src/connection/executor.rs b/sqlx-mysql/src/connection/executor.rs index eb95f94a50..3c84e833d3 100644 --- a/sqlx-mysql/src/connection/executor.rs +++ b/sqlx-mysql/src/connection/executor.rs @@ -112,13 +112,25 @@ impl MySqlConnection { let mut logger = QueryLogger::new_under_span(sql, self.inner.log_settings.clone(), parent_span) .with_db_system_name("mysql"); + // The generator needs its own clone of the span because the outer + // `InstrumentedStream::new(..., span)` below also consumes one. Cloning + // a `tracing::Span` is Arc-backed and cheap. let span = logger.span(); + let span_for_stream = span.clone(); self.inner.stream.wait_until_ready().await?; self.inner.stream.waiting.push_back(Waiting::Result); let stream = try_stream! { - let sql = logger.sql().as_str(); + // Same sqlcommenter handling as the postgres path. Computed inside the + // generator so the `Cow::Borrowed` case can live alongside `logger` + // (which is captured by move) without a self-referential closure. + let sql_cow = sqlx_core::sqlcommenter::maybe_append_comment(logger.sql().as_str(), &span_for_stream); + let sql = sql_cow.as_ref(); + // When a comment was actually appended, route through the non-cached prepare + // path so we don't fill the statement cache with one-shot entries whose + // trace id won't match future calls. Same wire round-trips. + let persistent = persistent && matches!(sql_cow, std::borrow::Cow::Borrowed(_)); // make a slot for the shared column data // as long as a reference to a row is not held past one iteration, this enables us diff --git a/sqlx-postgres/Cargo.toml b/sqlx-postgres/Cargo.toml index df45ab33ff..b491afb876 100644 --- a/sqlx-postgres/Cargo.toml +++ b/sqlx-postgres/Cargo.toml @@ -14,6 +14,7 @@ any = ["sqlx-core/any"] json = ["dep:serde", "dep:serde_json", "sqlx-core/json"] migrate = ["sqlx-core/migrate"] offline = ["json", "sqlx-core/offline", "smallvec/serde"] +sqlcommenter = ["sqlx-core/sqlcommenter"] # Type Integration features bigdecimal = ["dep:bigdecimal", "dep:num-bigint", "sqlx-core/bigdecimal"] diff --git a/sqlx-postgres/src/connection/executor.rs b/sqlx-postgres/src/connection/executor.rs index 589596754b..74e53c3f0b 100644 --- a/sqlx-postgres/src/connection/executor.rs +++ b/sqlx-postgres/src/connection/executor.rs @@ -26,10 +26,10 @@ async fn prepare( sql: &str, arg_types: &[PgTypeInfo], metadata: Option>, - persistent: bool, + named: bool, resolve_column_origin: bool, ) -> Result<(StatementId, Arc), Error> { - let id = if persistent { + let id = if named { let id = conn.inner.next_statement_id; conn.inner.next_statement_id = id.next(); id @@ -162,14 +162,27 @@ impl PgConnection { &mut self, sql: &str, parameters: &[PgTypeInfo], - persistent: bool, + // Use a server-side *named* prepared statement (vs the unnamed one). + // Named statements survive intervening simple-query messages, which is + // required if anything between Parse and Bind (e.g. type resolution + // queries from `resolve_statement_metadata` / `apply_patches`) might + // issue a simple Query — the unnamed statement would otherwise be + // destroyed by such a side trip. + named: bool, + // Insert this prepared statement into the per-connection LRU cache for + // later reuse. Implies `named`. When `false` and `named`, the caller is + // responsible for `Close::Statement`ing the returned id after use to + // avoid accumulating one-shot statements on the server. + cache: bool, // optional metadata that was provided by the user, this means they are reusing // a statement object metadata: Option>, resolve_column_origin: bool, ) -> Result<(StatementId, Arc), Error> { - if let Some(statement) = self.inner.cache_statement.get_mut(sql) { - return Ok((*statement).clone()); + if cache { + if let Some(statement) = self.inner.cache_statement.get_mut(sql) { + return Ok((*statement).clone()); + } } let statement = prepare( @@ -177,12 +190,12 @@ impl PgConnection { sql, parameters, metadata, - persistent, + named, resolve_column_origin, ) .await?; - if persistent && self.inner.cache_statement.is_enabled() { + if cache && self.inner.cache_statement.is_enabled() { if let Some((id, _)) = self.inner.cache_statement.insert(sql, statement.clone()) { self.inner.stream.write_msg(Close::Statement(id))?; self.write_sync(); @@ -209,7 +222,24 @@ impl PgConnection { QueryLogger::new_under_span(query, self.inner.log_settings.clone(), parent_span) .with_db_system_name("postgresql"); let span = logger.span(); - let sql = logger.sql().as_str(); + // When the `sqlcommenter` feature is enabled and an OTel trace context is + // active, appends `/*traceparent=...*/` to the SQL on the wire so server-side + // observability tools can correlate. With no context (or feature off), the + // returned Cow is Borrowed and this is a zero-cost no-op. + let sql_cow = sqlx_core::sqlcommenter::maybe_append_comment(logger.sql().as_str(), &span); + let sql = sql_cow.as_ref(); + // When a comment was appended, each call has a unique trace id baked + // into the SQL, so caching the prepared statement would (a) miss every + // time anyway and (b) silently fill the LRU with one-shot statements. + // Skip the cache in that case — but still use a *named* server-side + // statement, not the unnamed one: type-resolution side trips between + // Parse and Bind (`resolve_statement_metadata`, `apply_patches`) can + // issue simple Query messages, which destroy the unnamed statement. + // The named statement is `Close::Statement`d after Execute below so + // Postgres doesn't hold it. + let comment_appended = matches!(sql_cow, std::borrow::Cow::Owned(_)); + let cache = persistent && !comment_appended; + let named = persistent || comment_appended; // before we continue, wait until we are "ready" to accept more queries self.wait_until_ready().await?; @@ -233,7 +263,7 @@ impl PgConnection { // prepare the statement if this our first time executing it // always return the statement ID here let (statement, metadata_) = self - .get_or_prepare(sql, &arguments.types, persistent, metadata_opt, false) + .get_or_prepare(sql, &arguments.types, named, cache, metadata_opt, false) .await?; metadata = metadata_; @@ -275,6 +305,14 @@ impl PgConnection { .stream .write_msg(Close::Portal(PortalId::UNNAMED))?; + // If we prepared a named statement that won't be reused via the + // LRU cache (e.g. a sqlcommenter-traced query whose SQL is unique + // per trace), close it server-side so Postgres doesn't hold it. + // The matching CloseComplete is absorbed by the stream loop below. + if named && !cache && statement != StatementId::UNNAMED { + self.inner.stream.write_msg(Close::Statement(statement))?; + } + // finally, [Sync] asks postgres to process the messages that we sent and respond with // a [ReadyForQuery] message when it's completely done. Theoretically, we could send // dozens of queries before a [Sync] and postgres can handle that. Execution on the server @@ -460,7 +498,7 @@ impl<'c> Executor<'c> for &'c mut PgConnection { self.wait_until_ready().await?; let (_, metadata) = self - .get_or_prepare(sql.as_str(), parameters, true, None, true) + .get_or_prepare(sql.as_str(), parameters, true, true, None, true) .await?; Ok(PgStatement { sql, metadata }) @@ -479,7 +517,7 @@ impl<'c> Executor<'c> for &'c mut PgConnection { self.wait_until_ready().await?; let (stmt_id, metadata) = self - .get_or_prepare(sql.as_str(), &[], true, None, true) + .get_or_prepare(sql.as_str(), &[], true, true, None, true) .await?; let nullable = self.get_nullable_for_columns(stmt_id, &metadata).await?;