diff --git a/Cargo.toml b/Cargo.toml index 621cde9..fc2713b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dev-dependencies] +ctor = "0.4" opentelemetry_sdk = { version = "0.31", features = ["testing", "rt-tokio"] } proptest = "1" serial_test = "3" diff --git a/tests/common.rs b/tests/common.rs index ac823fe..28cc982 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -3,6 +3,7 @@ use opentelemetry::trace::{SpanKind, Status}; use opentelemetry_sdk::metrics::{InMemoryMetricExporter, PeriodicReader, SdkMeterProvider}; use opentelemetry_sdk::trace::{InMemorySpanExporter, SdkTracerProvider, SpanData}; +use sqlx_otel::QueryAnnotations; /// Test harness that installs in-memory span and metric exporters as the global providers, /// collects telemetry in-process, and cleans up on drop. @@ -171,3 +172,3088 @@ pub fn assert_error_span(span: &SpanData) { "exception.message should be a non-empty string, got {exception_message:?}", ); } + +// --------------------------------------------------------------------------- +// Backend parameterisation +// --------------------------------------------------------------------------- +// +// Why macros and not generic functions: the library's `impl_executor!` macro at +// `src/executor.rs` instantiates `Executor` impls for `&Pool`, `&mut +// PoolConnection`, `&mut Transaction<'_, DB>`, and the matching `Annotated` / +// `AnnotatedMut` wrappers, each gated by `for<'a> &'a mut DB::Connection: Executor<'a, +// Database = DB>`. Test bodies generic over `DB` (with that HRTB declared) trigger +// trait-resolution overflow on stable rustc – the compiler tries to satisfy the bound +// against multiple wrapper impls and recurses. Bumping `recursion_limit` does not +// help; the chain genuinely diverges. +// +// `macro_rules!` sidesteps the issue entirely: each invocation expands at the call +// site with concrete types, so the bound chain resolves directly against the upstream +// `sqlx::Sqlite` / `sqlx::Postgres` / `sqlx::MySql` impls. Trade-off: error messages +// point at the expansion site instead of the macro definition; mitigated by keeping +// each macro short and well-documented. + +/// Per-backend SQL fragments and metadata used by parameterised test bodies. +/// +/// Test bodies that vary only in SQL syntax (column types, upsert form, string concat) +/// read the relevant field from a `&Dialect` argument instead of being duplicated across +/// three backend files. Each backend file passes the matching constant +/// (`SQLITE_DIALECT`, `POSTGRES_DIALECT`, `MYSQL_DIALECT`) to the shared body. +pub struct Dialect { + /// The OpenTelemetry `db.system.name` value (`"sqlite"`, `"postgresql"`, `"mysql"`). + pub system: &'static str, + /// Column definition for an integer primary key in CREATE TABLE: e.g. `"INTEGER + /// PRIMARY KEY"` for sqlite, `"INT PRIMARY KEY"` for postgres / mysql. + pub id_pk_column: &'static str, + /// Column definition for a non-null text column: e.g. `"TEXT NOT NULL"` for sqlite + /// and postgres, `"VARCHAR(255) NOT NULL"` for mysql. + pub text_column: &'static str, + /// Full SQL for an upsert that updates `affected_test`'s row id=1 to a new name. + /// Each backend's syntax differs (`INSERT OR REPLACE` / `ON CONFLICT … DO UPDATE` / + /// `ON DUPLICATE KEY UPDATE`). + pub upsert_sql: &'static str, + /// Expected `db.response.affected_rows` for the upsert above. Sqlite and postgres + /// report `1`; mysql reports `2` (it counts match + update). + pub upsert_affected_rows: i64, + /// Full SQL for an UPDATE that mutates two rows by appending `_updated` to `name` + /// using the dialect's string-concat operator (`||` for sqlite/postgres, `CONCAT(...)` + /// for mysql). + pub string_concat_update_sql: &'static str, + /// Full SQL of the form `SELECT (?1 + ?2) AS sum`, accepting two `i32` binds and + /// returning an `i64` named `sum`. Each backend uses its own placeholder syntax and + /// (for postgres / mysql) explicit casts so the result fits in `i64` uniformly. + pub bind_two_sum_sql: &'static str, + /// Full SQL of the form `SELECT ` for `prepare_with` calls that supply + /// no concrete binds. Each backend uses its own placeholder syntax (`?` for sqlite + /// and mysql, `$1` for postgres). + pub prepare_with_select_sql: &'static str, +} + +pub const SQLITE_DIALECT: Dialect = Dialect { + system: "sqlite", + id_pk_column: "INTEGER PRIMARY KEY", + text_column: "TEXT NOT NULL", + upsert_sql: "INSERT OR REPLACE INTO affected_test (id, name) VALUES (1, 'alice_updated')", + upsert_affected_rows: 1, + string_concat_update_sql: "UPDATE affected_test SET name = name || '_updated' WHERE id IN (2, 3)", + bind_two_sum_sql: "SELECT ?1 + ?2 AS sum", + prepare_with_select_sql: "SELECT ?", +}; + +pub const POSTGRES_DIALECT: Dialect = Dialect { + system: "postgresql", + id_pk_column: "INT PRIMARY KEY", + text_column: "TEXT NOT NULL", + upsert_sql: "INSERT INTO affected_test (id, name) VALUES (1, 'alice_updated') \ + ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name", + upsert_affected_rows: 1, + string_concat_update_sql: "UPDATE affected_test SET name = name || '_updated' WHERE id IN (2, 3)", + bind_two_sum_sql: "SELECT ($1::bigint + $2::bigint) AS sum", + prepare_with_select_sql: "SELECT $1", +}; + +pub const MYSQL_DIALECT: Dialect = Dialect { + system: "mysql", + id_pk_column: "INT PRIMARY KEY", + text_column: "VARCHAR(255) NOT NULL", + upsert_sql: "INSERT INTO affected_test (id, name) VALUES (1, 'alice_updated') \ + ON DUPLICATE KEY UPDATE name = VALUES(name)", + // MySQL counts ON DUPLICATE KEY UPDATE as match (1) + update (1) = 2. + upsert_affected_rows: 2, + string_concat_update_sql: "UPDATE affected_test SET name = CONCAT(name, '_updated') WHERE id IN (2, 3)", + bind_two_sum_sql: "SELECT CAST(? + ? AS SIGNED) AS sum", + prepare_with_select_sql: "SELECT ?", +}; + +/// `DROP TABLE IF EXISTS` then `CREATE TABLE` at the supplied pool. Used at the top of +/// every parameterised body so sqlite (fresh `:memory:` per pool) and postgres / mysql +/// (shared container) behave identically. Expanded inline at the call site via the +/// `fresh_table!` macro so it operates on concrete pool types without HRTB issues. +#[macro_export] +macro_rules! fresh_table { + ($pool:expr, $table:expr, $columns:expr) => {{ + use sqlx::Executor as _; + let drop_sql = format!("DROP TABLE IF EXISTS {}", $table); + $pool.execute(drop_sql.as_str()).await.unwrap(); + let create_sql = format!("CREATE TABLE {} ({})", $table, $columns); + $pool.execute(create_sql.as_str()).await.unwrap(); + }}; +} + +/// The standard annotation set used across most annotation tests: +/// `db.operation.name = "SELECT"`, `db.collection.name = "users"`. +pub fn test_annotations() -> QueryAnnotations { + QueryAnnotations::new() + .operation("SELECT") + .collection("users") +} + +/// Assert that a span carries the attributes set by [`test_annotations`]. +/// +/// The `db.system.name` value is taken from the supplied `Dialect`, so this helper works +/// for every backend without per-file duplication. +pub fn assert_annotated_span(span: &SpanData, dialect: &Dialect) { + assert_eq!(span.span_kind, SpanKind::Client); + assert_eq!(span.name, "SELECT users"); + assert_eq!( + attr(span, "db.system.name"), + Some(opentelemetry::Value::String(dialect.system.into())), + ); + assert_eq!( + attr(span, "db.operation.name"), + Some(opentelemetry::Value::String("SELECT".into())), + ); + assert_eq!( + attr(span, "db.collection.name"), + Some(opentelemetry::Value::String("users".into())), + ); +} + +/// Assert that the exporter contains exactly one span and that it matches the standard +/// annotation shape. Used to collapse the recurring trailing assertion block at the end +/// of every annotation-style test (including the `sqlx::query!()` macro tests, whose +/// bodies must remain backend-specific but whose assertions can reuse this helper). +pub fn assert_one_annotated_span(tel: &TestTelemetry, dialect: &Dialect) { + let spans = tel.spans(); + assert_eq!( + spans.len(), + 1, + "expected exactly one span, got {}", + spans.len() + ); + assert_annotated_span(&spans[0], dialect); +} + +// --------------------------------------------------------------------------- +// Parameterised test bodies (macro_rules) +// --------------------------------------------------------------------------- +// +// Each macro takes a pool factory expression and a dialect constant and expands to +// the full test body. Backend wrappers invoke with their factory + dialect: +// +// #[tokio::test] +// #[serial] +// async fn execute_creates_span_via_pool() { +// test_execute_creates_span_via_pool!(test_pool().await, common::SQLITE_DIALECT); +// } + +/// Bound-chain proof: exercises `&Pool: Executor` (plain), `Annotated<'_, +/// Pool>: Executor` (`with_annotations`), and the same via the `with_operation` +/// shorthand. Each `pool.execute(...)` runs against a freshly created table, so the +/// macro is safe to invoke against shared postgres / mysql containers. +#[macro_export] +macro_rules! test_execute_creates_span_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + $crate::fresh_table!( + &pool, + "exec_pool_test", + &format!("id {}", $dialect.id_pk_column) + ); + tel.reset(); + + (&pool) + .execute("INSERT INTO exec_pool_test (id) VALUES (1)") + .await + .unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + assert!($crate::common::attr(&spans[0], "db.response.affected_rows").is_some()); + + pool.with_annotations($crate::common::test_annotations()) + .execute("INSERT INTO exec_pool_test (id) VALUES (2)") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + pool.with_operation("SELECT", "users") + .execute("INSERT INTO exec_pool_test (id) VALUES (3)") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// Counterpart to `test_execute_creates_span_via_pool!` for `&mut PoolConnection`. +/// Acquires a connection from the pool, then exercises plain / annotated / shorthand +/// executes against a freshly created table. +#[macro_export] +macro_rules! test_execute_creates_span_via_connection { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + $crate::fresh_table!( + &pool, + "exec_conn_test", + &format!("id {}", $dialect.id_pk_column) + ); + tel.reset(); + + let mut conn = pool.acquire().await.unwrap(); + (&mut conn) + .execute("INSERT INTO exec_conn_test (id) VALUES (1)") + .await + .unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + assert!($crate::common::attr(&spans[0], "db.response.affected_rows").is_some()); + + conn.with_annotations($crate::common::test_annotations()) + .execute("INSERT INTO exec_conn_test (id) VALUES (2)") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + conn.with_operation("SELECT", "users") + .execute("INSERT INTO exec_conn_test (id) VALUES (3)") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// Counterpart to `test_execute_creates_span_via_pool!` for `&mut Transaction<'_, DB>`. +/// Begins a transaction, runs three executes, and commits before asserting on the +/// collected spans. +#[macro_export] +macro_rules! test_execute_creates_span_via_transaction { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + $crate::fresh_table!( + &pool, + "exec_tx_test", + &format!("id {}", $dialect.id_pk_column) + ); + tel.reset(); + + let mut tx = pool.begin().await.unwrap(); + (&mut tx) + .execute("INSERT INTO exec_tx_test (id) VALUES (1)") + .await + .unwrap(); + + tx.with_annotations($crate::common::test_annotations()) + .execute("INSERT INTO exec_tx_test (id) VALUES (2)") + .await + .unwrap(); + + tx.with_operation("SELECT", "users") + .execute("INSERT INTO exec_tx_test (id) VALUES (3)") + .await + .unwrap(); + + tx.commit().await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 3); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + assert!($crate::common::attr(&spans[0], "db.response.affected_rows").is_some()); + $crate::common::assert_annotated_span(&spans[1], &$dialect); + $crate::common::assert_annotated_span(&spans[2], &$dialect); + }}; +} + +/// `execute` against invalid SQL records an error span. Exercises plain, annotated, and +/// shorthand annotation paths. +#[macro_export] +macro_rules! test_execute_records_error { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let result = (&pool).execute("INVALID SQL GIBBERISH").await; + assert!(result.is_err()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + $crate::common::assert_error_span(&spans[0]); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + + let result = pool + .with_annotations($crate::common::test_annotations()) + .execute("INVALID SQL GIBBERISH") + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + + let result = pool + .with_operation("SELECT", "users") + .execute("INVALID SQL GIBBERISH") + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + }}; +} + +/// `execute_many` over a multi-statement query yields one span per stream consumption. +/// Exercises plain, annotated, and shorthand paths against the wrapped pool. +#[macro_export] +macro_rules! test_execute_many_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut stream = (&pool).execute_many("SELECT 1; SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(0)) + ); + + let mut stream = pool + .with_annotations($crate::common::test_annotations()) + .execute_many("SELECT 1; SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + let mut stream = pool + .with_operation("SELECT", "users") + .execute_many("SELECT 1; SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `execute_many` against `&mut PoolConnection`. Same shape as the pool variant +/// but acquires a connection first. +#[macro_export] +macro_rules! test_execute_many_via_connection { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + let mut stream = (&mut conn).execute_many("SELECT 1; SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(0)) + ); + + let mut stream = conn + .with_annotations($crate::common::test_annotations()) + .execute_many("SELECT 1; SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + let mut stream = conn + .with_operation("SELECT", "users") + .execute_many("SELECT 1; SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `execute_many` against `&mut Transaction<'_, DB>`. Asserts on all three spans +/// after commit. +#[macro_export] +macro_rules! test_execute_many_via_transaction { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut tx = pool.begin().await.unwrap(); + let mut stream = (&mut tx).execute_many("SELECT 1; SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + + let mut stream = tx + .with_annotations($crate::common::test_annotations()) + .execute_many("SELECT 1; SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + + let mut stream = tx + .with_operation("SELECT", "users") + .execute_many("SELECT 1; SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + + tx.commit().await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 3); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(0)) + ); + $crate::common::assert_annotated_span(&spans[1], &$dialect); + $crate::common::assert_annotated_span(&spans[2], &$dialect); + }}; +} + +/// `execute_many` against invalid SQL records an error span on the streaming path. +#[macro_export] +macro_rules! test_execute_many_records_error { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut stream = (&pool).execute_many("INVALID SQL GIBBERISH"); + let result = stream.next().await; + assert!(result.is_some_and(|r| r.is_err())); + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + $crate::common::assert_error_span(&spans[0]); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(0)) + ); + + let mut stream = pool + .with_annotations($crate::common::test_annotations()) + .execute_many("INVALID SQL GIBBERISH"); + let result = stream.next().await; + assert!(result.is_some_and(|r| r.is_err())); + drop(stream); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + + let mut stream = pool + .with_operation("SELECT", "users") + .execute_many("INVALID SQL GIBBERISH"); + let result = stream.next().await; + assert!(result.is_some_and(|r| r.is_err())); + drop(stream); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + }}; +} + +/// `fetch` against the wrapped pool. Streams 2 rows, then exercises annotated and +/// shorthand variants. +#[macro_export] +macro_rules! test_fetch_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut stream = (&pool).fetch("SELECT 1 UNION ALL SELECT 2"); + let mut count = 0u64; + while stream.next().await.is_some() { + count += 1; + } + assert_eq!(count, 2); + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(2)) + ); + + let mut stream = pool + .with_annotations($crate::common::test_annotations()) + .fetch("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + let mut stream = pool + .with_operation("SELECT", "users") + .fetch("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `fetch` against `&mut PoolConnection`. +#[macro_export] +macro_rules! test_fetch_via_connection { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + let mut stream = (&mut conn).fetch("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(2)) + ); + + let mut stream = conn + .with_annotations($crate::common::test_annotations()) + .fetch("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + let mut stream = conn + .with_operation("SELECT", "users") + .fetch("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `fetch` against `&mut Transaction<'_, DB>`. +#[macro_export] +macro_rules! test_fetch_via_transaction { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut tx = pool.begin().await.unwrap(); + let mut stream = (&mut tx).fetch("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + + let mut stream = tx + .with_annotations($crate::common::test_annotations()) + .fetch("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + + let mut stream = tx + .with_operation("SELECT", "users") + .fetch("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + + tx.commit().await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 3); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(2)) + ); + $crate::common::assert_annotated_span(&spans[1], &$dialect); + $crate::common::assert_annotated_span(&spans[2], &$dialect); + }}; +} + +/// Verifies that dropping a `fetch` stream after consuming a single row still +/// finalises and exports the span (with `returned_rows` reflecting the partial read). +#[macro_export] +macro_rules! test_fetch_stream_dropped_early_still_records_span { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + { + let mut stream = (&pool).fetch("SELECT 1 UNION ALL SELECT 2"); + let _ = stream.next().await; + } + + let spans = tel.spans(); + assert_eq!( + spans.len(), + 1, + "span should be recorded even when stream is dropped early" + ); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(1)) + ); + }}; +} + +/// `fetch` against invalid SQL records an error span on the streaming path. +#[macro_export] +macro_rules! test_fetch_stream_records_error { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut stream = (&pool).fetch("INVALID SQL"); + let result = stream.next().await; + assert!(result.is_some_and(|r| r.is_err())); + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + $crate::common::assert_error_span(&spans[0]); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(0)) + ); + + let mut stream = pool + .with_annotations($crate::common::test_annotations()) + .fetch("INVALID SQL"); + let result = stream.next().await; + assert!(result.is_some_and(|r| r.is_err())); + drop(stream); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + + let mut stream = pool.with_operation("SELECT", "users").fetch("INVALID SQL"); + let result = stream.next().await; + assert!(result.is_some_and(|r| r.is_err())); + drop(stream); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + }}; +} + +/// `fetch_many` against the wrapped pool. Returns rows + result rows on a stream. +#[macro_export] +macro_rules! test_fetch_many_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut stream = (&pool).fetch_many("SELECT 1 UNION ALL SELECT 2"); + let mut rows = 0u64; + let mut results = 0u64; + while let Some(item) = stream.next().await { + match item.unwrap() { + sqlx::Either::Left(_) => results += 1, + sqlx::Either::Right(_) => rows += 1, + } + } + drop(stream); + + assert_eq!(rows, 2); + assert!(results >= 1, "should have at least one QueryResult"); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(2)) + ); + + let mut stream = pool + .with_annotations($crate::common::test_annotations()) + .fetch_many("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + let mut stream = pool + .with_operation("SELECT", "users") + .fetch_many("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `fetch_many` against `&mut PoolConnection`. +#[macro_export] +macro_rules! test_fetch_many_via_connection { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + let mut stream = (&mut conn).fetch_many("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(2)) + ); + + let mut stream = conn + .with_annotations($crate::common::test_annotations()) + .fetch_many("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + let mut stream = conn + .with_operation("SELECT", "users") + .fetch_many("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `fetch_many` against `&mut Transaction<'_, DB>`. +#[macro_export] +macro_rules! test_fetch_many_via_transaction { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut tx = pool.begin().await.unwrap(); + let mut stream = (&mut tx).fetch_many("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + + let mut stream = tx + .with_annotations($crate::common::test_annotations()) + .fetch_many("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + + let mut stream = tx + .with_operation("SELECT", "users") + .fetch_many("SELECT 1 UNION ALL SELECT 2"); + while stream.next().await.is_some() {} + drop(stream); + + tx.commit().await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 3); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(2)) + ); + $crate::common::assert_annotated_span(&spans[1], &$dialect); + $crate::common::assert_annotated_span(&spans[2], &$dialect); + }}; +} + +/// Verifies that dropping a `fetch_many` stream after consuming a single row still +/// finalises and exports the span. +#[macro_export] +macro_rules! test_fetch_many_dropped_early_still_records_span { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + { + let mut stream = (&pool).fetch_many("SELECT 1 UNION ALL SELECT 2"); + let _ = stream.next().await; + } + + let spans = tel.spans(); + assert_eq!( + spans.len(), + 1, + "span should be recorded even when stream is dropped early" + ); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(1)) + ); + }}; +} + +/// `fetch_many` against invalid SQL records an error span on the streaming path. +#[macro_export] +macro_rules! test_fetch_many_records_error { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut stream = (&pool).fetch_many("INVALID SQL GIBBERISH"); + let result = stream.next().await; + assert!(result.is_some_and(|r| r.is_err())); + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + $crate::common::assert_error_span(&spans[0]); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(0)) + ); + + let mut stream = pool + .with_annotations($crate::common::test_annotations()) + .fetch_many("INVALID SQL GIBBERISH"); + let result = stream.next().await; + assert!(result.is_some_and(|r| r.is_err())); + drop(stream); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + + let mut stream = pool + .with_operation("SELECT", "users") + .fetch_many("INVALID SQL GIBBERISH"); + let result = stream.next().await; + assert!(result.is_some_and(|r| r.is_err())); + drop(stream); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + }}; +} + +/// `fetch_all` against the wrapped pool. Returns 3 rows; exercises plain, annotated, +/// and shorthand variants. +#[macro_export] +macro_rules! test_fetch_all_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let rows = (&pool) + .fetch_all("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") + .await + .unwrap(); + assert_eq!(rows.len(), 3); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(3)) + ); + + pool.with_annotations($crate::common::test_annotations()) + .fetch_all("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + pool.with_operation("SELECT", "users") + .fetch_all("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `fetch_all` against `&mut PoolConnection`. +#[macro_export] +macro_rules! test_fetch_all_via_connection { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + let rows = (&mut conn) + .fetch_all("SELECT 1 UNION ALL SELECT 2") + .await + .unwrap(); + assert_eq!(rows.len(), 2); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(2)) + ); + + conn.with_annotations($crate::common::test_annotations()) + .fetch_all("SELECT 1 UNION ALL SELECT 2") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + conn.with_operation("SELECT", "users") + .fetch_all("SELECT 1 UNION ALL SELECT 2") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `fetch_all` against `&mut Transaction<'_, DB>`. +#[macro_export] +macro_rules! test_fetch_all_via_transaction { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut tx = pool.begin().await.unwrap(); + let rows = (&mut tx) + .fetch_all("SELECT 1 UNION ALL SELECT 2") + .await + .unwrap(); + assert_eq!(rows.len(), 2); + + tx.with_annotations($crate::common::test_annotations()) + .fetch_all("SELECT 1 UNION ALL SELECT 2") + .await + .unwrap(); + + tx.with_operation("SELECT", "users") + .fetch_all("SELECT 1 UNION ALL SELECT 2") + .await + .unwrap(); + + tx.commit().await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 3); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(2)) + ); + $crate::common::assert_annotated_span(&spans[1], &$dialect); + $crate::common::assert_annotated_span(&spans[2], &$dialect); + }}; +} + +/// `fetch_all` against invalid SQL records an error span. +#[macro_export] +macro_rules! test_fetch_all_records_error { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let result = (&pool).fetch_all("INVALID SQL GIBBERISH").await; + assert!(result.is_err()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + $crate::common::assert_error_span(&spans[0]); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + + let result = pool + .with_annotations($crate::common::test_annotations()) + .fetch_all("INVALID SQL GIBBERISH") + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + + let result = pool + .with_operation("SELECT", "users") + .fetch_all("INVALID SQL GIBBERISH") + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + }}; +} + +/// `fetch_one` against the wrapped pool. Returns 1 row. +#[macro_export] +macro_rules! test_fetch_one_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let _row = (&pool).fetch_one("SELECT 1").await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(1)) + ); + + pool.with_annotations($crate::common::test_annotations()) + .fetch_one("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + pool.with_operation("SELECT", "users") + .fetch_one("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `fetch_one` against `&mut PoolConnection`. +#[macro_export] +macro_rules! test_fetch_one_via_connection { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + let _row = (&mut conn).fetch_one("SELECT 1").await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(1)) + ); + + conn.with_annotations($crate::common::test_annotations()) + .fetch_one("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + conn.with_operation("SELECT", "users") + .fetch_one("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `fetch_one` against `&mut Transaction<'_, DB>`. +#[macro_export] +macro_rules! test_fetch_one_via_transaction { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut tx = pool.begin().await.unwrap(); + let _row = (&mut tx).fetch_one("SELECT 1").await.unwrap(); + + tx.with_annotations($crate::common::test_annotations()) + .fetch_one("SELECT 1") + .await + .unwrap(); + + tx.with_operation("SELECT", "users") + .fetch_one("SELECT 1") + .await + .unwrap(); + + tx.commit().await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 3); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(1)) + ); + $crate::common::assert_annotated_span(&spans[1], &$dialect); + $crate::common::assert_annotated_span(&spans[2], &$dialect); + }}; +} + +/// `fetch_one` against invalid SQL records an error span. +#[macro_export] +macro_rules! test_fetch_one_records_error { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let result = (&pool).fetch_one("INVALID SQL GIBBERISH").await; + assert!(result.is_err()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + $crate::common::assert_error_span(&spans[0]); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + + let result = pool + .with_annotations($crate::common::test_annotations()) + .fetch_one("INVALID SQL GIBBERISH") + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + + let result = pool + .with_operation("SELECT", "users") + .fetch_one("INVALID SQL GIBBERISH") + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + }}; +} + +/// `fetch_optional` against the wrapped pool when the query returns one row. +#[macro_export] +macro_rules! test_fetch_optional_records_one_row { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let result = (&pool).fetch_optional("SELECT 1").await.unwrap(); + assert!(result.is_some()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(1)) + ); + + pool.with_annotations($crate::common::test_annotations()) + .fetch_optional("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + pool.with_operation("SELECT", "users") + .fetch_optional("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `fetch_optional` against `&mut PoolConnection`. +#[macro_export] +macro_rules! test_fetch_optional_via_connection { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + let result = (&mut conn).fetch_optional("SELECT 1").await.unwrap(); + assert!(result.is_some()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(1)) + ); + + conn.with_annotations($crate::common::test_annotations()) + .fetch_optional("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + conn.with_operation("SELECT", "users") + .fetch_optional("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `fetch_optional` against `&mut Transaction<'_, DB>`. +#[macro_export] +macro_rules! test_fetch_optional_via_transaction { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut tx = pool.begin().await.unwrap(); + let result = (&mut tx).fetch_optional("SELECT 1").await.unwrap(); + assert!(result.is_some()); + + tx.with_annotations($crate::common::test_annotations()) + .fetch_optional("SELECT 1") + .await + .unwrap(); + + tx.with_operation("SELECT", "users") + .fetch_optional("SELECT 1") + .await + .unwrap(); + + tx.commit().await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 3); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(1)) + ); + $crate::common::assert_annotated_span(&spans[1], &$dialect); + $crate::common::assert_annotated_span(&spans[2], &$dialect); + }}; +} + +/// `fetch_optional` against invalid SQL records an error span. +#[macro_export] +macro_rules! test_fetch_optional_records_error { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let result = (&pool).fetch_optional("INVALID SQL GIBBERISH").await; + assert!(result.is_err()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + $crate::common::assert_error_span(&spans[0]); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + None + ); + + let result = pool + .with_annotations($crate::common::test_annotations()) + .fetch_optional("INVALID SQL GIBBERISH") + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + + let result = pool + .with_operation("SELECT", "users") + .fetch_optional("INVALID SQL GIBBERISH") + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + }}; +} + +// --------------------------------------------------------------------------- +// prepare / prepare_with / describe +// --------------------------------------------------------------------------- + +/// `prepare` against the wrapped pool. No rows returned; just verifies the span shape. +#[macro_export] +macro_rules! test_prepare_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let _stmt = (&pool).prepare("SELECT 1").await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + + pool.with_annotations($crate::common::test_annotations()) + .prepare("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + pool.with_operation("SELECT", "users") + .prepare("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `prepare` against `&mut PoolConnection`. +#[macro_export] +macro_rules! test_prepare_via_connection { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + let _stmt = (&mut conn).prepare("SELECT 1").await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + + conn.with_annotations($crate::common::test_annotations()) + .prepare("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + conn.with_operation("SELECT", "users") + .prepare("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `prepare` against `&mut Transaction<'_, DB>`. +#[macro_export] +macro_rules! test_prepare_via_transaction { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut tx = pool.begin().await.unwrap(); + let _stmt = (&mut tx).prepare("SELECT 1").await.unwrap(); + + tx.with_annotations($crate::common::test_annotations()) + .prepare("SELECT 1") + .await + .unwrap(); + + tx.with_operation("SELECT", "users") + .prepare("SELECT 1") + .await + .unwrap(); + + tx.commit().await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 3); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + $crate::common::assert_annotated_span(&spans[1], &$dialect); + $crate::common::assert_annotated_span(&spans[2], &$dialect); + }}; +} + +/// `prepare` against invalid SQL records an error span. +#[macro_export] +macro_rules! test_prepare_records_error { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + let result = (&mut conn).prepare("INVALID SQL GIBBERISH").await; + assert!(result.is_err()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + $crate::common::assert_error_span(&spans[0]); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + + let result = conn + .with_annotations($crate::common::test_annotations()) + .prepare("INVALID SQL GIBBERISH") + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + + let result = conn + .with_operation("SELECT", "users") + .prepare("INVALID SQL GIBBERISH") + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + }}; +} + +/// `prepare_with` against the wrapped pool. +#[macro_export] +macro_rules! test_prepare_with_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let _stmt = (&pool) + .prepare_with($dialect.prepare_with_select_sql, &[]) + .await + .unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + + pool.with_annotations($crate::common::test_annotations()) + .prepare_with($dialect.prepare_with_select_sql, &[]) + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + pool.with_operation("SELECT", "users") + .prepare_with($dialect.prepare_with_select_sql, &[]) + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `prepare_with` against `&mut PoolConnection`. +#[macro_export] +macro_rules! test_prepare_with_via_connection { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + let _stmt = (&mut conn) + .prepare_with($dialect.prepare_with_select_sql, &[]) + .await + .unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + + conn.with_annotations($crate::common::test_annotations()) + .prepare_with($dialect.prepare_with_select_sql, &[]) + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + conn.with_operation("SELECT", "users") + .prepare_with($dialect.prepare_with_select_sql, &[]) + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `prepare_with` against `&mut Transaction<'_, DB>`. +#[macro_export] +macro_rules! test_prepare_with_via_transaction { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut tx = pool.begin().await.unwrap(); + let _stmt = (&mut tx) + .prepare_with($dialect.prepare_with_select_sql, &[]) + .await + .unwrap(); + + tx.with_annotations($crate::common::test_annotations()) + .prepare_with($dialect.prepare_with_select_sql, &[]) + .await + .unwrap(); + + tx.with_operation("SELECT", "users") + .prepare_with($dialect.prepare_with_select_sql, &[]) + .await + .unwrap(); + + tx.commit().await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 3); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + $crate::common::assert_annotated_span(&spans[1], &$dialect); + $crate::common::assert_annotated_span(&spans[2], &$dialect); + }}; +} + +/// `prepare_with` against invalid SQL records an error span. +#[macro_export] +macro_rules! test_prepare_with_records_error { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + let result = (&mut conn).prepare_with("INVALID SQL GIBBERISH", &[]).await; + assert!(result.is_err()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + $crate::common::assert_error_span(&spans[0]); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + + let result = conn + .with_annotations($crate::common::test_annotations()) + .prepare_with("INVALID SQL GIBBERISH", &[]) + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + + let result = conn + .with_operation("SELECT", "users") + .prepare_with("INVALID SQL GIBBERISH", &[]) + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + }}; +} + +/// `describe` against the wrapped pool. +#[macro_export] +macro_rules! test_describe_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let _desc = (&pool).describe("SELECT 1").await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + + pool.with_annotations($crate::common::test_annotations()) + .describe("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + pool.with_operation("SELECT", "users") + .describe("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `describe` against `&mut PoolConnection`. +#[macro_export] +macro_rules! test_describe_via_connection { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + let _desc = (&mut conn).describe("SELECT 1").await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + + conn.with_annotations($crate::common::test_annotations()) + .describe("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + + conn.with_operation("SELECT", "users") + .describe("SELECT 1") + .await + .unwrap(); + $crate::common::assert_annotated_span(tel.spans().last().unwrap(), &$dialect); + }}; +} + +/// `describe` against `&mut Transaction<'_, DB>`. +#[macro_export] +macro_rules! test_describe_via_transaction { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut tx = pool.begin().await.unwrap(); + let _desc = (&mut tx).describe("SELECT 1").await.unwrap(); + + tx.with_annotations($crate::common::test_annotations()) + .describe("SELECT 1") + .await + .unwrap(); + + tx.with_operation("SELECT", "users") + .describe("SELECT 1") + .await + .unwrap(); + + tx.commit().await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 3); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + $crate::common::assert_annotated_span(&spans[1], &$dialect); + $crate::common::assert_annotated_span(&spans[2], &$dialect); + }}; +} + +/// `describe` against invalid SQL records an error span. +#[macro_export] +macro_rules! test_describe_records_error { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + let result = (&mut conn).describe("INVALID SQL GIBBERISH").await; + assert!(result.is_err()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + $crate::common::assert_error_span(&spans[0]); + assert!($crate::common::attr(&spans[0], "db.response.returned_rows").is_none()); + + let result = conn + .with_annotations($crate::common::test_annotations()) + .describe("INVALID SQL GIBBERISH") + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + + let result = conn + .with_operation("SELECT", "users") + .describe("INVALID SQL GIBBERISH") + .await; + assert!(result.is_err()); + let last = tel.spans().last().unwrap().clone(); + $crate::common::assert_annotated_span(&last, &$dialect); + $crate::common::assert_error_span(&last); + }}; +} + +// --------------------------------------------------------------------------- +// Misc: metrics, annotations +// --------------------------------------------------------------------------- + +/// `db.client.operation.duration` histogram is populated for any executed query. +#[macro_export] +macro_rules! test_operation_duration_metric_is_recorded { + ($pool_factory:expr, $dialect:expr) => {{ + use opentelemetry_sdk::metrics::data::{AggregatedMetrics, MetricData}; + let _ = $dialect; // unused – backend doesn't influence the metric shape + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let _: (i32,) = sqlx::query_as("SELECT 1").fetch_one(&pool).await.unwrap(); + + let resource_metrics = tel.metrics(); + assert!(!resource_metrics.is_empty(), "should have metric data"); + + let mut found_duration = false; + for rm in &resource_metrics { + for sm in rm.scope_metrics() { + for metric in sm.metrics() { + if metric.name() == "db.client.operation.duration" { + found_duration = true; + assert_eq!(metric.unit(), "s"); + if let AggregatedMetrics::F64(MetricData::Histogram(hist)) = metric.data() { + let dp: Vec<_> = hist.data_points().collect(); + assert!(!dp.is_empty(), "histogram should have data points"); + assert!(dp[0].count() > 0, "data point count should be > 0"); + let has_system = dp[0] + .attributes() + .any(|kv| kv.key.as_str() == "db.system.name"); + assert!(has_system, "metric should have db.system.name attribute"); + } else { + panic!("db.client.operation.duration should be an f64 histogram"); + } + } + } + } + } + assert!( + found_duration, + "db.client.operation.duration metric not found" + ); + }}; +} + +/// All four annotation fields populated together; summary drives the span name. +#[macro_export] +macro_rules! test_annotation_all_four_fields { + ($pool_factory:expr, $dialect:expr) => {{ + let _ = $dialect; // dialect.system is checked indirectly via assert_common_span_attributes when present + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + pool.with_annotations( + sqlx_otel::QueryAnnotations::new() + .operation("SELECT") + .collection("users") + .query_summary("users by id") + .stored_procedure("sp_get_users"), + ) + .fetch_all("SELECT 1") + .await + .unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!(spans[0].name, "users by id"); + assert_eq!( + $crate::common::attr(&spans[0], "db.operation.name"), + Some(opentelemetry::Value::String("SELECT".into())), + ); + assert_eq!( + $crate::common::attr(&spans[0], "db.collection.name"), + Some(opentelemetry::Value::String("users".into())), + ); + assert_eq!( + $crate::common::attr(&spans[0], "db.query.summary"), + Some(opentelemetry::Value::String("users by id".into())), + ); + assert_eq!( + $crate::common::attr(&spans[0], "db.stored_procedure.name"), + Some(opentelemetry::Value::String("sp_get_users".into())), + ); + }}; +} + +/// `db.query.summary` overrides the span name independently of `db.operation.name` and +/// `db.collection.name`, but does not suppress those attributes. +#[macro_export] +macro_rules! test_query_summary_drives_span_name { + ($pool_factory:expr, $dialect:expr) => {{ + let _ = $dialect; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + pool.with_annotations( + sqlx_otel::QueryAnnotations::new() + .operation("SELECT") + .collection("users") + .query_summary("users by tenant"), + ) + .fetch_all("SELECT 1") + .await + .unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!(spans[0].name, "users by tenant"); + assert_eq!( + $crate::common::attr(&spans[0], "db.query.summary"), + Some(opentelemetry::Value::String("users by tenant".into())), + ); + assert_eq!( + $crate::common::attr(&spans[0], "db.operation.name"), + Some(opentelemetry::Value::String("SELECT".into())), + ); + assert_eq!( + $crate::common::attr(&spans[0], "db.collection.name"), + Some(opentelemetry::Value::String("users".into())), + ); + }}; +} + +// --------------------------------------------------------------------------- +// query-side annotations: sqlx::query(...).with_annotations(...).(executor) +// --------------------------------------------------------------------------- + +/// `sqlx::query(...).with_annotations(...).execute_many(&pool)`. +#[macro_export] +macro_rules! test_query_execute_many_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + #[allow(deprecated)] + let mut stream = sqlx::query("SELECT 1; SELECT 2") + .with_annotations($crate::common::test_annotations()) + .execute_many(&pool) + .await; + while stream.next().await.is_some() {} + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `sqlx::query(...).with_annotations(...).fetch(&pool)`. +#[macro_export] +macro_rules! test_query_fetch_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") + .with_annotations($crate::common::test_annotations()) + .fetch(&pool); + while stream.next().await.is_some() {} + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(2)) + ); + }}; +} + +/// `sqlx::query(...).with_annotations(...).fetch_many(&pool)`. +#[macro_export] +macro_rules! test_query_fetch_many_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + #[allow(deprecated)] + let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") + .with_annotations($crate::common::test_annotations()) + .fetch_many(&pool); + while stream.next().await.is_some() {} + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `sqlx::query(...).with_annotations(...).fetch_all(&pool)`. +#[macro_export] +macro_rules! test_query_fetch_all_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let rows = sqlx::query("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") + .with_annotations($crate::common::test_annotations()) + .fetch_all(&pool) + .await + .unwrap(); + assert_eq!(rows.len(), 3); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(3)) + ); + }}; +} + +/// `sqlx::query(...).with_annotations(...).fetch_one(&pool)`. +#[macro_export] +macro_rules! test_query_fetch_one_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let _row = sqlx::query("SELECT 1") + .with_annotations($crate::common::test_annotations()) + .fetch_one(&pool) + .await + .unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(1)) + ); + }}; +} + +/// `sqlx::query("INVALID SQL").with_annotations(...).execute(&pool)` records an error span. +#[macro_export] +macro_rules! test_query_execute_with_annotations_records_error { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let result = sqlx::query("INVALID SQL GIBBERISH") + .with_annotations($crate::common::test_annotations()) + .execute(&pool) + .await; + assert!(result.is_err()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + $crate::common::assert_error_span(&spans[0]); + }}; +} + +/// `sqlx::query_as(...).with_annotations(...).fetch(&pool)`. +#[macro_export] +macro_rules! test_query_as_fetch_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut stream = sqlx::query_as::<_, (i32,)>("SELECT 1 UNION ALL SELECT 2") + .with_annotations($crate::common::test_annotations()) + .fetch(&pool); + while stream.next().await.is_some() {} + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `sqlx::query_as(...).with_annotations(...).fetch_many(&pool)`. +#[macro_export] +macro_rules! test_query_as_fetch_many_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + #[allow(deprecated)] + let mut stream = sqlx::query_as::<_, (i32,)>("SELECT 1 UNION ALL SELECT 2") + .with_annotations($crate::common::test_annotations()) + .fetch_many(&pool); + while stream.next().await.is_some() {} + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `sqlx::query_as(...).with_annotations(...).fetch_all(&pool)`. +#[macro_export] +macro_rules! test_query_as_fetch_all_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let rows: Vec<(i32,)> = sqlx::query_as("SELECT 1 UNION ALL SELECT 2") + .with_annotations($crate::common::test_annotations()) + .fetch_all(&pool) + .await + .unwrap(); + assert_eq!(rows.len(), 2); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `sqlx::query_as(...).with_annotations(...).fetch_one(&pool)`. +#[macro_export] +macro_rules! test_query_as_fetch_one_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let row: (i32,) = sqlx::query_as("SELECT 7") + .with_annotations($crate::common::test_annotations()) + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(row.0, 7); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `sqlx::query_as(...).with_annotations(...).fetch_optional(&pool)` returning none. +#[macro_export] +macro_rules! test_query_as_fetch_optional_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let row: Option<(i32,)> = sqlx::query_as("SELECT 1 WHERE 1 = 0") + .with_annotations($crate::common::test_annotations()) + .fetch_optional(&pool) + .await + .unwrap(); + assert!(row.is_none()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `sqlx::query_as("INVALID SQL").with_annotations(...).fetch_one(&pool)` records error. +#[macro_export] +macro_rules! test_query_as_fetch_one_with_annotations_records_error { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let result: Result<(i32,), _> = sqlx::query_as("INVALID SQL") + .with_annotations($crate::common::test_annotations()) + .fetch_one(&pool) + .await; + assert!(result.is_err()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + $crate::common::assert_error_span(&spans[0]); + }}; +} + +/// `sqlx::query_scalar(...).with_annotations(...).fetch(&pool)`. +#[macro_export] +macro_rules! test_query_scalar_fetch_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut stream = sqlx::query_scalar::<_, i32>("SELECT 1 UNION ALL SELECT 2") + .with_annotations($crate::common::test_annotations()) + .fetch(&pool); + while stream.next().await.is_some() {} + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `sqlx::query_scalar(...).with_annotations(...).fetch_many(&pool)`. +#[macro_export] +macro_rules! test_query_scalar_fetch_many_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + #[allow(deprecated)] + let mut stream = sqlx::query_scalar::<_, i32>("SELECT 1 UNION ALL SELECT 2") + .with_annotations($crate::common::test_annotations()) + .fetch_many(&pool); + while stream.next().await.is_some() {} + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `sqlx::query_scalar(...).with_annotations(...).fetch_all(&pool)`. +#[macro_export] +macro_rules! test_query_scalar_fetch_all_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let rows: Vec = sqlx::query_scalar("SELECT 1 UNION ALL SELECT 2") + .with_annotations($crate::common::test_annotations()) + .fetch_all(&pool) + .await + .unwrap(); + assert_eq!(rows, vec![1, 2]); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `sqlx::query_scalar(...).with_annotations(...).fetch_one(&pool)`. +#[macro_export] +macro_rules! test_query_scalar_fetch_one_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let value: i32 = sqlx::query_scalar("SELECT 42") + .with_annotations($crate::common::test_annotations()) + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(value, 42); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `sqlx::query_scalar(...).with_annotations(...).fetch_optional(&pool)` returning none. +#[macro_export] +macro_rules! test_query_scalar_fetch_optional_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let value: Option = sqlx::query_scalar("SELECT 1 WHERE 1 = 0") + .with_annotations($crate::common::test_annotations()) + .fetch_optional(&pool) + .await + .unwrap(); + assert!(value.is_none()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +// --------------------------------------------------------------------------- +// query-side annotations: Map (Query::map / Query::try_map) +// --------------------------------------------------------------------------- +// +// The closure parameter type for `.map(|row| ...)` is inferred from the surrounding +// `Query` chain, so we don't need to spell out per-backend `SqliteRow` / `PgRow` / +// `MySqlRow`. + +/// `Query::with_annotations` before `bind`/`map`. Position-1 in the builder pipeline. +#[macro_export] +macro_rules! test_query_map_position_1_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let value: i32 = sqlx::query("SELECT 7") + .with_annotations($crate::common::test_annotations()) + .map(|row: Row| row.get::(0)) + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(value, 7); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `Query::with_annotations` after `bind`, before `map`. +#[macro_export] +macro_rules! test_query_map_position_2_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let value: i32 = sqlx::query("SELECT 11") + .with_annotations($crate::common::test_annotations()) + .map(|row: Row| row.get::(0)) + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(value, 11); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `Query::with_annotations` after `bind` and `map` – last in the pipeline. +#[macro_export] +macro_rules! test_query_map_position_3_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let value: i32 = sqlx::query("SELECT 13") + .map(|row: Row| row.get::(0)) + .with_annotations($crate::common::test_annotations()) + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(value, 13); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// Same as `query_map_position_3` but with `try_map`. +#[macro_export] +macro_rules! test_query_try_map_position_3_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let value: i32 = sqlx::query("SELECT 17") + .try_map(|row: Row| Ok(row.get::(0))) + .with_annotations($crate::common::test_annotations()) + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(value, 17); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `Query::map` then `with_annotations` then `fetch(&pool)`. +#[macro_export] +macro_rules! test_map_fetch_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") + .map(|row: Row| row.get::(0)) + .with_annotations($crate::common::test_annotations()) + .fetch(&pool); + while stream.next().await.is_some() {} + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(2)) + ); + }}; +} + +/// `Query::map` then `with_annotations` then `fetch_many(&pool)`. +#[macro_export] +macro_rules! test_map_fetch_many_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use futures::StreamExt as _; + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + #[allow(deprecated)] + let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") + .map(|row: Row| row.get::(0)) + .with_annotations($crate::common::test_annotations()) + .fetch_many(&pool); + while stream.next().await.is_some() {} + drop(stream); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `Query::map` then `with_annotations` then `fetch_all(&pool)`. +#[macro_export] +macro_rules! test_map_fetch_all_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let rows: Vec = sqlx::query("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") + .map(|row: Row| row.get::(0)) + .with_annotations($crate::common::test_annotations()) + .fetch_all(&pool) + .await + .unwrap(); + assert_eq!(rows, vec![1, 2, 3]); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `Query::map` then `with_annotations` then `fetch_one(&pool)`. +#[macro_export] +macro_rules! test_map_fetch_one_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let value: i32 = sqlx::query("SELECT 19") + .map(|row: Row| row.get::(0)) + .with_annotations($crate::common::test_annotations()) + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(value, 19); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `Query::map` then `with_annotations` then `fetch_optional(&pool)`. +#[macro_export] +macro_rules! test_map_fetch_optional_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let value: Option = sqlx::query("SELECT 1 WHERE 1 = 0") + .map(|row: Row| row.get::(0)) + .with_annotations($crate::common::test_annotations()) + .fetch_optional(&pool) + .await + .unwrap(); + assert!(value.is_none()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// Composing two `map` calls, with annotations between them. +#[macro_export] +macro_rules! test_map_compose_after_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let value: i32 = sqlx::query("SELECT 5") + .map(|row: Row| row.get::(0)) + .with_annotations($crate::common::test_annotations()) + .map(|n| n * 2) + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(value, 10); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// Composing `map` then `try_map`, with annotations between. +#[macro_export] +macro_rules! test_map_try_map_compose_after_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let value: i32 = sqlx::query("SELECT 6") + .map(|row: Row| row.get::(0)) + .with_annotations($crate::common::test_annotations()) + .try_map(|n: i32| Ok::<_, sqlx::Error>(n + 100)) + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(value, 106); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `Query::map` against `&mut PoolConnection`. +#[macro_export] +macro_rules! test_query_map_with_annotations_via_connection { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + let value: i32 = sqlx::query("SELECT 23") + .map(|row: Row| row.get::(0)) + .with_annotations($crate::common::test_annotations()) + .fetch_one(&mut conn) + .await + .unwrap(); + assert_eq!(value, 23); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `Query::map` against `&mut Transaction<'_, DB>`. +#[macro_export] +macro_rules! test_query_map_with_annotations_via_transaction { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut tx = pool.begin().await.unwrap(); + let value: i32 = sqlx::query("SELECT 29") + .map(|row: Row| row.get::(0)) + .with_annotations($crate::common::test_annotations()) + .fetch_one(&mut tx) + .await + .unwrap(); + assert_eq!(value, 29); + tx.commit().await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `Query::map` against invalid SQL records an error span. +#[macro_export] +macro_rules! test_query_map_with_annotations_records_error { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let result: Result = sqlx::query("INVALID SQL") + .map(|row: Row| row.get::(0)) + .with_annotations($crate::common::test_annotations()) + .fetch_one(&pool) + .await; + assert!(result.is_err()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + $crate::common::assert_error_span(&spans[0]); + }}; +} + +/// `try_map` returning a mapper-side error: the database round-trip succeeds, the span +/// reports success, but the user-visible `Result` carries the mapper's error. +#[macro_export] +macro_rules! test_query_try_map_with_annotations_propagates_mapper_error { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let result: Result = sqlx::query("SELECT 1") + .try_map(|_row: Row| { + Err::(sqlx::Error::Decode( + "intentional decode failure".to_string().into(), + )) + }) + .with_annotations($crate::common::test_annotations()) + .fetch_one(&pool) + .await; + assert!(result.is_err()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +// --------------------------------------------------------------------------- +// PoolBuilder with_* methods +// --------------------------------------------------------------------------- +// +// These macros accept a *raw* pool factory (not the wrapped `Pool`) so the test +// body can configure `PoolBuilder` itself with the override under test. + +/// `PoolBuilder::with_database` overrides the inferred `db.namespace`. +#[macro_export] +macro_rules! test_builder_with_database_overrides_namespace { + ($raw_pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let _ = $dialect; + let tel = $crate::common::TestTelemetry::install(); + let raw = $raw_pool_factory; + let pool = sqlx_otel::PoolBuilder::from(raw) + .with_database("custom_db") + .build(); + + let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!( + $crate::common::attr(&spans[0], "db.namespace"), + Some(opentelemetry::Value::String("custom_db".into())) + ); + }}; +} + +/// `PoolBuilder::with_host` overrides the inferred `server.address`. +#[macro_export] +macro_rules! test_builder_with_host_overrides_server_address { + ($raw_pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let _ = $dialect; + let tel = $crate::common::TestTelemetry::install(); + let raw = $raw_pool_factory; + let pool = sqlx_otel::PoolBuilder::from(raw) + .with_host("custom-host") + .build(); + + let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!( + $crate::common::attr(&spans[0], "server.address"), + Some(opentelemetry::Value::String("custom-host".into())) + ); + }}; +} + +/// `PoolBuilder::with_port` overrides the inferred `server.port`. +#[macro_export] +macro_rules! test_builder_with_port_overrides_server_port { + ($raw_pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let _ = $dialect; + let tel = $crate::common::TestTelemetry::install(); + let raw = $raw_pool_factory; + let pool = sqlx_otel::PoolBuilder::from(raw).with_port(9999).build(); + + let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!( + $crate::common::attr(&spans[0], "server.port"), + Some(opentelemetry::Value::I64(9999)) + ); + }}; +} + +/// `PoolBuilder::with_network_peer_address` populates `network.peer.address`. +#[macro_export] +macro_rules! test_builder_with_network_peer_address { + ($raw_pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let _ = $dialect; + let tel = $crate::common::TestTelemetry::install(); + let raw = $raw_pool_factory; + let pool = sqlx_otel::PoolBuilder::from(raw) + .with_network_peer_address("10.0.0.5") + .build(); + + let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!( + $crate::common::attr(&spans[0], "network.peer.address"), + Some(opentelemetry::Value::String("10.0.0.5".into())) + ); + }}; +} + +/// `PoolBuilder::with_network_peer_port` populates `network.peer.port`. +#[macro_export] +macro_rules! test_builder_with_network_peer_port { + ($raw_pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let _ = $dialect; + let tel = $crate::common::TestTelemetry::install(); + let raw = $raw_pool_factory; + let pool = sqlx_otel::PoolBuilder::from(raw) + .with_network_peer_port(5433) + .build(); + + let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!( + $crate::common::attr(&spans[0], "network.peer.port"), + Some(opentelemetry::Value::I64(5433)) + ); + }}; +} + +/// `Pool::close` and `Pool::is_closed` round-trip. +#[macro_export] +macro_rules! test_pool_close_and_is_closed { + ($pool_factory:expr, $dialect:expr) => {{ + let _ = $dialect; + let _tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + assert!(!pool.is_closed()); + pool.close().await; + assert!(pool.is_closed()); + }}; +} + +/// `QueryTextMode::Off` suppresses the `db.query.text` attribute. +#[macro_export] +macro_rules! test_query_text_mode_off_suppresses_sql { + ($raw_pool_factory:expr, $dialect:expr) => {{ + let tel = $crate::common::TestTelemetry::install(); + let raw = $raw_pool_factory; + let pool = sqlx_otel::PoolBuilder::from(raw) + .with_query_text_mode(sqlx_otel::QueryTextMode::Off) + .build(); + + let _: Option<(i32,)> = sqlx::query_as("SELECT 1") + .fetch_optional(&pool) + .await + .unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!(spans[0].span_kind, opentelemetry::trace::SpanKind::Client); + assert_eq!( + $crate::common::attr(&spans[0], "db.system.name"), + Some(opentelemetry::Value::String($dialect.system.into())) + ); + assert!($crate::common::attr(&spans[0], "db.namespace").is_some()); + assert!( + $crate::common::attr(&spans[0], "db.query.text").is_none(), + "db.query.text should not be present when QueryTextMode::Off" + ); + }}; +} + +// --------------------------------------------------------------------------- +// Dialect-portable test bodies +// --------------------------------------------------------------------------- + +/// `execute` records the correct `db.response.affected_rows` for a sequence of +/// INSERT / upsert / UPDATE / DELETE statements. Uses the dialect's `upsert_sql`, +/// `upsert_affected_rows`, and `string_concat_update_sql` to handle backend-specific +/// upsert syntax and string-concat operators. +#[macro_export] +macro_rules! test_execute_records_affected_rows { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + $crate::fresh_table!( + &pool, + "affected_test", + &format!("id {}, name {}", $dialect.id_pk_column, $dialect.text_column) + ); + tel.reset(); + + // --- Bulk insert via VALUES list --- + (&pool) + .execute( + "INSERT INTO affected_test (id, name) VALUES (1, 'alice'), (2, 'bob'), (3, 'carol')", + ) + .await + .unwrap(); + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.affected_rows"), + Some(opentelemetry::Value::I64(3)), + "inserting 3 rows should affect 3 rows" + ); + tel.reset(); + + // --- Upsert (dialect-specific) --- + (&pool).execute($dialect.upsert_sql).await.unwrap(); + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.affected_rows"), + Some(opentelemetry::Value::I64($dialect.upsert_affected_rows)), + "upsert affected_rows differs per backend" + ); + tel.reset(); + + // --- Update multiple rows (dialect-specific concat) --- + (&pool) + .execute($dialect.string_concat_update_sql) + .await + .unwrap(); + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.affected_rows"), + Some(opentelemetry::Value::I64(2)), + "updating two rows should affect 2 rows" + ); + tel.reset(); + + // --- Delete multiple rows --- + (&pool) + .execute("DELETE FROM affected_test WHERE id IN (1, 2, 3)") + .await + .unwrap(); + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.affected_rows"), + Some(opentelemetry::Value::I64(3)), + "deleting three rows should affect 3 rows" + ); + tel.reset(); + + // --- Delete with no matching rows --- + (&pool) + .execute("DELETE FROM affected_test WHERE id = 999") + .await + .unwrap(); + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.affected_rows"), + Some(opentelemetry::Value::I64(0)), + "deleting non-existent rows should affect 0 rows" + ); + }}; +} + +/// Transaction rollback emits a single CREATE TABLE span and discards the table. +/// Uses `fresh_table!` so the test is repeatable against the shared postgres / mysql +/// containers. +#[macro_export] +macro_rules! test_transaction_rollback { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let pool = $pool_factory; + // Pre-clean any leftover table before installing telemetry, so the rollback test + // sees only its own span. + let drop_sql = "DROP TABLE IF EXISTS rollback_test"; + (&pool).execute(drop_sql).await.unwrap(); + + let tel = $crate::common::TestTelemetry::install(); + + let mut tx = pool.begin().await.unwrap(); + let create_sql = format!("CREATE TABLE rollback_test (id {})", $dialect.id_pk_column); + (&mut tx).execute(create_sql.as_str()).await.unwrap(); + tx.rollback().await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + }}; +} + +/// `QueryTextMode::Obfuscated` rewrites string and numeric literals in `db.query.text`. +/// The query under test is dialect-neutral (`SELECT 1, 'alice', 3.14`), so the only +/// dialect input is the raw pool factory used to build a custom-configured pool. +#[macro_export] +macro_rules! test_query_text_mode_obfuscated_replaces_literals { + ($raw_pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let _ = $dialect; + let raw = $raw_pool_factory; + let pool = sqlx_otel::PoolBuilder::from(raw) + .with_query_text_mode(sqlx_otel::QueryTextMode::Obfuscated) + .build(); + + let tel = $crate::common::TestTelemetry::install(); + let _row = (&pool) + .fetch_optional("SELECT 1, 'alice', 3.14") + .await + .unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + assert_eq!( + $crate::common::attr(&spans[0], "db.query.text"), + Some(opentelemetry::Value::String("SELECT ?, ?, ?".into())) + ); + }}; +} + +/// `fetch_optional` against an empty table returns `None` and records `returned_rows = 0`. +/// Uses `fresh_table!` to set up a guaranteed-empty table. +#[macro_export] +macro_rules! test_fetch_optional_records_zero_rows { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Executor as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + $crate::fresh_table!( + &pool, + "empty_table", + &format!("id {}", $dialect.id_pk_column) + ); + tel.reset(); + + let result = (&pool) + .fetch_optional("SELECT id FROM empty_table") + .await + .unwrap(); + assert!(result.is_none()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_common_span_attributes(&spans[0], $dialect.system); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(0)) + ); + }}; +} + +/// `sqlx::query(...).bind(...).bind(...).with_annotations(...).fetch_one(&pool)` with a +/// dialect-specific SELECT that adds two bound `i32` arguments and returns an `i64`. +#[macro_export] +macro_rules! test_query_bind_first_then_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let row = sqlx::query($dialect.bind_two_sum_sql) + .bind(2_i32) + .bind(3_i32) + .with_annotations($crate::common::test_annotations()) + .fetch_one(&pool) + .await + .unwrap(); + let sum: i64 = row.try_get("sum").unwrap(); + assert_eq!(sum, 5); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// Same as `test_query_bind_first_then_annotations_via_pool` but with `with_annotations` +/// applied before the binds. +#[macro_export] +macro_rules! test_query_annotations_first_then_bind_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx::Row as _; + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let row = sqlx::query($dialect.bind_two_sum_sql) + .with_annotations($crate::common::test_annotations()) + .bind(10_i32) + .bind(20_i32) + .fetch_one(&pool) + .await + .unwrap(); + let sum: i64 = row.try_get("sum").unwrap(); + assert_eq!(sum, 30); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// Annotated `execute` against the wrapped pool. Uses `SELECT 1` so the test is +/// portable; the executor records `affected_rows` regardless of statement kind. +#[macro_export] +macro_rules! test_query_execute_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + sqlx::query("SELECT 1") + .with_annotations($crate::common::test_annotations()) + .execute(&pool) + .await + .unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + assert!($crate::common::attr(&spans[0], "db.response.affected_rows").is_some()); + }}; +} + +/// Annotated `execute` against `&mut PoolConnection`. +#[macro_export] +macro_rules! test_query_execute_with_annotations_via_connection { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut conn = pool.acquire().await.unwrap(); + sqlx::query("SELECT 1") + .with_annotations($crate::common::test_annotations()) + .execute(&mut conn) + .await + .unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// Annotated `execute` against `&mut Transaction<'_, DB>`. +#[macro_export] +macro_rules! test_query_execute_with_annotations_via_transaction { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let mut tx = pool.begin().await.unwrap(); + sqlx::query("SELECT 1") + .with_annotations($crate::common::test_annotations()) + .execute(&mut tx) + .await + .unwrap(); + tx.commit().await.unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// `with_operation` shorthand attaching the same annotations as the manual +/// `with_annotations(test_annotations())` call. +#[macro_export] +macro_rules! test_query_with_operation_shorthand_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + sqlx::query("SELECT 1") + .with_operation("SELECT", "users") + .execute(&pool) + .await + .unwrap(); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + }}; +} + +/// Annotated `fetch_optional` returning `None`. Uses `SELECT 1 WHERE 1 = 0` to express +/// the empty-row case without dialect-specific table setup. +#[macro_export] +macro_rules! test_query_fetch_optional_with_annotations_via_pool { + ($pool_factory:expr, $dialect:expr) => {{ + use sqlx_otel::QueryAnnotateExt as _; + let tel = $crate::common::TestTelemetry::install(); + let pool = $pool_factory; + + let row = sqlx::query("SELECT 1 WHERE 1 = 0") + .with_annotations($crate::common::test_annotations()) + .fetch_optional(&pool) + .await + .unwrap(); + assert!(row.is_none()); + + let spans = tel.spans(); + assert_eq!(spans.len(), 1); + $crate::common::assert_annotated_span(&spans[0], &$dialect); + assert_eq!( + $crate::common::attr(&spans[0], "db.response.returned_rows"), + Some(opentelemetry::Value::I64(0)) + ); + }}; +} diff --git a/tests/mysql.rs b/tests/mysql.rs index 13dcb1d..e4cb0ac 100644 --- a/tests/mysql.rs +++ b/tests/mysql.rs @@ -5,20 +5,18 @@ mod common; use std::sync::OnceLock; use std::time::Duration; -use common::{assert_common_span_attributes, assert_error_span, attr}; -use futures::StreamExt; -use opentelemetry::trace::SpanKind; +use common::{assert_error_span, attr, test_annotations}; use serial_test::serial; use sqlx::Executor as _; use sqlx::MySql; -use sqlx::Row as _; -use sqlx_otel::{Pool, PoolBuilder, QueryAnnotateExt, QueryAnnotations, Transaction}; +use sqlx_otel::{Pool, PoolBuilder, QueryAnnotateExt}; use testcontainers::core::IntoContainerPort; use testcontainers::runners::AsyncRunner; use testcontainers::{ContainerAsync, GenericImage, ImageExt}; use tokio::sync::OnceCell; -const SYSTEM: &str = "mysql"; +/// Backend row type used by parameterised map test macros (see `tests/sqlite.rs`). +type Row = sqlx::mysql::MySqlRow; /// Shared container and connection URL, initialised once across all tests. struct SharedContainer { @@ -28,6 +26,12 @@ struct SharedContainer { static CONTAINER: OnceLock> = OnceLock::new(); +/// Container ID captured at start-time for use by the [`drop_container`] destructor. +/// Held in a sibling static (rather than reading from `SharedContainer` at exit) +/// because the `ContainerAsync` value is itself locked behind a `'static` future and +/// the destructor must run synchronously without async access. +static CONTAINER_ID: OnceLock = OnceLock::new(); + async fn shared_container() -> &'static SharedContainer { CONTAINER .get_or_init(OnceCell::new) @@ -45,6 +49,8 @@ async fn shared_container() -> &'static SharedContainer { .await .expect("starting mysql container"); + let _ = CONTAINER_ID.set(container.id().to_string()); + let port = container.get_host_port_ipv4(3306).await.unwrap(); let url = format!("mysql://root:test@localhost:{port}/testdb"); SharedContainer { @@ -55,36 +61,32 @@ async fn shared_container() -> &'static SharedContainer { .await } -/// Return an instrumented pool connected to the shared container. -async fn test_pool() -> Pool { - let shared = shared_container().await; - let raw = sqlx::MySqlPool::connect(&shared.url).await.unwrap(); - PoolBuilder::from(raw).build() +/// Stop and remove the shared container at process exit. Required because the +/// `ContainerAsync` value lives in a `'static` (`CONTAINER`), so the language never +/// runs its `Drop`. Using `ctor::dtor` schedules a synchronous shell-out to +/// `docker rm -f` that fires after `main` returns – equivalent to the per-test +/// RAII cleanup that existed before the shared-container refactor (commit c29f995). +#[ctor::dtor] +fn drop_container() { + if let Some(id) = CONTAINER_ID.get() { + let _ = std::process::Command::new("docker") + .args(["rm", "-f", id.as_str()]) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status(); + } } -/// Standard annotations used across annotation assertions. -fn test_annotations() -> QueryAnnotations { - QueryAnnotations::new() - .operation("SELECT") - .collection("users") +/// Return an instrumented pool connected to the shared container. +async fn test_pool() -> Pool { + PoolBuilder::from(raw_pool().await).build() } -/// Assert that the span carries the standard annotation attributes. -fn assert_annotated_span(span: &opentelemetry_sdk::trace::SpanData) { - assert_eq!(span.span_kind, SpanKind::Client); - assert_eq!(span.name, "SELECT users"); - assert_eq!( - attr(span, "db.system.name"), - Some(opentelemetry::Value::String(SYSTEM.to_owned().into())), - ); - assert_eq!( - attr(span, "db.operation.name"), - Some(opentelemetry::Value::String("SELECT".into())), - ); - assert_eq!( - attr(span, "db.collection.name"), - Some(opentelemetry::Value::String("users".into())), - ); +/// Raw (un-instrumented) sqlx pool, used by parameterised builder / query-text-mode +/// tests that need to apply specific `PoolBuilder` configurations themselves. +async fn raw_pool() -> sqlx::MySqlPool { + let shared = shared_container().await; + sqlx::MySqlPool::connect(&shared.url).await.unwrap() } // =========================================================================== @@ -94,237 +96,31 @@ fn assert_annotated_span(span: &opentelemetry_sdk::trace::SpanData) { #[tokio::test] #[serial] async fn execute_creates_span_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE IF NOT EXISTS exec_pool (id INT AUTO_INCREMENT PRIMARY KEY)") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert!(attr(&spans[0], "db.response.affected_rows").is_some()); - - // With annotations - pool.with_annotations(test_annotations()) - .execute("CREATE TABLE IF NOT EXISTS exec_pool (id INT AUTO_INCREMENT PRIMARY KEY)") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .execute("CREATE TABLE IF NOT EXISTS exec_pool3 (id INT AUTO_INCREMENT PRIMARY KEY)") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_creates_span_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn execute_creates_span_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - sqlx::query("CREATE TABLE IF NOT EXISTS exec_conn (id INT AUTO_INCREMENT PRIMARY KEY)") - .execute(&mut conn) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert!(attr(&spans[0], "db.response.affected_rows").is_some()); - - // With annotations - conn.with_annotations(test_annotations()) - .execute("CREATE TABLE IF NOT EXISTS exec_conn (id INT AUTO_INCREMENT PRIMARY KEY)") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .execute("CREATE TABLE IF NOT EXISTS exec_conn3 (id INT AUTO_INCREMENT PRIMARY KEY)") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_creates_span_via_connection!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn execute_creates_span_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, MySql> = pool.begin().await.unwrap(); - sqlx::query("CREATE TABLE IF NOT EXISTS exec_tx (id INT AUTO_INCREMENT PRIMARY KEY)") - .execute(&mut tx) - .await - .unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .execute("CREATE TABLE IF NOT EXISTS exec_tx (id INT AUTO_INCREMENT PRIMARY KEY)") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .execute("CREATE TABLE IF NOT EXISTS exec_tx3 (id INT AUTO_INCREMENT PRIMARY KEY)") - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert!(attr(&spans[0], "db.response.affected_rows").is_some()); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_creates_span_via_transaction!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn execute_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = sqlx::query("INVALID SQL GIBBERISH").execute(&pool).await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations (error path) - let result = pool - .with_annotations(test_annotations()) - .execute("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = pool - .with_operation("SELECT", "users") - .execute("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_execute_records_error!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn execute_records_affected_rows() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query( - "CREATE TABLE IF NOT EXISTS affected_test (id INT PRIMARY KEY, name VARCHAR(255) NOT NULL)", - ) - .execute(&pool) - .await - .unwrap(); - sqlx::query("DELETE FROM affected_test") - .execute(&pool) - .await - .unwrap(); - - tel.reset(); - - // --- Bulk insert --- - sqlx::query( - "INSERT INTO affected_test (id, name) VALUES (1, 'alice'), (2, 'bob'), (3, 'carol')", - ) - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(3)), - "inserting 3 rows in one statement should affect 3 rows" - ); - tel.reset(); - - // --- Upsert (INSERT ON DUPLICATE KEY UPDATE) --- - sqlx::query( - "INSERT INTO affected_test (id, name) VALUES (1, 'alice_updated') \ - ON DUPLICATE KEY UPDATE name = VALUES(name)", - ) - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - // MySQL reports 2 for ON DUPLICATE KEY UPDATE – it counts the matched row plus the - // updated row. - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(2)), - "MySQL upsert reports 2 affected rows (match + update)" - ); - tel.reset(); - - // --- Update multiple rows --- - sqlx::query("UPDATE affected_test SET name = CONCAT(name, '_updated') WHERE id IN (2, 3)") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(2)), - "updating two rows should affect 2 rows" - ); - tel.reset(); - - // --- Delete multiple rows --- - sqlx::query("DELETE FROM affected_test WHERE id IN (1, 2, 3)") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(3)), - "deleting three rows should affect 3 rows" - ); - tel.reset(); - - // --- Delete with no matching rows --- - sqlx::query("DELETE FROM affected_test WHERE id = 999") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(0)), - "deleting non-existent rows should affect 0 rows" - ); + test_execute_records_affected_rows!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -334,153 +130,25 @@ async fn execute_records_affected_rows() { #[tokio::test] #[serial] async fn execute_many_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - - // With annotations - let mut stream = pool - .with_annotations(test_annotations()) - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = pool - .with_operation("SELECT", "users") - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_many_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn execute_many_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let mut stream = (&mut conn).execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - - // With annotations - let mut stream = conn - .with_annotations(test_annotations()) - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = conn - .with_operation("SELECT", "users") - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_many_via_connection!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn execute_many_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, MySql> = pool.begin().await.unwrap(); - - let mut stream = (&mut tx).execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With annotations - let mut stream = tx - .with_annotations(test_annotations()) - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With shorthand - let mut stream = tx - .with_operation("SELECT", "users") - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_many_via_transaction!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn execute_many_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).execute_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - - // With annotations (error path) - let mut stream = pool - .with_annotations(test_annotations()) - .execute_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let mut stream = pool - .with_operation("SELECT", "users") - .execute_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_execute_many_records_error!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -490,175 +158,31 @@ async fn execute_many_records_error() { #[tokio::test] #[serial] async fn fetch_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).fetch("SELECT 1 UNION ALL SELECT 2"); - let mut count = 0u64; - while stream.next().await.is_some() { - count += 1; - } - assert_eq!(count, 2); - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - let mut stream = pool - .with_annotations(test_annotations()) - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = pool - .with_operation("SELECT", "users") - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let mut stream = (&mut conn).fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - let mut stream = conn - .with_annotations(test_annotations()) - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = conn - .with_operation("SELECT", "users") - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_via_connection!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, MySql> = pool.begin().await.unwrap(); - - let mut stream = (&mut tx).fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With annotations - let mut stream = tx - .with_annotations(test_annotations()) - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With shorthand - let mut stream = tx - .with_operation("SELECT", "users") - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_via_transaction!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_stream_dropped_early_still_records_span() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - { - let mut stream = (&pool).fetch("SELECT 1 UNION ALL SELECT 2"); - let _ = stream.next().await; - } - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); + test_fetch_stream_dropped_early_still_records_span!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_stream_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).fetch("INVALID SQL"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - assert_error_span(&spans[0]); - - // With annotations (error path) - let mut stream = pool - .with_annotations(test_annotations()) - .fetch("INVALID SQL"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let mut stream = pool.with_operation("SELECT", "users").fetch("INVALID SQL"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_stream_records_error!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -668,179 +192,31 @@ async fn fetch_stream_records_error() { #[tokio::test] #[serial] async fn fetch_many_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).fetch_many("SELECT 1 UNION ALL SELECT 2"); - let mut rows = 0u64; - while let Some(item) = stream.next().await { - if let Ok(sqlx::Either::Right(_)) = item { - rows += 1; - } - } - drop(stream); - assert_eq!(rows, 2); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - let mut stream = pool - .with_annotations(test_annotations()) - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = pool - .with_operation("SELECT", "users") - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_many_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_many_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let mut stream = (&mut conn).fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - let mut stream = conn - .with_annotations(test_annotations()) - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = conn - .with_operation("SELECT", "users") - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_many_via_connection!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_many_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, MySql> = pool.begin().await.unwrap(); - - let mut stream = (&mut tx).fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With annotations - let mut stream = tx - .with_annotations(test_annotations()) - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With shorthand - let mut stream = tx - .with_operation("SELECT", "users") - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_many_via_transaction!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_many_dropped_early_still_records_span() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - { - let mut stream = (&pool).fetch_many("SELECT 1 UNION ALL SELECT 2"); - let _ = stream.next().await; - } - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); + test_fetch_many_dropped_early_still_records_span!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_many_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).fetch_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - assert_error_span(&spans[0]); - - // With annotations (error path) - let mut stream = pool - .with_annotations(test_annotations()) - .fetch_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let mut stream = pool - .with_operation("SELECT", "users") - .fetch_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_many_records_error!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -850,145 +226,25 @@ async fn fetch_many_records_error() { #[tokio::test] #[serial] async fn fetch_all_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows = (&pool) - .fetch_all("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .await - .unwrap(); - assert_eq!(rows.len(), 3); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(3)) - ); - - // With annotations - pool.with_annotations(test_annotations()) - .fetch_all("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .fetch_all("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_all_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_all_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let rows = (&mut conn) - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - assert_eq!(rows.len(), 2); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - conn.with_annotations(test_annotations()) - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_all_via_connection!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_all_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, MySql> = pool.begin().await.unwrap(); - let rows = (&mut tx) - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - assert_eq!(rows.len(), 2); - - // With annotations - tx.with_annotations(test_annotations()) - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_all_via_transaction!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_all_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = (&pool).fetch_all("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_error_span(&spans[0]); - - // With annotations (error path) - let result = pool - .with_annotations(test_annotations()) - .fetch_all("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = pool - .with_operation("SELECT", "users") - .fetch_all("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_all_records_error!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -998,133 +254,25 @@ async fn fetch_all_records_error() { #[tokio::test] #[serial] async fn fetch_one_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _row = (&pool).fetch_one("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - - // With annotations - pool.with_annotations(test_annotations()) - .fetch_one("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .fetch_one("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_one_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_one_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let _row = (&mut conn).fetch_one("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - - // With annotations - conn.with_annotations(test_annotations()) - .fetch_one("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .fetch_one("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_one_via_connection!(test_pool().await, common::MYSQL_DIALECT); } -#[tokio::test] -#[serial] -async fn fetch_one_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, MySql> = pool.begin().await.unwrap(); - let _row = (&mut tx).fetch_one("SELECT 1").await.unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .fetch_one("SELECT 1") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .fetch_one("SELECT 1") - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - assert_annotated_span(tel.spans().last().unwrap()); +#[tokio::test] +#[serial] +async fn fetch_one_via_transaction() { + test_fetch_one_via_transaction!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_one_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = (&pool).fetch_one("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_error_span(&spans[0]); - - // With annotations (error path) - let result = pool - .with_annotations(test_annotations()) - .fetch_one("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = pool - .with_operation("SELECT", "users") - .fetch_one("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_one_records_error!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -1134,167 +282,31 @@ async fn fetch_one_records_error() { #[tokio::test] #[serial] async fn fetch_optional_records_one_row() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = (&pool).fetch_optional("SELECT 1").await.unwrap(); - assert!(result.is_some()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - - // With annotations - pool.with_annotations(test_annotations()) - .fetch_optional("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .fetch_optional("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_optional_records_one_row!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_optional_records_zero_rows() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE IF NOT EXISTS empty_table (id INT AUTO_INCREMENT PRIMARY KEY)") - .execute(&pool) - .await - .unwrap(); - sqlx::query("DELETE FROM empty_table") - .execute(&pool) - .await - .unwrap(); - - tel.reset(); - let result = (&pool) - .fetch_optional("SELECT id FROM empty_table") - .await - .unwrap(); - assert!(result.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); + test_fetch_optional_records_zero_rows!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_optional_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let result = (&mut conn).fetch_optional("SELECT 42").await.unwrap(); - assert!(result.is_some()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - - // With annotations - conn.with_annotations(test_annotations()) - .fetch_optional("SELECT 42") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .fetch_optional("SELECT 42") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_optional_via_connection!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_optional_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, MySql> = pool.begin().await.unwrap(); - let result = (&mut tx).fetch_optional("SELECT 99").await.unwrap(); - assert!(result.is_some()); - - // With annotations - tx.with_annotations(test_annotations()) - .fetch_optional("SELECT 99") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .fetch_optional("SELECT 99") - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_optional_via_transaction!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn fetch_optional_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = (&pool).fetch_optional("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_error_span(&spans[0]); - - // With annotations (error path) - let result = pool - .with_annotations(test_annotations()) - .fetch_optional("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = pool - .with_operation("SELECT", "users") - .fetch_optional("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_optional_records_error!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -1304,125 +316,25 @@ async fn fetch_optional_records_error() { #[tokio::test] #[serial] async fn prepare_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _stmt = (&pool).prepare("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - pool.with_annotations(test_annotations()) - .prepare("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .prepare("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn prepare_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let _stmt = (&mut conn).prepare("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - conn.with_annotations(test_annotations()) - .prepare("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .prepare("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_via_connection!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn prepare_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, MySql> = pool.begin().await.unwrap(); - let _stmt = (&mut tx).prepare("SELECT 1").await.unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .prepare("SELECT 1") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .prepare("SELECT 1") - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_via_transaction!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn prepare_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let result = (&mut conn).prepare("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_error_span(&spans[0]); - - // With annotations (error path) - let result = conn - .with_annotations(test_annotations()) - .prepare("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = conn - .with_operation("SELECT", "users") - .prepare("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_prepare_records_error!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -1432,125 +344,25 @@ async fn prepare_records_error() { #[tokio::test] #[serial] async fn prepare_with_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _stmt = (&pool).prepare_with("SELECT ?", &[]).await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - pool.with_annotations(test_annotations()) - .prepare_with("SELECT ?", &[]) - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .prepare_with("SELECT ?", &[]) - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_with_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn prepare_with_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let _stmt = (&mut conn).prepare_with("SELECT ?", &[]).await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - conn.with_annotations(test_annotations()) - .prepare_with("SELECT ?", &[]) - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .prepare_with("SELECT ?", &[]) - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_with_via_connection!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn prepare_with_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, MySql> = pool.begin().await.unwrap(); - let _stmt = (&mut tx).prepare_with("SELECT ?", &[]).await.unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .prepare_with("SELECT ?", &[]) - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .prepare_with("SELECT ?", &[]) - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_with_via_transaction!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn prepare_with_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let result = (&mut conn).prepare_with("INVALID SQL GIBBERISH", &[]).await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_error_span(&spans[0]); - - // With annotations (error path) - let result = conn - .with_annotations(test_annotations()) - .prepare_with("INVALID SQL GIBBERISH", &[]) - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = conn - .with_operation("SELECT", "users") - .prepare_with("INVALID SQL GIBBERISH", &[]) - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_prepare_with_records_error!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -1560,125 +372,25 @@ async fn prepare_with_records_error() { #[tokio::test] #[serial] async fn describe_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _desc = (&pool).describe("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - pool.with_annotations(test_annotations()) - .describe("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .describe("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_describe_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn describe_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let _desc = (&mut conn).describe("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - conn.with_annotations(test_annotations()) - .describe("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .describe("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_describe_via_connection!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn describe_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, MySql> = pool.begin().await.unwrap(); - let _desc = (&mut tx).describe("SELECT 1").await.unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .describe("SELECT 1") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .describe("SELECT 1") - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_annotated_span(tel.spans().last().unwrap()); + test_describe_via_transaction!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn describe_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let result = (&mut conn).describe("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_error_span(&spans[0]); - - // With annotations (error path) - let result = conn - .with_annotations(test_annotations()) - .describe("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = conn - .with_operation("SELECT", "users") - .describe("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_describe_records_error!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -1758,43 +470,8 @@ async fn sqlstate_recorded_on_constraint_violation() { #[tokio::test] #[serial] -async fn operation_duration_metric_is_recorded() { - use opentelemetry_sdk::metrics::data::{AggregatedMetrics, MetricData}; - - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _row = (&pool).fetch_one("SELECT 1").await.unwrap(); - - let resource_metrics = tel.metrics(); - assert!(!resource_metrics.is_empty(), "should have metric data"); - - let mut found_duration = false; - for rm in &resource_metrics { - for sm in rm.scope_metrics() { - for metric in sm.metrics() { - if metric.name() == "db.client.operation.duration" { - found_duration = true; - assert_eq!(metric.unit(), "s"); - if let AggregatedMetrics::F64(MetricData::Histogram(hist)) = metric.data() { - let dp: Vec<_> = hist.data_points().collect(); - assert!(!dp.is_empty(), "histogram should have data points"); - assert!(dp[0].count() > 0, "data point count should be > 0"); - let has_system = dp[0] - .attributes() - .any(|kv| kv.key.as_str() == "db.system.name"); - assert!(has_system, "metric should have db.system.name attribute"); - } else { - panic!("db.client.operation.duration should be an f64 histogram"); - } - } - } - } - } - assert!( - found_duration, - "db.client.operation.duration metric not found" - ); +async fn operation_duration_metric_is_recorded() { + test_operation_duration_metric_is_recorded!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -1804,19 +481,7 @@ async fn operation_duration_metric_is_recorded() { #[tokio::test] #[serial] async fn transaction_rollback() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, MySql> = pool.begin().await.unwrap(); - sqlx::query("CREATE TABLE IF NOT EXISTS rollback_test (id INT AUTO_INCREMENT PRIMARY KEY)") - .execute(&mut tx) - .await - .unwrap(); - tx.rollback().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); + test_transaction_rollback!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -1826,93 +491,31 @@ async fn transaction_rollback() { #[tokio::test] #[serial] async fn builder_with_database_overrides_namespace() { - let tel = common::TestTelemetry::install(); - let shared = shared_container().await; - let raw = sqlx::MySqlPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw).with_database("custom_db").build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.namespace"), - Some(opentelemetry::Value::String("custom_db".into())) - ); + test_builder_with_database_overrides_namespace!(raw_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn builder_with_host_overrides_server_address() { - let tel = common::TestTelemetry::install(); - let shared = shared_container().await; - let raw = sqlx::MySqlPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw).with_host("custom-host").build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "server.address"), - Some(opentelemetry::Value::String("custom-host".into())) - ); + test_builder_with_host_overrides_server_address!(raw_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn builder_with_port_overrides_server_port() { - let tel = common::TestTelemetry::install(); - let shared = shared_container().await; - let raw = sqlx::MySqlPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw).with_port(9999).build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "server.port"), - Some(opentelemetry::Value::I64(9999)) - ); + test_builder_with_port_overrides_server_port!(raw_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn builder_with_network_peer_address() { - let tel = common::TestTelemetry::install(); - let shared = shared_container().await; - let raw = sqlx::MySqlPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw) - .with_network_peer_address("10.0.0.5") - .build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "network.peer.address"), - Some(opentelemetry::Value::String("10.0.0.5".into())) - ); + test_builder_with_network_peer_address!(raw_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn builder_with_network_peer_port() { - let tel = common::TestTelemetry::install(); - let shared = shared_container().await; - let raw = sqlx::MySqlPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw).with_network_peer_port(3307).build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "network.peer.port"), - Some(opentelemetry::Value::I64(3307)) - ); + test_builder_with_network_peer_port!(raw_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -1922,12 +525,7 @@ async fn builder_with_network_peer_port() { #[tokio::test] #[serial] async fn pool_close_and_is_closed() { - let _tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - assert!(!pool.is_closed()); - pool.close().await; - assert!(pool.is_closed()); + test_pool_close_and_is_closed!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -1937,50 +535,13 @@ async fn pool_close_and_is_closed() { #[tokio::test] #[serial] async fn query_text_mode_off_suppresses_sql() { - let shared = shared_container().await; - let raw = sqlx::MySqlPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw) - .with_query_text_mode(sqlx_otel::QueryTextMode::Off) - .build(); - - let tel = common::TestTelemetry::install(); - let _row = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!(spans[0].span_kind, SpanKind::Client); - assert_eq!( - attr(&spans[0], "db.system.name"), - Some(opentelemetry::Value::String(SYSTEM.to_owned().into())) - ); - assert!(attr(&spans[0], "db.namespace").is_some()); - assert!( - attr(&spans[0], "db.query.text").is_none(), - "db.query.text should not be present when QueryTextMode::Off" - ); + test_query_text_mode_off_suppresses_sql!(raw_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_text_mode_obfuscated_replaces_literals() { - let shared = shared_container().await; - let raw = sqlx::MySqlPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw) - .with_query_text_mode(sqlx_otel::QueryTextMode::Obfuscated) - .build(); - - let tel = common::TestTelemetry::install(); - let _row = (&pool) - .fetch_optional("SELECT 1, 'alice', 3.14") - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.query.text"), - Some(opentelemetry::Value::String("SELECT ?, ?, ?".into())) - ); + test_query_text_mode_obfuscated_replaces_literals!(raw_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -1990,75 +551,13 @@ async fn query_text_mode_obfuscated_replaces_literals() { #[tokio::test] #[serial] async fn annotation_all_four_fields() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - pool.with_annotations( - QueryAnnotations::new() - .operation("SELECT") - .collection("users") - .query_summary("users by id") - .stored_procedure("sp_get_users"), - ) - .fetch_all("SELECT 1") - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - // Summary drives the span name (semconv level 1), distinct from "SELECT users" so - // the assertion proves the summary path won rather than coinciding with level 2. - assert_eq!(spans[0].name, "users by id"); - assert_eq!( - attr(&spans[0], "db.operation.name"), - Some(opentelemetry::Value::String("SELECT".into())), - ); - assert_eq!( - attr(&spans[0], "db.collection.name"), - Some(opentelemetry::Value::String("users".into())), - ); - assert_eq!( - attr(&spans[0], "db.query.summary"), - Some(opentelemetry::Value::String("users by id".into())), - ); - assert_eq!( - attr(&spans[0], "db.stored_procedure.name"), - Some(opentelemetry::Value::String("sp_get_users".into())), - ); + test_annotation_all_four_fields!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_summary_drives_span_name() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - pool.with_annotations( - QueryAnnotations::new() - .operation("SELECT") - .collection("users") - .query_summary("users by tenant"), - ) - .fetch_all("SELECT 1") - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!(spans[0].name, "users by tenant"); - // Summary drives the *name*, but does not suppress the other attributes. - assert_eq!( - attr(&spans[0], "db.query.summary"), - Some(opentelemetry::Value::String("users by tenant".into())), - ); - assert_eq!( - attr(&spans[0], "db.operation.name"), - Some(opentelemetry::Value::String("SELECT".into())), - ); - assert_eq!( - attr(&spans[0], "db.collection.name"), - Some(opentelemetry::Value::String("users".into())), - ); + test_query_summary_drives_span_name!(test_pool().await, common::MYSQL_DIALECT); } // =========================================================================== @@ -2068,263 +567,79 @@ async fn query_summary_drives_span_name() { #[tokio::test] #[serial] async fn query_execute_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE IF NOT EXISTS qe_pool (id INT AUTO_INCREMENT PRIMARY KEY)") - .with_annotations(test_annotations()) - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert!(attr(&spans[0], "db.response.affected_rows").is_some()); + test_query_execute_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_execute_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query("SELECT 1; SELECT 2") - .with_annotations(test_annotations()) - .execute_many(&pool) - .await; - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_execute_many_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_fetch_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); + test_query_fetch_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_fetch_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_many(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_fetch_many_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_fetch_all_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows = sqlx::query("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .with_annotations(test_annotations()) - .fetch_all(&pool) - .await - .unwrap(); - assert_eq!(rows.len(), 3); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(3)) - ); + test_query_fetch_all_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_fetch_one_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _row = sqlx::query("SELECT 1") - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); + test_query_fetch_one_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_fetch_optional_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE IF NOT EXISTS qfo_pool (id INT PRIMARY KEY)") - .execute(&pool) - .await - .unwrap(); - - tel.reset(); - - let row = sqlx::query("SELECT id FROM qfo_pool WHERE id = 1") - .with_annotations(test_annotations()) - .fetch_optional(&pool) - .await - .unwrap(); - assert!(row.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); + test_query_fetch_optional_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_bind_first_then_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let row = sqlx::query("SELECT CAST(? + ? AS SIGNED) AS sum") - .bind(2_i32) - .bind(3_i32) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - let sum: i64 = row.try_get("sum").unwrap(); - assert_eq!(sum, 5); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_bind_first_then_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_annotations_first_then_bind_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let row = sqlx::query("SELECT CAST(? + ? AS SIGNED) AS sum") - .with_annotations(test_annotations()) - .bind(10_i32) - .bind(20_i32) - .fetch_one(&pool) - .await - .unwrap(); - let sum: i64 = row.try_get("sum").unwrap(); - assert_eq!(sum, 30); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_annotations_first_then_bind_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_with_operation_shorthand_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE IF NOT EXISTS qop_pool (id INT AUTO_INCREMENT PRIMARY KEY)") - .with_operation("SELECT", "users") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_with_operation_shorthand_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_execute_with_annotations_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - sqlx::query("CREATE TABLE IF NOT EXISTS qe_conn (id INT AUTO_INCREMENT PRIMARY KEY)") - .with_annotations(test_annotations()) - .execute(&mut conn) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_execute_with_annotations_via_connection!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_execute_with_annotations_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, MySql> = pool.begin().await.unwrap(); - sqlx::query("CREATE TABLE IF NOT EXISTS qe_tx (id INT AUTO_INCREMENT PRIMARY KEY)") - .with_annotations(test_annotations()) - .execute(&mut tx) - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_execute_with_annotations_via_transaction!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_execute_with_annotations_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = sqlx::query("INVALID SQL GIBBERISH") - .with_annotations(test_annotations()) - .execute(&pool) - .await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_error_span(&spans[0]); + test_query_execute_with_annotations_records_error!(test_pool().await, common::MYSQL_DIALECT); } // --- query_as side --------------------------------------------------------- @@ -2332,108 +647,43 @@ async fn query_execute_with_annotations_records_error() { #[tokio::test] #[serial] async fn query_as_fetch_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = sqlx::query_as::<_, (i32,)>("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_as_fetch_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] -#[serial] -async fn query_as_fetch_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query_as::<_, (i32,)>("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_many(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); +#[serial] +async fn query_as_fetch_many_with_annotations_via_pool() { + test_query_as_fetch_many_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_as_fetch_all_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows: Vec<(i32,)> = sqlx::query_as("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_all(&pool) - .await - .unwrap(); - assert_eq!(rows.len(), 2); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_as_fetch_all_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_as_fetch_one_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let row: (i32,) = sqlx::query_as("SELECT 7") - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(row.0, 7); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_as_fetch_one_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_as_fetch_optional_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let row: Option<(i32,)> = sqlx::query_as("SELECT 1 FROM (SELECT 1) t WHERE 1 = 0") - .with_annotations(test_annotations()) - .fetch_optional(&pool) - .await - .unwrap(); - assert!(row.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_as_fetch_optional_with_annotations_via_pool!( + test_pool().await, + common::MYSQL_DIALECT + ); } #[tokio::test] #[serial] async fn query_as_fetch_one_with_annotations_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result: Result<(i32,), _> = sqlx::query_as("INVALID SQL") - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_error_span(&spans[0]); + test_query_as_fetch_one_with_annotations_records_error!( + test_pool().await, + common::MYSQL_DIALECT + ); } // --- query_scalar side ----------------------------------------------------- @@ -2441,90 +691,43 @@ async fn query_as_fetch_one_with_annotations_records_error() { #[tokio::test] #[serial] async fn query_scalar_fetch_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = sqlx::query_scalar::<_, i32>("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_scalar_fetch_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query_scalar::<_, i32>("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_many(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_many_with_annotations_via_pool!( + test_pool().await, + common::MYSQL_DIALECT + ); } #[tokio::test] #[serial] async fn query_scalar_fetch_all_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows: Vec = sqlx::query_scalar("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_all(&pool) - .await - .unwrap(); - assert_eq!(rows, vec![1, 2]); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_all_with_annotations_via_pool!( + test_pool().await, + common::MYSQL_DIALECT + ); } #[tokio::test] #[serial] async fn query_scalar_fetch_one_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i32 = sqlx::query_scalar("SELECT 42") - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 42); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_one_with_annotations_via_pool!( + test_pool().await, + common::MYSQL_DIALECT + ); } #[tokio::test] #[serial] async fn query_scalar_fetch_optional_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: Option = sqlx::query_scalar("SELECT 1 FROM (SELECT 1) t WHERE 1 = 0") - .with_annotations(test_annotations()) - .fetch_optional(&pool) - .await - .unwrap(); - assert!(value.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_optional_with_annotations_via_pool!( + test_pool().await, + common::MYSQL_DIALECT + ); } // =========================================================================== @@ -2540,81 +743,25 @@ async fn query_scalar_fetch_optional_with_annotations_via_pool() { #[tokio::test] #[serial] async fn query_map_position_1_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT CAST(? AS SIGNED)") - .with_annotations(test_annotations()) - .bind(7_i64) - .map(|row: sqlx::mysql::MySqlRow| row.get::(0)) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 7); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_position_1_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_map_position_2_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT CAST(? AS SIGNED)") - .bind(11_i64) - .with_annotations(test_annotations()) - .map(|row: sqlx::mysql::MySqlRow| row.get::(0)) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 11); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_position_2_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_map_position_3_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT CAST(? AS SIGNED)") - .bind(13_i64) - .map(|row: sqlx::mysql::MySqlRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 13); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_position_3_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_try_map_position_3_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT CAST(? AS SIGNED)") - .bind(17_i64) - .try_map(|row: sqlx::mysql::MySqlRow| Ok(row.get::(0))) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 17); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_try_map_position_3_via_pool!(test_pool().await, common::MYSQL_DIALECT); } // --- Per-method on Map (so each forwarder body is hit) -------------------- @@ -2622,99 +769,31 @@ async fn query_try_map_position_3_via_pool() { #[tokio::test] #[serial] async fn map_fetch_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") - .map(|row: sqlx::mysql::MySqlRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); + test_map_fetch_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn map_fetch_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") - .map(|row: sqlx::mysql::MySqlRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_many(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_fetch_many_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn map_fetch_all_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows: Vec = sqlx::query("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .map(|row: sqlx::mysql::MySqlRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_all(&pool) - .await - .unwrap(); - assert_eq!(rows, vec![1, 2, 3]); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_fetch_all_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn map_fetch_one_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT 19") - .map(|row: sqlx::mysql::MySqlRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 19); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_fetch_one_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn map_fetch_optional_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: Option = sqlx::query("SELECT 1 FROM (SELECT 1) t WHERE 1 = 0") - .map(|row: sqlx::mysql::MySqlRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_optional(&pool) - .await - .unwrap(); - assert!(value.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_fetch_optional_with_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } // --- Composition (multi-map; both branches of step 4) -------------------- @@ -2722,41 +801,13 @@ async fn map_fetch_optional_with_annotations_via_pool() { #[tokio::test] #[serial] async fn map_compose_after_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT 5") - .map(|row: sqlx::mysql::MySqlRow| row.get::(0)) - .with_annotations(test_annotations()) - .map(|n| n * 2) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 10); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_compose_after_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn map_try_map_compose_after_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT 6") - .map(|row: sqlx::mysql::MySqlRow| row.get::(0)) - .with_annotations(test_annotations()) - .try_map(|n: i64| Ok::<_, sqlx::Error>(n + 100)) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 106); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_try_map_compose_after_annotations_via_pool!(test_pool().await, common::MYSQL_DIALECT); } // --- Other executor receivers (smoke) ------------------------------------- @@ -2764,42 +815,13 @@ async fn map_try_map_compose_after_annotations_via_pool() { #[tokio::test] #[serial] async fn query_map_with_annotations_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let value: i64 = sqlx::query("SELECT 23") - .map(|row: sqlx::mysql::MySqlRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&mut conn) - .await - .unwrap(); - assert_eq!(value, 23); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_with_annotations_via_connection!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_map_with_annotations_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, MySql> = pool.begin().await.unwrap(); - let value: i64 = sqlx::query("SELECT 29") - .map(|row: sqlx::mysql::MySqlRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&mut tx) - .await - .unwrap(); - assert_eq!(value, 29); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_with_annotations_via_transaction!(test_pool().await, common::MYSQL_DIALECT); } // --- Error paths ---------------------------------------------------------- @@ -2807,47 +829,16 @@ async fn query_map_with_annotations_via_transaction() { #[tokio::test] #[serial] async fn query_map_with_annotations_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result: Result = sqlx::query("INVALID SQL") - .map(|row: sqlx::mysql::MySqlRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_error_span(&spans[0]); + test_query_map_with_annotations_records_error!(test_pool().await, common::MYSQL_DIALECT); } #[tokio::test] #[serial] async fn query_try_map_with_annotations_propagates_mapper_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - // The mapper error fires *after* the database round-trip succeeds – the executor - // sees the row arrive and completes the fetch successfully, then the mapper surfaces - // the error to the caller. The span therefore stays at success at the database - // layer; the contract verified here is that the user-visible Err propagates through - // the wrapper and that the annotations were attached to the (successful) span. - let result: Result = sqlx::query("SELECT 1") - .try_map(|_row: sqlx::mysql::MySqlRow| { - Err::(sqlx::Error::Decode( - "intentional decode failure".to_string().into(), - )) - }) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_try_map_with_annotations_propagates_mapper_error!( + test_pool().await, + common::MYSQL_DIALECT + ); } // =========================================================================== @@ -2883,9 +874,7 @@ async fn query_macro_execute_with_annotations_via_pool() { .unwrap(); assert_eq!(result.rows_affected(), 1); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::MYSQL_DIALECT); } #[tokio::test] @@ -2911,9 +900,7 @@ async fn query_macro_fetch_one_with_annotations_via_pool() { assert_eq!(row.id, 202); assert_eq!(row.name, "bob"); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::MYSQL_DIALECT); } #[tokio::test] @@ -2944,9 +931,7 @@ async fn query_macro_fetch_all_with_annotations_via_pool() { .unwrap(); assert_eq!(rows.len(), 3); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::MYSQL_DIALECT); } #[tokio::test] @@ -2967,9 +952,7 @@ async fn query_macro_fetch_optional_with_annotations_via_pool() { .unwrap(); assert!(row.is_none()); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::MYSQL_DIALECT); } type MacroUser = common::MacroUser; @@ -3001,9 +984,7 @@ async fn query_as_macro_fetch_one_with_annotations_via_pool() { assert_eq!(user.id, 206); assert_eq!(user.name, "frank"); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::MYSQL_DIALECT); } #[tokio::test] @@ -3033,9 +1014,7 @@ async fn query_as_macro_fetch_all_with_annotations_via_pool() { .unwrap(); assert_eq!(users.len(), 2); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::MYSQL_DIALECT); } #[tokio::test] @@ -3060,9 +1039,7 @@ async fn query_as_macro_fetch_optional_with_annotations_via_pool() { .unwrap(); assert!(user.is_none()); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::MYSQL_DIALECT); } #[tokio::test] @@ -3087,9 +1064,7 @@ async fn query_scalar_macro_fetch_one_with_annotations_via_pool() { .unwrap(); assert_eq!(name, "irene"); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::MYSQL_DIALECT); } #[tokio::test] @@ -3118,7 +1093,5 @@ async fn query_scalar_macro_fetch_all_with_annotations_via_pool() { .unwrap(); assert_eq!(ids, vec![210, 211]); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::MYSQL_DIALECT); } diff --git a/tests/postgres.rs b/tests/postgres.rs index 42efe96..d382d9d 100644 --- a/tests/postgres.rs +++ b/tests/postgres.rs @@ -5,20 +5,18 @@ mod common; use std::sync::OnceLock; use std::time::Duration; -use common::{assert_common_span_attributes, assert_error_span, attr}; -use futures::StreamExt; -use opentelemetry::trace::SpanKind; +use common::{assert_error_span, attr, test_annotations}; use serial_test::serial; use sqlx::Executor as _; use sqlx::Postgres; -use sqlx::Row as _; -use sqlx_otel::{Pool, PoolBuilder, QueryAnnotateExt, QueryAnnotations, Transaction}; +use sqlx_otel::{Pool, PoolBuilder, QueryAnnotateExt}; use testcontainers::core::IntoContainerPort; use testcontainers::runners::AsyncRunner; use testcontainers::{ContainerAsync, GenericImage, ImageExt}; use tokio::sync::OnceCell; -const SYSTEM: &str = "postgresql"; +/// Backend row type used by parameterised map test macros (see `tests/sqlite.rs`). +type Row = sqlx::postgres::PgRow; /// Shared container and connection URL, initialised once across all tests. struct SharedContainer { @@ -28,6 +26,12 @@ struct SharedContainer { static CONTAINER: OnceLock> = OnceLock::new(); +/// Container ID captured at start-time for use by the [`drop_container`] destructor. +/// Held in a sibling static (rather than reading from `SharedContainer` at exit) +/// because the `ContainerAsync` value is itself locked behind a `'static` future and +/// the destructor must run synchronously without async access. +static CONTAINER_ID: OnceLock = OnceLock::new(); + async fn shared_container() -> &'static SharedContainer { CONTAINER .get_or_init(OnceCell::new) @@ -45,6 +49,8 @@ async fn shared_container() -> &'static SharedContainer { .await .expect("starting postgres container"); + let _ = CONTAINER_ID.set(container.id().to_string()); + let port = container.get_host_port_ipv4(5432).await.unwrap(); let url = format!("postgres://postgres@localhost:{port}/testdb"); SharedContainer { @@ -55,36 +61,32 @@ async fn shared_container() -> &'static SharedContainer { .await } -/// Return an instrumented pool connected to the shared container. -async fn test_pool() -> Pool { - let shared = shared_container().await; - let raw = sqlx::PgPool::connect(&shared.url).await.unwrap(); - PoolBuilder::from(raw).build() +/// Stop and remove the shared container at process exit. Required because the +/// `ContainerAsync` value lives in a `'static` (`CONTAINER`), so the language never +/// runs its `Drop`. Using `ctor::dtor` schedules a synchronous shell-out to +/// `docker rm -f` that fires after `main` returns – equivalent to the per-test +/// RAII cleanup that existed before the shared-container refactor (commit c29f995). +#[ctor::dtor] +fn drop_container() { + if let Some(id) = CONTAINER_ID.get() { + let _ = std::process::Command::new("docker") + .args(["rm", "-f", id.as_str()]) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status(); + } } -/// Standard annotations used across annotation assertions. -fn test_annotations() -> QueryAnnotations { - QueryAnnotations::new() - .operation("SELECT") - .collection("users") +/// Return an instrumented pool connected to the shared container. +async fn test_pool() -> Pool { + PoolBuilder::from(raw_pool().await).build() } -/// Assert that the span carries the standard annotation attributes. -fn assert_annotated_span(span: &opentelemetry_sdk::trace::SpanData) { - assert_eq!(span.span_kind, SpanKind::Client); - assert_eq!(span.name, "SELECT users"); - assert_eq!( - attr(span, "db.system.name"), - Some(opentelemetry::Value::String(SYSTEM.to_owned().into())), - ); - assert_eq!( - attr(span, "db.operation.name"), - Some(opentelemetry::Value::String("SELECT".into())), - ); - assert_eq!( - attr(span, "db.collection.name"), - Some(opentelemetry::Value::String("users".into())), - ); +/// Raw (un-instrumented) sqlx pool, used by parameterised builder / query-text-mode +/// tests that need to apply specific `PoolBuilder` configurations themselves. +async fn raw_pool() -> sqlx::PgPool { + let shared = shared_container().await; + sqlx::PgPool::connect(&shared.url).await.unwrap() } // =========================================================================== @@ -94,235 +96,31 @@ fn assert_annotated_span(span: &opentelemetry_sdk::trace::SpanData) { #[tokio::test] #[serial] async fn execute_creates_span_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE IF NOT EXISTS exec_pool (id SERIAL PRIMARY KEY)") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert!(attr(&spans[0], "db.response.affected_rows").is_some()); - - // With annotations - pool.with_annotations(test_annotations()) - .execute("CREATE TABLE IF NOT EXISTS exec_pool (id SERIAL PRIMARY KEY)") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .execute("CREATE TABLE IF NOT EXISTS exec_pool3 (id SERIAL PRIMARY KEY)") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_creates_span_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn execute_creates_span_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - sqlx::query("CREATE TABLE IF NOT EXISTS exec_conn (id SERIAL PRIMARY KEY)") - .execute(&mut conn) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert!(attr(&spans[0], "db.response.affected_rows").is_some()); - - // With annotations - conn.with_annotations(test_annotations()) - .execute("CREATE TABLE IF NOT EXISTS exec_conn (id SERIAL PRIMARY KEY)") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .execute("CREATE TABLE IF NOT EXISTS exec_conn3 (id SERIAL PRIMARY KEY)") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_creates_span_via_connection!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn execute_creates_span_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Postgres> = pool.begin().await.unwrap(); - sqlx::query("CREATE TABLE IF NOT EXISTS exec_tx (id SERIAL PRIMARY KEY)") - .execute(&mut tx) - .await - .unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .execute("CREATE TABLE IF NOT EXISTS exec_tx (id SERIAL PRIMARY KEY)") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .execute("CREATE TABLE IF NOT EXISTS exec_tx3 (id SERIAL PRIMARY KEY)") - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert!(attr(&spans[0], "db.response.affected_rows").is_some()); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_creates_span_via_transaction!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn execute_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = sqlx::query("INVALID SQL GIBBERISH").execute(&pool).await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations (error path) - let result = pool - .with_annotations(test_annotations()) - .execute("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = pool - .with_operation("SELECT", "users") - .execute("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_execute_records_error!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn execute_records_affected_rows() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query( - "CREATE TABLE IF NOT EXISTS affected_test (id INT PRIMARY KEY, name TEXT NOT NULL)", - ) - .execute(&pool) - .await - .unwrap(); - sqlx::query("DELETE FROM affected_test") - .execute(&pool) - .await - .unwrap(); - - tel.reset(); - - // --- Bulk insert --- - sqlx::query( - "INSERT INTO affected_test (id, name) VALUES (1, 'alice'), (2, 'bob'), (3, 'carol')", - ) - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(3)), - "inserting 3 rows in one statement should affect 3 rows" - ); - tel.reset(); - - // --- Upsert (INSERT ON CONFLICT) --- - sqlx::query( - "INSERT INTO affected_test (id, name) VALUES (1, 'alice_updated') \ - ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name", - ) - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(1)), - "upsert should affect 1 row" - ); - tel.reset(); - - // --- Update multiple rows --- - sqlx::query("UPDATE affected_test SET name = name || '_updated' WHERE id IN (2, 3)") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(2)), - "updating two rows should affect 2 rows" - ); - tel.reset(); - - // --- Delete multiple rows --- - sqlx::query("DELETE FROM affected_test WHERE id IN (1, 2, 3)") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(3)), - "deleting three rows should affect 3 rows" - ); - tel.reset(); - - // --- Delete with no matching rows --- - sqlx::query("DELETE FROM affected_test WHERE id = 999") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(0)), - "deleting non-existent rows should affect 0 rows" - ); + test_execute_records_affected_rows!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -332,153 +130,25 @@ async fn execute_records_affected_rows() { #[tokio::test] #[serial] async fn execute_many_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - - // With annotations - let mut stream = pool - .with_annotations(test_annotations()) - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = pool - .with_operation("SELECT", "users") - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_many_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn execute_many_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let mut stream = (&mut conn).execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - - // With annotations - let mut stream = conn - .with_annotations(test_annotations()) - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = conn - .with_operation("SELECT", "users") - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_many_via_connection!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn execute_many_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Postgres> = pool.begin().await.unwrap(); - - let mut stream = (&mut tx).execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With annotations - let mut stream = tx - .with_annotations(test_annotations()) - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With shorthand - let mut stream = tx - .with_operation("SELECT", "users") - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_many_via_transaction!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn execute_many_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).execute_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - - // With annotations (error path) - let mut stream = pool - .with_annotations(test_annotations()) - .execute_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let mut stream = pool - .with_operation("SELECT", "users") - .execute_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_execute_many_records_error!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -488,175 +158,34 @@ async fn execute_many_records_error() { #[tokio::test] #[serial] async fn fetch_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).fetch("SELECT 1 UNION ALL SELECT 2"); - let mut count = 0u64; - while stream.next().await.is_some() { - count += 1; - } - assert_eq!(count, 2); - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - let mut stream = pool - .with_annotations(test_annotations()) - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = pool - .with_operation("SELECT", "users") - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let mut stream = (&mut conn).fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - let mut stream = conn - .with_annotations(test_annotations()) - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = conn - .with_operation("SELECT", "users") - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_via_connection!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Postgres> = pool.begin().await.unwrap(); - - let mut stream = (&mut tx).fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With annotations - let mut stream = tx - .with_annotations(test_annotations()) - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With shorthand - let mut stream = tx - .with_operation("SELECT", "users") - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_via_transaction!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_stream_dropped_early_still_records_span() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - { - let mut stream = (&pool).fetch("SELECT 1 UNION ALL SELECT 2"); - let _ = stream.next().await; - } - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) + test_fetch_stream_dropped_early_still_records_span!( + test_pool().await, + common::POSTGRES_DIALECT ); } #[tokio::test] #[serial] async fn fetch_stream_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).fetch("INVALID SQL"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - assert_error_span(&spans[0]); - - // With annotations (error path) - let mut stream = pool - .with_annotations(test_annotations()) - .fetch("INVALID SQL"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let mut stream = pool.with_operation("SELECT", "users").fetch("INVALID SQL"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_stream_records_error!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -666,179 +195,31 @@ async fn fetch_stream_records_error() { #[tokio::test] #[serial] async fn fetch_many_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).fetch_many("SELECT 1 UNION ALL SELECT 2"); - let mut rows = 0u64; - while let Some(item) = stream.next().await { - if let Ok(sqlx::Either::Right(_)) = item { - rows += 1; - } - } - drop(stream); - assert_eq!(rows, 2); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - let mut stream = pool - .with_annotations(test_annotations()) - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = pool - .with_operation("SELECT", "users") - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_many_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_many_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let mut stream = (&mut conn).fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - let mut stream = conn - .with_annotations(test_annotations()) - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = conn - .with_operation("SELECT", "users") - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_many_via_connection!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_many_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Postgres> = pool.begin().await.unwrap(); - - let mut stream = (&mut tx).fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With annotations - let mut stream = tx - .with_annotations(test_annotations()) - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With shorthand - let mut stream = tx - .with_operation("SELECT", "users") - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_many_via_transaction!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_many_dropped_early_still_records_span() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - { - let mut stream = (&pool).fetch_many("SELECT 1 UNION ALL SELECT 2"); - let _ = stream.next().await; - } - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); + test_fetch_many_dropped_early_still_records_span!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_many_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).fetch_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - assert_error_span(&spans[0]); - - // With annotations (error path) - let mut stream = pool - .with_annotations(test_annotations()) - .fetch_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let mut stream = pool - .with_operation("SELECT", "users") - .fetch_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_many_records_error!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -848,145 +229,25 @@ async fn fetch_many_records_error() { #[tokio::test] #[serial] async fn fetch_all_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows = (&pool) - .fetch_all("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .await - .unwrap(); - assert_eq!(rows.len(), 3); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(3)) - ); - - // With annotations - pool.with_annotations(test_annotations()) - .fetch_all("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .fetch_all("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_all_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_all_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let rows = (&mut conn) - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - assert_eq!(rows.len(), 2); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - conn.with_annotations(test_annotations()) - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_all_via_connection!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_all_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Postgres> = pool.begin().await.unwrap(); - let rows = (&mut tx) - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - assert_eq!(rows.len(), 2); - - // With annotations - tx.with_annotations(test_annotations()) - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_all_via_transaction!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_all_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = (&pool).fetch_all("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_error_span(&spans[0]); - - // With annotations (error path) - let result = pool - .with_annotations(test_annotations()) - .fetch_all("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = pool - .with_operation("SELECT", "users") - .fetch_all("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_all_records_error!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -996,133 +257,25 @@ async fn fetch_all_records_error() { #[tokio::test] #[serial] async fn fetch_one_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _row = (&pool).fetch_one("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - - // With annotations - pool.with_annotations(test_annotations()) - .fetch_one("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .fetch_one("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_one_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_one_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let _row = (&mut conn).fetch_one("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - - // With annotations - conn.with_annotations(test_annotations()) - .fetch_one("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .fetch_one("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_one_via_connection!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] -async fn fetch_one_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Postgres> = pool.begin().await.unwrap(); - let _row = (&mut tx).fetch_one("SELECT 1").await.unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .fetch_one("SELECT 1") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .fetch_one("SELECT 1") - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - assert_annotated_span(tel.spans().last().unwrap()); -} - -#[tokio::test] -#[serial] -async fn fetch_one_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = (&pool).fetch_one("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_error_span(&spans[0]); +async fn fetch_one_via_transaction() { + test_fetch_one_via_transaction!(test_pool().await, common::POSTGRES_DIALECT); +} - // With annotations (error path) - let result = pool - .with_annotations(test_annotations()) - .fetch_one("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = pool - .with_operation("SELECT", "users") - .fetch_one("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); +#[tokio::test] +#[serial] +async fn fetch_one_records_error() { + test_fetch_one_records_error!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -1132,167 +285,31 @@ async fn fetch_one_records_error() { #[tokio::test] #[serial] async fn fetch_optional_records_one_row() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = (&pool).fetch_optional("SELECT 1").await.unwrap(); - assert!(result.is_some()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - - // With annotations - pool.with_annotations(test_annotations()) - .fetch_optional("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .fetch_optional("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_optional_records_one_row!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_optional_records_zero_rows() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE IF NOT EXISTS empty_table (id SERIAL PRIMARY KEY)") - .execute(&pool) - .await - .unwrap(); - sqlx::query("DELETE FROM empty_table") - .execute(&pool) - .await - .unwrap(); - - tel.reset(); - let result = (&pool) - .fetch_optional("SELECT id FROM empty_table") - .await - .unwrap(); - assert!(result.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); + test_fetch_optional_records_zero_rows!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_optional_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let result = (&mut conn).fetch_optional("SELECT 42").await.unwrap(); - assert!(result.is_some()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - - // With annotations - conn.with_annotations(test_annotations()) - .fetch_optional("SELECT 42") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .fetch_optional("SELECT 42") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_optional_via_connection!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_optional_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Postgres> = pool.begin().await.unwrap(); - let result = (&mut tx).fetch_optional("SELECT 99").await.unwrap(); - assert!(result.is_some()); - - // With annotations - tx.with_annotations(test_annotations()) - .fetch_optional("SELECT 99") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .fetch_optional("SELECT 99") - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_optional_via_transaction!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn fetch_optional_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = (&pool).fetch_optional("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_error_span(&spans[0]); - - // With annotations (error path) - let result = pool - .with_annotations(test_annotations()) - .fetch_optional("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = pool - .with_operation("SELECT", "users") - .fetch_optional("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_optional_records_error!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -1302,125 +319,25 @@ async fn fetch_optional_records_error() { #[tokio::test] #[serial] async fn prepare_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _stmt = (&pool).prepare("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - pool.with_annotations(test_annotations()) - .prepare("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .prepare("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn prepare_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let _stmt = (&mut conn).prepare("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - conn.with_annotations(test_annotations()) - .prepare("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .prepare("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_via_connection!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn prepare_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Postgres> = pool.begin().await.unwrap(); - let _stmt = (&mut tx).prepare("SELECT 1").await.unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .prepare("SELECT 1") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .prepare("SELECT 1") - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_via_transaction!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn prepare_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let result = (&mut conn).prepare("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_error_span(&spans[0]); - - // With annotations (error path) - let result = conn - .with_annotations(test_annotations()) - .prepare("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = conn - .with_operation("SELECT", "users") - .prepare("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_prepare_records_error!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -1430,125 +347,25 @@ async fn prepare_records_error() { #[tokio::test] #[serial] async fn prepare_with_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _stmt = (&pool).prepare_with("SELECT $1", &[]).await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - pool.with_annotations(test_annotations()) - .prepare_with("SELECT $1", &[]) - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .prepare_with("SELECT $1", &[]) - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_with_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn prepare_with_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let _stmt = (&mut conn).prepare_with("SELECT $1", &[]).await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - conn.with_annotations(test_annotations()) - .prepare_with("SELECT $1", &[]) - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .prepare_with("SELECT $1", &[]) - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_with_via_connection!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn prepare_with_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Postgres> = pool.begin().await.unwrap(); - let _stmt = (&mut tx).prepare_with("SELECT $1", &[]).await.unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .prepare_with("SELECT $1", &[]) - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .prepare_with("SELECT $1", &[]) - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_with_via_transaction!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn prepare_with_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let result = (&mut conn).prepare_with("INVALID SQL GIBBERISH", &[]).await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_error_span(&spans[0]); - - // With annotations (error path) - let result = conn - .with_annotations(test_annotations()) - .prepare_with("INVALID SQL GIBBERISH", &[]) - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = conn - .with_operation("SELECT", "users") - .prepare_with("INVALID SQL GIBBERISH", &[]) - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_prepare_with_records_error!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -1558,125 +375,25 @@ async fn prepare_with_records_error() { #[tokio::test] #[serial] async fn describe_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _desc = (&pool).describe("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - pool.with_annotations(test_annotations()) - .describe("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .describe("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_describe_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn describe_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let _desc = (&mut conn).describe("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - conn.with_annotations(test_annotations()) - .describe("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .describe("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_describe_via_connection!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn describe_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Postgres> = pool.begin().await.unwrap(); - let _desc = (&mut tx).describe("SELECT 1").await.unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .describe("SELECT 1") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .describe("SELECT 1") - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_annotated_span(tel.spans().last().unwrap()); + test_describe_via_transaction!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn describe_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let result = (&mut conn).describe("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_error_span(&spans[0]); - - // With annotations (error path) - let result = conn - .with_annotations(test_annotations()) - .describe("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let spans = tel.spans(); - let last = spans.last().unwrap(); - assert_annotated_span(last); - assert_error_span(last); - - // With shorthand (error path) - let result = conn - .with_operation("SELECT", "users") - .describe("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_describe_records_error!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -1757,64 +474,17 @@ async fn sqlstate_recorded_on_constraint_violation() { #[tokio::test] #[serial] async fn operation_duration_metric_is_recorded() { - use opentelemetry_sdk::metrics::data::{AggregatedMetrics, MetricData}; - - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _row = (&pool).fetch_one("SELECT 1").await.unwrap(); - - let resource_metrics = tel.metrics(); - assert!(!resource_metrics.is_empty(), "should have metric data"); - - let mut found_duration = false; - for rm in &resource_metrics { - for sm in rm.scope_metrics() { - for metric in sm.metrics() { - if metric.name() == "db.client.operation.duration" { - found_duration = true; - assert_eq!(metric.unit(), "s"); - if let AggregatedMetrics::F64(MetricData::Histogram(hist)) = metric.data() { - let dp: Vec<_> = hist.data_points().collect(); - assert!(!dp.is_empty(), "histogram should have data points"); - assert!(dp[0].count() > 0, "data point count should be > 0"); - let has_system = dp[0] - .attributes() - .any(|kv| kv.key.as_str() == "db.system.name"); - assert!(has_system, "metric should have db.system.name attribute"); - } else { - panic!("db.client.operation.duration should be an f64 histogram"); - } - } - } - } - } - assert!( - found_duration, - "db.client.operation.duration metric not found" - ); + test_operation_duration_metric_is_recorded!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== -// Transaction rollback -// =========================================================================== - -#[tokio::test] -#[serial] -async fn transaction_rollback() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Postgres> = pool.begin().await.unwrap(); - sqlx::query("CREATE TABLE IF NOT EXISTS rollback_test (id SERIAL PRIMARY KEY)") - .execute(&mut tx) - .await - .unwrap(); - tx.rollback().await.unwrap(); +// Transaction rollback +// =========================================================================== - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); +#[tokio::test] +#[serial] +async fn transaction_rollback() { + test_transaction_rollback!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -1824,93 +494,31 @@ async fn transaction_rollback() { #[tokio::test] #[serial] async fn builder_with_database_overrides_namespace() { - let tel = common::TestTelemetry::install(); - let shared = shared_container().await; - let raw = sqlx::PgPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw).with_database("custom_db").build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.namespace"), - Some(opentelemetry::Value::String("custom_db".into())) - ); + test_builder_with_database_overrides_namespace!(raw_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn builder_with_host_overrides_server_address() { - let tel = common::TestTelemetry::install(); - let shared = shared_container().await; - let raw = sqlx::PgPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw).with_host("custom-host").build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "server.address"), - Some(opentelemetry::Value::String("custom-host".into())) - ); + test_builder_with_host_overrides_server_address!(raw_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn builder_with_port_overrides_server_port() { - let tel = common::TestTelemetry::install(); - let shared = shared_container().await; - let raw = sqlx::PgPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw).with_port(9999).build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "server.port"), - Some(opentelemetry::Value::I64(9999)) - ); + test_builder_with_port_overrides_server_port!(raw_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn builder_with_network_peer_address() { - let tel = common::TestTelemetry::install(); - let shared = shared_container().await; - let raw = sqlx::PgPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw) - .with_network_peer_address("10.0.0.5") - .build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "network.peer.address"), - Some(opentelemetry::Value::String("10.0.0.5".into())) - ); + test_builder_with_network_peer_address!(raw_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn builder_with_network_peer_port() { - let tel = common::TestTelemetry::install(); - let shared = shared_container().await; - let raw = sqlx::PgPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw).with_network_peer_port(5433).build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "network.peer.port"), - Some(opentelemetry::Value::I64(5433)) - ); + test_builder_with_network_peer_port!(raw_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -1920,12 +528,7 @@ async fn builder_with_network_peer_port() { #[tokio::test] #[serial] async fn pool_close_and_is_closed() { - let _tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - assert!(!pool.is_closed()); - pool.close().await; - assert!(pool.is_closed()); + test_pool_close_and_is_closed!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -1935,50 +538,13 @@ async fn pool_close_and_is_closed() { #[tokio::test] #[serial] async fn query_text_mode_off_suppresses_sql() { - let shared = shared_container().await; - let raw = sqlx::PgPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw) - .with_query_text_mode(sqlx_otel::QueryTextMode::Off) - .build(); - - let tel = common::TestTelemetry::install(); - let _row = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!(spans[0].span_kind, SpanKind::Client); - assert_eq!( - attr(&spans[0], "db.system.name"), - Some(opentelemetry::Value::String(SYSTEM.to_owned().into())) - ); - assert!(attr(&spans[0], "db.namespace").is_some()); - assert!( - attr(&spans[0], "db.query.text").is_none(), - "db.query.text should not be present when QueryTextMode::Off" - ); + test_query_text_mode_off_suppresses_sql!(raw_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_text_mode_obfuscated_replaces_literals() { - let shared = shared_container().await; - let raw = sqlx::PgPool::connect(&shared.url).await.unwrap(); - let pool = PoolBuilder::from(raw) - .with_query_text_mode(sqlx_otel::QueryTextMode::Obfuscated) - .build(); - - let tel = common::TestTelemetry::install(); - let _row = (&pool) - .fetch_optional("SELECT 1, 'alice', 3.14") - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.query.text"), - Some(opentelemetry::Value::String("SELECT ?, ?, ?".into())) - ); + test_query_text_mode_obfuscated_replaces_literals!(raw_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -1988,75 +554,13 @@ async fn query_text_mode_obfuscated_replaces_literals() { #[tokio::test] #[serial] async fn annotation_all_four_fields() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - pool.with_annotations( - QueryAnnotations::new() - .operation("SELECT") - .collection("users") - .query_summary("users by id") - .stored_procedure("sp_get_users"), - ) - .fetch_all("SELECT 1") - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - // Summary drives the span name (semconv level 1), distinct from "SELECT users" so - // the assertion proves the summary path won rather than coinciding with level 2. - assert_eq!(spans[0].name, "users by id"); - assert_eq!( - attr(&spans[0], "db.operation.name"), - Some(opentelemetry::Value::String("SELECT".into())), - ); - assert_eq!( - attr(&spans[0], "db.collection.name"), - Some(opentelemetry::Value::String("users".into())), - ); - assert_eq!( - attr(&spans[0], "db.query.summary"), - Some(opentelemetry::Value::String("users by id".into())), - ); - assert_eq!( - attr(&spans[0], "db.stored_procedure.name"), - Some(opentelemetry::Value::String("sp_get_users".into())), - ); + test_annotation_all_four_fields!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_summary_drives_span_name() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - pool.with_annotations( - QueryAnnotations::new() - .operation("SELECT") - .collection("users") - .query_summary("users by tenant"), - ) - .fetch_all("SELECT 1") - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!(spans[0].name, "users by tenant"); - // Summary drives the *name*, but does not suppress the other attributes. - assert_eq!( - attr(&spans[0], "db.query.summary"), - Some(opentelemetry::Value::String("users by tenant".into())), - ); - assert_eq!( - attr(&spans[0], "db.operation.name"), - Some(opentelemetry::Value::String("SELECT".into())), - ); - assert_eq!( - attr(&spans[0], "db.collection.name"), - Some(opentelemetry::Value::String("users".into())), - ); + test_query_summary_drives_span_name!(test_pool().await, common::POSTGRES_DIALECT); } // =========================================================================== @@ -2066,263 +570,88 @@ async fn query_summary_drives_span_name() { #[tokio::test] #[serial] async fn query_execute_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE IF NOT EXISTS qe_pool (id SERIAL PRIMARY KEY)") - .with_annotations(test_annotations()) - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert!(attr(&spans[0], "db.response.affected_rows").is_some()); + test_query_execute_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_execute_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query("SELECT 1; SELECT 2") - .with_annotations(test_annotations()) - .execute_many(&pool) - .await; - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_execute_many_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_fetch_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); + test_query_fetch_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_fetch_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_many(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_fetch_many_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_fetch_all_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows = sqlx::query("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .with_annotations(test_annotations()) - .fetch_all(&pool) - .await - .unwrap(); - assert_eq!(rows.len(), 3); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(3)) - ); + test_query_fetch_all_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_fetch_one_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _row = sqlx::query("SELECT 1") - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); + test_query_fetch_one_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_fetch_optional_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE IF NOT EXISTS qfo_pool (id INT PRIMARY KEY)") - .execute(&pool) - .await - .unwrap(); - - tel.reset(); - - let row = sqlx::query("SELECT id FROM qfo_pool WHERE id = 1") - .with_annotations(test_annotations()) - .fetch_optional(&pool) - .await - .unwrap(); - assert!(row.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) + test_query_fetch_optional_with_annotations_via_pool!( + test_pool().await, + common::POSTGRES_DIALECT ); } #[tokio::test] #[serial] async fn query_bind_first_then_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let row = sqlx::query("SELECT $1::int + $2::int AS sum") - .bind(2_i32) - .bind(3_i32) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - let sum: i32 = row.try_get("sum").unwrap(); - assert_eq!(sum, 5); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_bind_first_then_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_annotations_first_then_bind_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let row = sqlx::query("SELECT $1::int + $2::int AS sum") - .with_annotations(test_annotations()) - .bind(10_i32) - .bind(20_i32) - .fetch_one(&pool) - .await - .unwrap(); - let sum: i32 = row.try_get("sum").unwrap(); - assert_eq!(sum, 30); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_annotations_first_then_bind_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_with_operation_shorthand_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE IF NOT EXISTS qop_pool (id SERIAL PRIMARY KEY)") - .with_operation("SELECT", "users") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_with_operation_shorthand_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_execute_with_annotations_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - sqlx::query("CREATE TABLE IF NOT EXISTS qe_conn (id SERIAL PRIMARY KEY)") - .with_annotations(test_annotations()) - .execute(&mut conn) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_execute_with_annotations_via_connection!( + test_pool().await, + common::POSTGRES_DIALECT + ); } #[tokio::test] #[serial] async fn query_execute_with_annotations_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Postgres> = pool.begin().await.unwrap(); - sqlx::query("CREATE TABLE IF NOT EXISTS qe_tx (id SERIAL PRIMARY KEY)") - .with_annotations(test_annotations()) - .execute(&mut tx) - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_execute_with_annotations_via_transaction!( + test_pool().await, + common::POSTGRES_DIALECT + ); } #[tokio::test] #[serial] async fn query_execute_with_annotations_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = sqlx::query("INVALID SQL GIBBERISH") - .with_annotations(test_annotations()) - .execute(&pool) - .await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_error_span(&spans[0]); + test_query_execute_with_annotations_records_error!(test_pool().await, common::POSTGRES_DIALECT); } // --- query_as side --------------------------------------------------------- @@ -2330,108 +659,46 @@ async fn query_execute_with_annotations_records_error() { #[tokio::test] #[serial] async fn query_as_fetch_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = sqlx::query_as::<_, (i32,)>("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_as_fetch_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } -#[tokio::test] -#[serial] -async fn query_as_fetch_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query_as::<_, (i32,)>("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_many(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); +#[tokio::test] +#[serial] +async fn query_as_fetch_many_with_annotations_via_pool() { + test_query_as_fetch_many_with_annotations_via_pool!( + test_pool().await, + common::POSTGRES_DIALECT + ); } #[tokio::test] #[serial] async fn query_as_fetch_all_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows: Vec<(i32,)> = sqlx::query_as("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_all(&pool) - .await - .unwrap(); - assert_eq!(rows.len(), 2); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_as_fetch_all_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_as_fetch_one_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let row: (i32,) = sqlx::query_as("SELECT 7") - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(row.0, 7); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_as_fetch_one_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_as_fetch_optional_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let row: Option<(i32,)> = sqlx::query_as("SELECT 1 WHERE 1 = 0") - .with_annotations(test_annotations()) - .fetch_optional(&pool) - .await - .unwrap(); - assert!(row.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_as_fetch_optional_with_annotations_via_pool!( + test_pool().await, + common::POSTGRES_DIALECT + ); } #[tokio::test] #[serial] async fn query_as_fetch_one_with_annotations_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result: Result<(i32,), _> = sqlx::query_as("INVALID SQL") - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_error_span(&spans[0]); + test_query_as_fetch_one_with_annotations_records_error!( + test_pool().await, + common::POSTGRES_DIALECT + ); } // --- query_scalar side ----------------------------------------------------- @@ -2439,90 +706,43 @@ async fn query_as_fetch_one_with_annotations_records_error() { #[tokio::test] #[serial] async fn query_scalar_fetch_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = sqlx::query_scalar::<_, i32>("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_scalar_fetch_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query_scalar::<_, i32>("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_many(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_many_with_annotations_via_pool!( + test_pool().await, + common::POSTGRES_DIALECT + ); } #[tokio::test] #[serial] async fn query_scalar_fetch_all_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows: Vec = sqlx::query_scalar("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_all(&pool) - .await - .unwrap(); - assert_eq!(rows, vec![1, 2]); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_all_with_annotations_via_pool!( + test_pool().await, + common::POSTGRES_DIALECT + ); } #[tokio::test] #[serial] async fn query_scalar_fetch_one_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i32 = sqlx::query_scalar("SELECT 42") - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 42); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_one_with_annotations_via_pool!( + test_pool().await, + common::POSTGRES_DIALECT + ); } #[tokio::test] #[serial] async fn query_scalar_fetch_optional_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: Option = sqlx::query_scalar("SELECT 1 WHERE 1 = 0") - .with_annotations(test_annotations()) - .fetch_optional(&pool) - .await - .unwrap(); - assert!(value.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_optional_with_annotations_via_pool!( + test_pool().await, + common::POSTGRES_DIALECT + ); } // =========================================================================== @@ -2534,81 +754,25 @@ async fn query_scalar_fetch_optional_with_annotations_via_pool() { #[tokio::test] #[serial] async fn query_map_position_1_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT $1::int8") - .with_annotations(test_annotations()) - .bind(7_i64) - .map(|row: sqlx::postgres::PgRow| row.get::(0)) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 7); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_position_1_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_map_position_2_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT $1::int8") - .bind(11_i64) - .with_annotations(test_annotations()) - .map(|row: sqlx::postgres::PgRow| row.get::(0)) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 11); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_position_2_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_map_position_3_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT $1::int8") - .bind(13_i64) - .map(|row: sqlx::postgres::PgRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 13); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_position_3_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_try_map_position_3_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT $1::int8") - .bind(17_i64) - .try_map(|row: sqlx::postgres::PgRow| Ok(row.get::(0))) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 17); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_try_map_position_3_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } // --- Per-method on Map (so each forwarder body is hit) -------------------- @@ -2616,100 +780,31 @@ async fn query_try_map_position_3_via_pool() { #[tokio::test] #[serial] async fn map_fetch_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = sqlx::query("SELECT 1::int8 UNION ALL SELECT 2::int8") - .map(|row: sqlx::postgres::PgRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); + test_map_fetch_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn map_fetch_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query("SELECT 1::int8 UNION ALL SELECT 2::int8") - .map(|row: sqlx::postgres::PgRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_many(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_fetch_many_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn map_fetch_all_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows: Vec = - sqlx::query("SELECT 1::int8 UNION ALL SELECT 2::int8 UNION ALL SELECT 3::int8") - .map(|row: sqlx::postgres::PgRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_all(&pool) - .await - .unwrap(); - assert_eq!(rows, vec![1, 2, 3]); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_fetch_all_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn map_fetch_one_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT 19::int8") - .map(|row: sqlx::postgres::PgRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 19); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_fetch_one_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn map_fetch_optional_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: Option = sqlx::query("SELECT 1::int8 WHERE 1 = 0") - .map(|row: sqlx::postgres::PgRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_optional(&pool) - .await - .unwrap(); - assert!(value.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_fetch_optional_with_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } // --- Composition (multi-map; both branches of step 4) -------------------- @@ -2717,41 +812,16 @@ async fn map_fetch_optional_with_annotations_via_pool() { #[tokio::test] #[serial] async fn map_compose_after_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT 5::int8") - .map(|row: sqlx::postgres::PgRow| row.get::(0)) - .with_annotations(test_annotations()) - .map(|n| n * 2) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 10); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_compose_after_annotations_via_pool!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn map_try_map_compose_after_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT 6::int8") - .map(|row: sqlx::postgres::PgRow| row.get::(0)) - .with_annotations(test_annotations()) - .try_map(|n: i64| Ok::<_, sqlx::Error>(n + 100)) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 106); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_try_map_compose_after_annotations_via_pool!( + test_pool().await, + common::POSTGRES_DIALECT + ); } // --- Other executor receivers (smoke) ------------------------------------- @@ -2759,42 +829,13 @@ async fn map_try_map_compose_after_annotations_via_pool() { #[tokio::test] #[serial] async fn query_map_with_annotations_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let value: i64 = sqlx::query("SELECT 23::int8") - .map(|row: sqlx::postgres::PgRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&mut conn) - .await - .unwrap(); - assert_eq!(value, 23); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_with_annotations_via_connection!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_map_with_annotations_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Postgres> = pool.begin().await.unwrap(); - let value: i64 = sqlx::query("SELECT 29::int8") - .map(|row: sqlx::postgres::PgRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&mut tx) - .await - .unwrap(); - assert_eq!(value, 29); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_with_annotations_via_transaction!(test_pool().await, common::POSTGRES_DIALECT); } // --- Error paths ---------------------------------------------------------- @@ -2802,47 +843,16 @@ async fn query_map_with_annotations_via_transaction() { #[tokio::test] #[serial] async fn query_map_with_annotations_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result: Result = sqlx::query("INVALID SQL") - .map(|row: sqlx::postgres::PgRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_error_span(&spans[0]); + test_query_map_with_annotations_records_error!(test_pool().await, common::POSTGRES_DIALECT); } #[tokio::test] #[serial] async fn query_try_map_with_annotations_propagates_mapper_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - // The mapper error fires *after* the database round-trip succeeds – the executor - // sees the row arrive and completes the fetch successfully, then the mapper surfaces - // the error to the caller. The span therefore stays at success at the database - // layer; the contract verified here is that the user-visible Err propagates through - // the wrapper and that the annotations were attached to the (successful) span. - let result: Result = sqlx::query("SELECT 1::int8") - .try_map(|_row: sqlx::postgres::PgRow| { - Err::(sqlx::Error::Decode( - "intentional decode failure".to_string().into(), - )) - }) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_try_map_with_annotations_propagates_mapper_error!( + test_pool().await, + common::POSTGRES_DIALECT + ); } // =========================================================================== @@ -2878,9 +888,7 @@ async fn query_macro_execute_with_annotations_via_pool() { .unwrap(); assert_eq!(result.rows_affected(), 1); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::POSTGRES_DIALECT); } #[tokio::test] @@ -2908,9 +916,7 @@ async fn query_macro_fetch_one_with_annotations_via_pool() { assert_eq!(row.id, 102); assert_eq!(row.name, "bob"); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::POSTGRES_DIALECT); } #[tokio::test] @@ -2941,9 +947,7 @@ async fn query_macro_fetch_all_with_annotations_via_pool() { .unwrap(); assert_eq!(rows.len(), 3); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::POSTGRES_DIALECT); } #[tokio::test] @@ -2964,9 +968,7 @@ async fn query_macro_fetch_optional_with_annotations_via_pool() { .unwrap(); assert!(row.is_none()); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::POSTGRES_DIALECT); } type MacroUser = common::MacroUser; @@ -3000,9 +1002,7 @@ async fn query_as_macro_fetch_one_with_annotations_via_pool() { assert_eq!(user.id, 106); assert_eq!(user.name, "frank"); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::POSTGRES_DIALECT); } #[tokio::test] @@ -3032,9 +1032,7 @@ async fn query_as_macro_fetch_all_with_annotations_via_pool() { .unwrap(); assert_eq!(users.len(), 2); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::POSTGRES_DIALECT); } #[tokio::test] @@ -3059,9 +1057,7 @@ async fn query_as_macro_fetch_optional_with_annotations_via_pool() { .unwrap(); assert!(user.is_none()); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::POSTGRES_DIALECT); } #[tokio::test] @@ -3088,9 +1084,7 @@ async fn query_scalar_macro_fetch_one_with_annotations_via_pool() { .unwrap(); assert_eq!(name, "irene"); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::POSTGRES_DIALECT); } #[tokio::test] @@ -3119,7 +1113,5 @@ async fn query_scalar_macro_fetch_all_with_annotations_via_pool() { .unwrap(); assert_eq!(ids, vec![110, 111]); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::POSTGRES_DIALECT); } diff --git a/tests/sqlite.rs b/tests/sqlite.rs index a3b5873..6af5cfa 100644 --- a/tests/sqlite.rs +++ b/tests/sqlite.rs @@ -2,47 +2,29 @@ mod common; -use common::{assert_common_span_attributes, assert_error_span, attr}; -use futures::StreamExt; -use opentelemetry::trace::SpanKind; +use common::test_annotations; +use futures::StreamExt as _; use serial_test::serial; use sqlx::Executor as _; -use sqlx::Row as _; use sqlx::Sqlite; -use sqlx_otel::{Pool, PoolBuilder, QueryAnnotateExt, QueryAnnotations, Transaction}; +use sqlx_otel::{Pool, PoolBuilder, QueryAnnotateExt}; const SYSTEM: &str = "sqlite"; +/// Backend row type used by parameterised map test macros: `|row| row.get::(idx)`. +/// Each backend defines its own alias so the shared macros can reference `Row` without +/// hardcoding a per-backend SQL row type. +type Row = sqlx::sqlite::SqliteRow; + /// Helper to create an in-memory Sqlite pool wrapped in our instrumented Pool. async fn test_pool() -> Pool { - let raw = sqlx::SqlitePool::connect(":memory:").await.unwrap(); - PoolBuilder::from(raw).build() -} - -/// Standard annotations used across most annotation tests. -fn test_annotations() -> QueryAnnotations { - QueryAnnotations::new() - .operation("SELECT") - .collection("users") + PoolBuilder::from(raw_pool().await).build() } -/// Assert that the span carries the standard annotation attributes set by -/// [`test_annotations`]. -fn assert_annotated_span(span: &opentelemetry_sdk::trace::SpanData) { - assert_eq!(span.span_kind, SpanKind::Client); - assert_eq!(span.name, "SELECT users"); - assert_eq!( - attr(span, "db.system.name"), - Some(opentelemetry::Value::String(SYSTEM.to_owned().into())), - ); - assert_eq!( - attr(span, "db.operation.name"), - Some(opentelemetry::Value::String("SELECT".into())), - ); - assert_eq!( - attr(span, "db.collection.name"), - Some(opentelemetry::Value::String("users".into())), - ); +/// Raw (un-instrumented) sqlx pool, used by parameterised builder / query-text-mode +/// tests that need to apply specific `PoolBuilder` configurations themselves. +async fn raw_pool() -> sqlx::SqlitePool { + sqlx::SqlitePool::connect(":memory:").await.unwrap() } // =========================================================================== @@ -52,226 +34,31 @@ fn assert_annotated_span(span: &opentelemetry_sdk::trace::SpanData) { #[tokio::test] #[serial] async fn execute_creates_span_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - (&pool) - .execute("CREATE TABLE exec_pool (id INTEGER PRIMARY KEY)") - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert!(attr(&spans[0], "db.response.affected_rows").is_some()); - - // With annotations - pool.with_annotations(test_annotations()) - .execute("CREATE TABLE exec_pool2 (id INTEGER PRIMARY KEY)") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .execute("CREATE TABLE exec_pool3 (id INTEGER PRIMARY KEY)") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_creates_span_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn execute_creates_span_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - sqlx::query("CREATE TABLE exec_conn (id INTEGER PRIMARY KEY)") - .execute(&mut conn) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert!(attr(&spans[0], "db.response.affected_rows").is_some()); - - // With annotations - conn.with_annotations(test_annotations()) - .execute("CREATE TABLE exec_conn2 (id INTEGER PRIMARY KEY)") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .execute("CREATE TABLE exec_conn3 (id INTEGER PRIMARY KEY)") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_creates_span_via_connection!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn execute_creates_span_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Sqlite> = pool.begin().await.unwrap(); - sqlx::query("CREATE TABLE exec_tx (id INTEGER PRIMARY KEY)") - .execute(&mut tx) - .await - .unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .execute("CREATE TABLE exec_tx2 (id INTEGER PRIMARY KEY)") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .execute("CREATE TABLE exec_tx3 (id INTEGER PRIMARY KEY)") - .await - .unwrap(); - - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert!(attr(&spans[0], "db.response.affected_rows").is_some()); - assert_annotated_span(&spans[1]); - assert_annotated_span(&spans[2]); + test_execute_creates_span_via_transaction!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn execute_records_affected_rows() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE affected_test (id INTEGER PRIMARY KEY, name TEXT NOT NULL)") - .execute(&pool) - .await - .unwrap(); - tel.reset(); - - // --- Bulk insert via VALUES list --- - sqlx::query( - "INSERT INTO affected_test (id, name) VALUES (1, 'alice'), (2, 'bob'), (3, 'carol')", - ) - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(3)), - "inserting 3 rows in one statement should affect 3 rows" - ); - tel.reset(); - - // --- Upsert (INSERT OR REPLACE) --- - sqlx::query("INSERT OR REPLACE INTO affected_test (id, name) VALUES (1, 'alice_updated')") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(1)), - "upsert should affect 1 row" - ); - tel.reset(); - - // --- Update multiple rows --- - sqlx::query("UPDATE affected_test SET name = name || '_updated' WHERE id IN (2, 3)") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(2)), - "updating two rows should affect 2 rows" - ); - tel.reset(); - - // --- Delete multiple rows --- - sqlx::query("DELETE FROM affected_test WHERE id IN (1, 2, 3)") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(3)), - "deleting three rows should affect 3 rows" - ); - tel.reset(); - - // --- Delete with no matching rows --- - sqlx::query("DELETE FROM affected_test WHERE id = 999") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.response.affected_rows"), - Some(opentelemetry::Value::I64(0)), - "deleting non-existent rows should affect 0 rows" - ); + test_execute_records_affected_rows!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn execute_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = sqlx::query("INVALID SQL GIBBERISH").execute(&pool).await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations (error path) - let result = pool - .with_annotations(test_annotations()) - .execute("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); - - // With shorthand (error path) - let result = pool - .with_operation("SELECT", "users") - .execute("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_execute_records_error!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -281,153 +68,25 @@ async fn execute_records_error() { #[tokio::test] #[serial] async fn execute_many_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - - // With annotations - let mut stream = pool - .with_annotations(test_annotations()) - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = pool - .with_operation("SELECT", "users") - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_many_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn execute_many_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let mut stream = (&mut conn).execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - - // With annotations - let mut stream = conn - .with_annotations(test_annotations()) - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = conn - .with_operation("SELECT", "users") - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_execute_many_via_connection!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn execute_many_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Sqlite> = pool.begin().await.unwrap(); - let mut stream = (&mut tx).execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With annotations - let mut stream = tx - .with_annotations(test_annotations()) - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With shorthand - let mut stream = tx - .with_operation("SELECT", "users") - .execute_many("SELECT 1; SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - assert_annotated_span(&spans[1]); - assert_annotated_span(&spans[2]); + test_execute_many_via_transaction!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn execute_many_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).execute_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - - // With annotations (error path) - let mut stream = pool - .with_annotations(test_annotations()) - .execute_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); - - // With shorthand (error path) - let mut stream = pool - .with_operation("SELECT", "users") - .execute_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_execute_many_records_error!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -437,179 +96,31 @@ async fn execute_many_records_error() { #[tokio::test] #[serial] async fn fetch_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).fetch("SELECT 1 UNION ALL SELECT 2"); - let mut count = 0u64; - while stream.next().await.is_some() { - count += 1; - } - assert_eq!(count, 2); - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - let mut stream = pool - .with_annotations(test_annotations()) - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = pool - .with_operation("SELECT", "users") - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let mut stream = (&mut conn).fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - let mut stream = conn - .with_annotations(test_annotations()) - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = conn - .with_operation("SELECT", "users") - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_via_connection!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Sqlite> = pool.begin().await.unwrap(); - let mut stream = (&mut tx).fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With annotations - let mut stream = tx - .with_annotations(test_annotations()) - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With shorthand - let mut stream = tx - .with_operation("SELECT", "users") - .fetch("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - assert_annotated_span(&spans[1]); - assert_annotated_span(&spans[2]); + test_fetch_via_transaction!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_stream_dropped_early_still_records_span() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - { - let mut stream = (&pool).fetch("SELECT 1 UNION ALL SELECT 2"); - let _ = stream.next().await; - } - - let spans = tel.spans(); - assert_eq!( - spans.len(), - 1, - "span should be recorded even when stream is dropped early" - ); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); + test_fetch_stream_dropped_early_still_records_span!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_stream_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).fetch("INVALID SQL"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - - // With annotations (error path) - let mut stream = pool - .with_annotations(test_annotations()) - .fetch("INVALID SQL"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); - - // With shorthand (error path) - let mut stream = pool.with_operation("SELECT", "users").fetch("INVALID SQL"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_stream_records_error!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -619,187 +130,31 @@ async fn fetch_stream_records_error() { #[tokio::test] #[serial] async fn fetch_many_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).fetch_many("SELECT 1 UNION ALL SELECT 2"); - let mut rows = 0u64; - let mut results = 0u64; - while let Some(item) = stream.next().await { - match item.unwrap() { - sqlx::Either::Left(_) => results += 1, - sqlx::Either::Right(_) => rows += 1, - } - } - drop(stream); - - assert_eq!(rows, 2); - assert!(results >= 1, "should have at least one QueryResult"); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - let mut stream = pool - .with_annotations(test_annotations()) - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = pool - .with_operation("SELECT", "users") - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_many_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_many_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let mut stream = (&mut conn).fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - let mut stream = conn - .with_annotations(test_annotations()) - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - let mut stream = conn - .with_operation("SELECT", "users") - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_many_via_connection!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_many_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Sqlite> = pool.begin().await.unwrap(); - let mut stream = (&mut tx).fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With annotations - let mut stream = tx - .with_annotations(test_annotations()) - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - // With shorthand - let mut stream = tx - .with_operation("SELECT", "users") - .fetch_many("SELECT 1 UNION ALL SELECT 2"); - while stream.next().await.is_some() {} - drop(stream); - - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - assert_annotated_span(&spans[1]); - assert_annotated_span(&spans[2]); + test_fetch_many_via_transaction!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_many_dropped_early_still_records_span() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - { - let mut stream = (&pool).fetch_many("SELECT 1 UNION ALL SELECT 2"); - let _ = stream.next().await; - } - - let spans = tel.spans(); - assert_eq!( - spans.len(), - 1, - "span should be recorded even when stream is dropped early" - ); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); + test_fetch_many_dropped_early_still_records_span!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_many_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = (&pool).fetch_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); - - // With annotations (error path) - let mut stream = pool - .with_annotations(test_annotations()) - .fetch_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); - - // With shorthand (error path) - let mut stream = pool - .with_operation("SELECT", "users") - .fetch_many("INVALID SQL GIBBERISH"); - let result = stream.next().await; - assert!(result.is_some_and(|r| r.is_err())); - drop(stream); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_many_records_error!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -808,147 +163,26 @@ async fn fetch_many_records_error() { #[tokio::test] #[serial] -async fn fetch_all_records_row_count() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows = (&pool) - .fetch_all("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .await - .unwrap(); - assert_eq!(rows.len(), 3); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(3)) - ); - - // With annotations - pool.with_annotations(test_annotations()) - .fetch_all("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .fetch_all("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); +async fn fetch_all_via_pool() { + test_fetch_all_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_all_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let rows = (&mut conn) - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - assert_eq!(rows.len(), 2); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - - // With annotations - conn.with_annotations(test_annotations()) - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_all_via_connection!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_all_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Sqlite> = pool.begin().await.unwrap(); - let rows = (&mut tx) - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - assert_eq!(rows.len(), 2); - - // With annotations - tx.with_annotations(test_annotations()) - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .fetch_all("SELECT 1 UNION ALL SELECT 2") - .await - .unwrap(); - - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); - assert_annotated_span(&spans[1]); - assert_annotated_span(&spans[2]); + test_fetch_all_via_transaction!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_all_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = (&pool).fetch_all("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations (error path) - let result = pool - .with_annotations(test_annotations()) - .fetch_all("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); - - // With shorthand (error path) - let result = pool - .with_operation("SELECT", "users") - .fetch_all("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_all_records_error!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -958,134 +192,25 @@ async fn fetch_all_records_error() { #[tokio::test] #[serial] async fn fetch_one_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _row = (&pool).fetch_one("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - - // With annotations - pool.with_annotations(test_annotations()) - .fetch_one("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .fetch_one("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_one_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_one_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let _row = (&mut conn).fetch_one("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - - // With annotations - conn.with_annotations(test_annotations()) - .fetch_one("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .fetch_one("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_one_via_connection!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_one_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Sqlite> = pool.begin().await.unwrap(); - let _row = (&mut tx).fetch_one("SELECT 1").await.unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .fetch_one("SELECT 1") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .fetch_one("SELECT 1") - .await - .unwrap(); - - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - assert_annotated_span(&spans[1]); - assert_annotated_span(&spans[2]); + test_fetch_one_via_transaction!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_one_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = (&pool).fetch_one("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations (error path) - let result = pool - .with_annotations(test_annotations()) - .fetch_one("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); - - // With shorthand (error path) - let result = pool - .with_operation("SELECT", "users") - .fetch_one("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_one_records_error!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -1095,167 +220,31 @@ async fn fetch_one_records_error() { #[tokio::test] #[serial] async fn fetch_optional_records_one_row() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = (&pool).fetch_optional("SELECT 1").await.unwrap(); - assert!(result.is_some()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - - // With annotations - pool.with_annotations(test_annotations()) - .fetch_optional("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .fetch_optional("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_optional_records_one_row!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_optional_records_zero_rows() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE empty_table (id INTEGER PRIMARY KEY)") - .execute(&pool) - .await - .unwrap(); - - let result = (&pool) - .fetch_optional("SELECT id FROM empty_table") - .await - .unwrap(); - assert!(result.is_none()); - - let spans = tel.spans(); - let select_span = spans - .iter() - .find(|s| attr(s, "db.query.text").is_some_and(|v| v.to_string().contains("SELECT"))); - assert!(select_span.is_some()); - let select_span = select_span.unwrap(); - assert_common_span_attributes(select_span, SYSTEM); - assert_eq!( - attr(select_span, "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); + test_fetch_optional_records_zero_rows!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_optional_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let result = (&mut conn).fetch_optional("SELECT 42").await.unwrap(); - assert!(result.is_some()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - - // With annotations - conn.with_annotations(test_annotations()) - .fetch_optional("SELECT 42") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .fetch_optional("SELECT 42") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_fetch_optional_via_connection!(test_pool().await, common::SQLITE_DIALECT); } - -#[tokio::test] -#[serial] -async fn fetch_optional_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Sqlite> = pool.begin().await.unwrap(); - let result = (&mut tx).fetch_optional("SELECT 99").await.unwrap(); - assert!(result.is_some()); - - // With annotations - tx.with_annotations(test_annotations()) - .fetch_optional("SELECT 99") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .fetch_optional("SELECT 99") - .await - .unwrap(); - - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); - assert_annotated_span(&spans[1]); - assert_annotated_span(&spans[2]); + +#[tokio::test] +#[serial] +async fn fetch_optional_via_transaction() { + test_fetch_optional_via_transaction!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn fetch_optional_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = (&pool).fetch_optional("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert_eq!(attr(&spans[0], "db.response.returned_rows"), None); - - // With annotations (error path) - let result = pool - .with_annotations(test_annotations()) - .fetch_optional("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); - - // With shorthand (error path) - let result = pool - .with_operation("SELECT", "users") - .fetch_optional("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_fetch_optional_records_error!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -1265,126 +254,25 @@ async fn fetch_optional_records_error() { #[tokio::test] #[serial] async fn prepare_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _stmt = (&pool).prepare("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - pool.with_annotations(test_annotations()) - .prepare("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .prepare("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn prepare_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let _stmt = (&mut conn).prepare("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - conn.with_annotations(test_annotations()) - .prepare("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .prepare("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_via_connection!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn prepare_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Sqlite> = pool.begin().await.unwrap(); - let _stmt = (&mut tx).prepare("SELECT 1").await.unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .prepare("SELECT 1") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .prepare("SELECT 1") - .await - .unwrap(); - - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_annotated_span(&spans[1]); - assert_annotated_span(&spans[2]); + test_prepare_via_transaction!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn prepare_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let result = (&mut conn).prepare("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations (error path) - let result = conn - .with_annotations(test_annotations()) - .prepare("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); - - // With shorthand (error path) - let result = conn - .with_operation("SELECT", "users") - .prepare("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_prepare_records_error!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -1394,126 +282,25 @@ async fn prepare_records_error() { #[tokio::test] #[serial] async fn prepare_with_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _stmt = (&pool).prepare_with("SELECT ?", &[]).await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - pool.with_annotations(test_annotations()) - .prepare_with("SELECT ?", &[]) - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .prepare_with("SELECT ?", &[]) - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_with_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn prepare_with_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let _stmt = (&mut conn).prepare_with("SELECT ?", &[]).await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - conn.with_annotations(test_annotations()) - .prepare_with("SELECT ?", &[]) - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .prepare_with("SELECT ?", &[]) - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_prepare_with_via_connection!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn prepare_with_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Sqlite> = pool.begin().await.unwrap(); - let _stmt = (&mut tx).prepare_with("SELECT ?", &[]).await.unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .prepare_with("SELECT ?", &[]) - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .prepare_with("SELECT ?", &[]) - .await - .unwrap(); - - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_annotated_span(&spans[1]); - assert_annotated_span(&spans[2]); + test_prepare_with_via_transaction!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn prepare_with_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let result = (&mut conn).prepare_with("INVALID SQL GIBBERISH", &[]).await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations (error path) - let result = conn - .with_annotations(test_annotations()) - .prepare_with("INVALID SQL GIBBERISH", &[]) - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); - - // With shorthand (error path) - let result = conn - .with_operation("SELECT", "users") - .prepare_with("INVALID SQL GIBBERISH", &[]) - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_prepare_with_records_error!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -1523,126 +310,25 @@ async fn prepare_with_records_error() { #[tokio::test] #[serial] async fn describe_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _desc = (&pool).describe("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - pool.with_annotations(test_annotations()) - .describe("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - pool.with_operation("SELECT", "users") - .describe("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_describe_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn describe_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let _desc = (&mut conn).describe("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations - conn.with_annotations(test_annotations()) - .describe("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); - - // With shorthand - conn.with_operation("SELECT", "users") - .describe("SELECT 1") - .await - .unwrap(); - assert_annotated_span(tel.spans().last().unwrap()); + test_describe_via_connection!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn describe_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Sqlite> = pool.begin().await.unwrap(); - let _desc = (&mut tx).describe("SELECT 1").await.unwrap(); - - // With annotations - tx.with_annotations(test_annotations()) - .describe("SELECT 1") - .await - .unwrap(); - - // With shorthand - tx.with_operation("SELECT", "users") - .describe("SELECT 1") - .await - .unwrap(); - - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 3); - assert_common_span_attributes(&spans[0], SYSTEM); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - assert_annotated_span(&spans[1]); - assert_annotated_span(&spans[2]); + test_describe_via_transaction!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn describe_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let result = (&mut conn).describe("INVALID SQL GIBBERISH").await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); - assert_error_span(&spans[0]); - assert!(attr(&spans[0], "db.response.returned_rows").is_none()); - - // With annotations (error path) - let result = conn - .with_annotations(test_annotations()) - .describe("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); - - // With shorthand (error path) - let result = conn - .with_operation("SELECT", "users") - .describe("INVALID SQL GIBBERISH") - .await; - assert!(result.is_err()); - let last = tel.spans().last().unwrap().clone(); - assert_annotated_span(&last); - assert_error_span(&last); + test_describe_records_error!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -1652,42 +338,7 @@ async fn describe_records_error() { #[tokio::test] #[serial] async fn operation_duration_metric_is_recorded() { - use opentelemetry_sdk::metrics::data::{AggregatedMetrics, MetricData}; - - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _: (i32,) = sqlx::query_as("SELECT 1").fetch_one(&pool).await.unwrap(); - - let resource_metrics = tel.metrics(); - assert!(!resource_metrics.is_empty(), "should have metric data"); - - let mut found_duration = false; - for rm in &resource_metrics { - for sm in rm.scope_metrics() { - for metric in sm.metrics() { - if metric.name() == "db.client.operation.duration" { - found_duration = true; - assert_eq!(metric.unit(), "s"); - if let AggregatedMetrics::F64(MetricData::Histogram(hist)) = metric.data() { - let dp: Vec<_> = hist.data_points().collect(); - assert!(!dp.is_empty(), "histogram should have data points"); - assert!(dp[0].count() > 0, "data point count should be > 0"); - let has_system = dp[0] - .attributes() - .any(|kv| kv.key.as_str() == "db.system.name"); - assert!(has_system, "metric should have db.system.name attribute"); - } else { - panic!("db.client.operation.duration should be an f64 histogram"); - } - } - } - } - } - assert!( - found_duration, - "db.client.operation.duration metric not found" - ); + test_operation_duration_metric_is_recorded!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -1697,57 +348,13 @@ async fn operation_duration_metric_is_recorded() { #[tokio::test] #[serial] async fn query_text_mode_off_suppresses_sql() { - let tel = common::TestTelemetry::install(); - let raw = sqlx::SqlitePool::connect(":memory:").await.unwrap(); - let pool = PoolBuilder::from(raw) - .with_query_text_mode(sqlx_otel::QueryTextMode::Off) - .build(); - - let _: Option<(i32,)> = sqlx::query_as("SELECT 1") - .fetch_optional(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!(spans[0].span_kind, SpanKind::Client); - assert_eq!( - attr(&spans[0], "db.system.name"), - Some(opentelemetry::Value::String(SYSTEM.into())) - ); - assert!(attr(&spans[0], "db.namespace").is_some()); - assert!( - attr(&spans[0], "db.query.text").is_none(), - "db.query.text should not be present when QueryTextMode::Off" - ); + test_query_text_mode_off_suppresses_sql!(raw_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_text_mode_obfuscated_replaces_literals() { - let tel = common::TestTelemetry::install(); - let raw = sqlx::SqlitePool::connect(":memory:").await.unwrap(); - let pool = PoolBuilder::from(raw) - .with_query_text_mode(sqlx_otel::QueryTextMode::Obfuscated) - .build(); - - sqlx::query("CREATE TABLE t (id INTEGER, name TEXT)") - .execute(&pool) - .await - .unwrap(); - sqlx::query("INSERT INTO t (id, name) VALUES (1, 'alice')") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 2); - assert_eq!( - attr(&spans[1], "db.query.text"), - Some(opentelemetry::Value::String( - "INSERT INTO t (id, name) VALUES (?, ?)".into() - )) - ); + test_query_text_mode_obfuscated_replaces_literals!(raw_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -1757,19 +364,7 @@ async fn query_text_mode_obfuscated_replaces_literals() { #[tokio::test] #[serial] async fn transaction_rollback() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Sqlite> = pool.begin().await.unwrap(); - sqlx::query("CREATE TABLE rollback_test (id INTEGER PRIMARY KEY)") - .execute(&mut tx) - .await - .unwrap(); - tx.rollback().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_common_span_attributes(&spans[0], SYSTEM); + test_transaction_rollback!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -1779,88 +374,31 @@ async fn transaction_rollback() { #[tokio::test] #[serial] async fn builder_with_database_overrides_namespace() { - let tel = common::TestTelemetry::install(); - let raw = sqlx::SqlitePool::connect(":memory:").await.unwrap(); - let pool = PoolBuilder::from(raw).with_database("custom_db").build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "db.namespace"), - Some(opentelemetry::Value::String("custom_db".into())) - ); + test_builder_with_database_overrides_namespace!(raw_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn builder_with_host_overrides_server_address() { - let tel = common::TestTelemetry::install(); - let raw = sqlx::SqlitePool::connect(":memory:").await.unwrap(); - let pool = PoolBuilder::from(raw).with_host("custom-host").build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "server.address"), - Some(opentelemetry::Value::String("custom-host".into())) - ); + test_builder_with_host_overrides_server_address!(raw_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn builder_with_port_overrides_server_port() { - let tel = common::TestTelemetry::install(); - let raw = sqlx::SqlitePool::connect(":memory:").await.unwrap(); - let pool = PoolBuilder::from(raw).with_port(9999).build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "server.port"), - Some(opentelemetry::Value::I64(9999)) - ); + test_builder_with_port_overrides_server_port!(raw_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn builder_with_network_peer_address() { - let tel = common::TestTelemetry::install(); - let raw = sqlx::SqlitePool::connect(":memory:").await.unwrap(); - let pool = PoolBuilder::from(raw) - .with_network_peer_address("10.0.0.5") - .build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "network.peer.address"), - Some(opentelemetry::Value::String("10.0.0.5".into())) - ); + test_builder_with_network_peer_address!(raw_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn builder_with_network_peer_port() { - let tel = common::TestTelemetry::install(); - let raw = sqlx::SqlitePool::connect(":memory:").await.unwrap(); - let pool = PoolBuilder::from(raw).with_network_peer_port(5433).build(); - - let _ = (&pool).fetch_optional("SELECT 1").await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!( - attr(&spans[0], "network.peer.port"), - Some(opentelemetry::Value::I64(5433)) - ); + test_builder_with_network_peer_port!(raw_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -1870,12 +408,7 @@ async fn builder_with_network_peer_port() { #[tokio::test] #[serial] async fn pool_close_and_is_closed() { - let _tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - assert!(!pool.is_closed()); - pool.close().await; - assert!(pool.is_closed()); + test_pool_close_and_is_closed!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -1885,75 +418,13 @@ async fn pool_close_and_is_closed() { #[tokio::test] #[serial] async fn annotation_all_four_fields() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - pool.with_annotations( - QueryAnnotations::new() - .operation("SELECT") - .collection("users") - .query_summary("users by id") - .stored_procedure("sp_get_users"), - ) - .fetch_all("SELECT 1") - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - // Summary drives the span name (semconv level 1), distinct from "SELECT users" so - // the assertion proves the summary path won rather than coinciding with level 2. - assert_eq!(spans[0].name, "users by id"); - assert_eq!( - attr(&spans[0], "db.operation.name"), - Some(opentelemetry::Value::String("SELECT".into())), - ); - assert_eq!( - attr(&spans[0], "db.collection.name"), - Some(opentelemetry::Value::String("users".into())), - ); - assert_eq!( - attr(&spans[0], "db.query.summary"), - Some(opentelemetry::Value::String("users by id".into())), - ); - assert_eq!( - attr(&spans[0], "db.stored_procedure.name"), - Some(opentelemetry::Value::String("sp_get_users".into())), - ); + test_annotation_all_four_fields!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_summary_drives_span_name() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - pool.with_annotations( - QueryAnnotations::new() - .operation("SELECT") - .collection("users") - .query_summary("users by tenant"), - ) - .fetch_all("SELECT 1") - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_eq!(spans[0].name, "users by tenant"); - // Summary drives the *name*, but does not suppress the other attributes. - assert_eq!( - attr(&spans[0], "db.query.summary"), - Some(opentelemetry::Value::String("users by tenant".into())), - ); - assert_eq!( - attr(&spans[0], "db.operation.name"), - Some(opentelemetry::Value::String("SELECT".into())), - ); - assert_eq!( - attr(&spans[0], "db.collection.name"), - Some(opentelemetry::Value::String("users".into())), - ); + test_query_summary_drives_span_name!(test_pool().await, common::SQLITE_DIALECT); } // =========================================================================== @@ -1963,263 +434,79 @@ async fn query_summary_drives_span_name() { #[tokio::test] #[serial] async fn query_execute_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE qe_pool (id INTEGER PRIMARY KEY)") - .with_annotations(test_annotations()) - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert!(attr(&spans[0], "db.response.affected_rows").is_some()); + test_query_execute_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_execute_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query("SELECT 1; SELECT 2") - .with_annotations(test_annotations()) - .execute_many(&pool) - .await; - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_execute_many_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_fetch_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); + test_query_fetch_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_fetch_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_many(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); -} - -#[tokio::test] -#[serial] -async fn query_fetch_all_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows = sqlx::query("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .with_annotations(test_annotations()) - .fetch_all(&pool) - .await - .unwrap(); - assert_eq!(rows.len(), 3); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(3)) - ); + test_query_fetch_many_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] -async fn query_fetch_one_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let _row = sqlx::query("SELECT 1") - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); +async fn query_fetch_all_with_annotations_via_pool() { + test_query_fetch_all_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); +} - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(1)) - ); +#[tokio::test] +#[serial] +async fn query_fetch_one_with_annotations_via_pool() { + test_query_fetch_one_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_fetch_optional_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE qfo_pool (id INTEGER PRIMARY KEY)") - .execute(&pool) - .await - .unwrap(); - - tel.reset(); - - let row = sqlx::query("SELECT id FROM qfo_pool WHERE id = 1") - .with_annotations(test_annotations()) - .fetch_optional(&pool) - .await - .unwrap(); - assert!(row.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(0)) - ); + test_query_fetch_optional_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_bind_first_then_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let row = sqlx::query("SELECT ?1 + ?2 AS sum") - .bind(2_i32) - .bind(3_i32) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - let sum: i32 = row.try_get("sum").unwrap(); - assert_eq!(sum, 5); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_bind_first_then_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_annotations_first_then_bind_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let row = sqlx::query("SELECT ?1 + ?2 AS sum") - .with_annotations(test_annotations()) - .bind(10_i32) - .bind(20_i32) - .fetch_one(&pool) - .await - .unwrap(); - let sum: i32 = row.try_get("sum").unwrap(); - assert_eq!(sum, 30); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_annotations_first_then_bind_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_with_operation_shorthand_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - sqlx::query("CREATE TABLE qop_pool (id INTEGER PRIMARY KEY)") - .with_operation("SELECT", "users") - .execute(&pool) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_with_operation_shorthand_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_execute_with_annotations_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - sqlx::query("CREATE TABLE qe_conn (id INTEGER PRIMARY KEY)") - .with_annotations(test_annotations()) - .execute(&mut conn) - .await - .unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_execute_with_annotations_via_connection!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_execute_with_annotations_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Sqlite> = pool.begin().await.unwrap(); - sqlx::query("CREATE TABLE qe_tx (id INTEGER PRIMARY KEY)") - .with_annotations(test_annotations()) - .execute(&mut tx) - .await - .unwrap(); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_execute_with_annotations_via_transaction!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_execute_with_annotations_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result = sqlx::query("INVALID SQL GIBBERISH") - .with_annotations(test_annotations()) - .execute(&pool) - .await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_error_span(&spans[0]); + test_query_execute_with_annotations_records_error!(test_pool().await, common::SQLITE_DIALECT); } // --- query_as side --------------------------------------------------------- @@ -2227,108 +514,43 @@ async fn query_execute_with_annotations_records_error() { #[tokio::test] #[serial] async fn query_as_fetch_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = sqlx::query_as::<_, (i32,)>("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_as_fetch_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_as_fetch_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query_as::<_, (i32,)>("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_many(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_as_fetch_many_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_as_fetch_all_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows: Vec<(i32,)> = sqlx::query_as("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_all(&pool) - .await - .unwrap(); - assert_eq!(rows.len(), 2); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_as_fetch_all_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_as_fetch_one_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let row: (i32,) = sqlx::query_as("SELECT 7") - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(row.0, 7); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_as_fetch_one_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_as_fetch_optional_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let row: Option<(i32,)> = sqlx::query_as("SELECT 1 WHERE 1 = 0") - .with_annotations(test_annotations()) - .fetch_optional(&pool) - .await - .unwrap(); - assert!(row.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_as_fetch_optional_with_annotations_via_pool!( + test_pool().await, + common::SQLITE_DIALECT + ); } #[tokio::test] #[serial] async fn query_as_fetch_one_with_annotations_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result: Result<(i32,), _> = sqlx::query_as("INVALID SQL") - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_error_span(&spans[0]); + test_query_as_fetch_one_with_annotations_records_error!( + test_pool().await, + common::SQLITE_DIALECT + ); } // --- query_scalar side ----------------------------------------------------- @@ -2336,90 +558,43 @@ async fn query_as_fetch_one_with_annotations_records_error() { #[tokio::test] #[serial] async fn query_scalar_fetch_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = sqlx::query_scalar::<_, i32>("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_scalar_fetch_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query_scalar::<_, i32>("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_many(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_many_with_annotations_via_pool!( + test_pool().await, + common::SQLITE_DIALECT + ); } #[tokio::test] #[serial] async fn query_scalar_fetch_all_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows: Vec = sqlx::query_scalar("SELECT 1 UNION ALL SELECT 2") - .with_annotations(test_annotations()) - .fetch_all(&pool) - .await - .unwrap(); - assert_eq!(rows, vec![1, 2]); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_all_with_annotations_via_pool!( + test_pool().await, + common::SQLITE_DIALECT + ); } #[tokio::test] #[serial] async fn query_scalar_fetch_one_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i32 = sqlx::query_scalar("SELECT 42") - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 42); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_one_with_annotations_via_pool!( + test_pool().await, + common::SQLITE_DIALECT + ); } #[tokio::test] #[serial] async fn query_scalar_fetch_optional_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: Option = sqlx::query_scalar("SELECT 1 WHERE 1 = 0") - .with_annotations(test_annotations()) - .fetch_optional(&pool) - .await - .unwrap(); - assert!(value.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_scalar_fetch_optional_with_annotations_via_pool!( + test_pool().await, + common::SQLITE_DIALECT + ); } // =========================================================================== @@ -2431,81 +606,25 @@ async fn query_scalar_fetch_optional_with_annotations_via_pool() { #[tokio::test] #[serial] async fn query_map_position_1_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT ?1") - .with_annotations(test_annotations()) - .bind(7_i64) - .map(|row: sqlx::sqlite::SqliteRow| row.get::(0)) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 7); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_position_1_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_map_position_2_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT ?1") - .bind(11_i64) - .with_annotations(test_annotations()) - .map(|row: sqlx::sqlite::SqliteRow| row.get::(0)) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 11); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_position_2_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_map_position_3_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT ?1") - .bind(13_i64) - .map(|row: sqlx::sqlite::SqliteRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 13); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_position_3_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_try_map_position_3_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT ?1") - .bind(17_i64) - .try_map(|row: sqlx::sqlite::SqliteRow| Ok(row.get::(0))) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 17); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_try_map_position_3_via_pool!(test_pool().await, common::SQLITE_DIALECT); } // --- Per-method on Map (so each forwarder body is hit) -------------------- @@ -2513,99 +632,31 @@ async fn query_try_map_position_3_via_pool() { #[tokio::test] #[serial] async fn map_fetch_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") - .map(|row: sqlx::sqlite::SqliteRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_eq!( - attr(&spans[0], "db.response.returned_rows"), - Some(opentelemetry::Value::I64(2)) - ); + test_map_fetch_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn map_fetch_many_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - #[allow(deprecated)] - let mut stream = sqlx::query("SELECT 1 UNION ALL SELECT 2") - .map(|row: sqlx::sqlite::SqliteRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_many(&pool); - while stream.next().await.is_some() {} - drop(stream); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_fetch_many_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn map_fetch_all_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let rows: Vec = sqlx::query("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3") - .map(|row: sqlx::sqlite::SqliteRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_all(&pool) - .await - .unwrap(); - assert_eq!(rows, vec![1, 2, 3]); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_fetch_all_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn map_fetch_one_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT 19") - .map(|row: sqlx::sqlite::SqliteRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 19); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_fetch_one_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn map_fetch_optional_with_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: Option = sqlx::query("SELECT 1 WHERE 1 = 0") - .map(|row: sqlx::sqlite::SqliteRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_optional(&pool) - .await - .unwrap(); - assert!(value.is_none()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_fetch_optional_with_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } // --- Composition (multi-map; both branches of step 4) -------------------- @@ -2613,41 +664,13 @@ async fn map_fetch_optional_with_annotations_via_pool() { #[tokio::test] #[serial] async fn map_compose_after_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT 5") - .map(|row: sqlx::sqlite::SqliteRow| row.get::(0)) - .with_annotations(test_annotations()) - .map(|n| n * 2) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 10); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_compose_after_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn map_try_map_compose_after_annotations_via_pool() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let value: i64 = sqlx::query("SELECT 6") - .map(|row: sqlx::sqlite::SqliteRow| row.get::(0)) - .with_annotations(test_annotations()) - .try_map(|n: i64| Ok::<_, sqlx::Error>(n + 100)) - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(value, 106); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_map_try_map_compose_after_annotations_via_pool!(test_pool().await, common::SQLITE_DIALECT); } // --- Other executor receivers (smoke) ------------------------------------- @@ -2655,42 +678,13 @@ async fn map_try_map_compose_after_annotations_via_pool() { #[tokio::test] #[serial] async fn query_map_with_annotations_via_connection() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut conn = pool.acquire().await.unwrap(); - let value: i64 = sqlx::query("SELECT 23") - .map(|row: sqlx::sqlite::SqliteRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&mut conn) - .await - .unwrap(); - assert_eq!(value, 23); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_with_annotations_via_connection!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_map_with_annotations_via_transaction() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let mut tx: Transaction<'_, Sqlite> = pool.begin().await.unwrap(); - let value: i64 = sqlx::query("SELECT 29") - .map(|row: sqlx::sqlite::SqliteRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&mut tx) - .await - .unwrap(); - assert_eq!(value, 29); - tx.commit().await.unwrap(); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_map_with_annotations_via_transaction!(test_pool().await, common::SQLITE_DIALECT); } // --- Error paths ---------------------------------------------------------- @@ -2698,48 +692,16 @@ async fn query_map_with_annotations_via_transaction() { #[tokio::test] #[serial] async fn query_map_with_annotations_records_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - let result: Result = sqlx::query("INVALID SQL") - .map(|row: sqlx::sqlite::SqliteRow| row.get::(0)) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); - assert_error_span(&spans[0]); + test_query_map_with_annotations_records_error!(test_pool().await, common::SQLITE_DIALECT); } #[tokio::test] #[serial] async fn query_try_map_with_annotations_propagates_mapper_error() { - let tel = common::TestTelemetry::install(); - let pool = test_pool().await; - - // A `try_map` closure that fails on an otherwise-valid row produces a user-visible - // error, but it happens *after* the database round-trip has already succeeded – the - // executor sees the row arrive, completes the fetch, and only then does the mapper - // surface the error. The span therefore reports success at the database layer; the - // important contract here is that the user-visible Err carries through and that the - // annotations were attached to the (successful) span. - let result: Result = sqlx::query("SELECT 1") - .try_map(|_row: sqlx::sqlite::SqliteRow| { - Err::(sqlx::Error::Decode( - "intentional decode failure".to_string().into(), - )) - }) - .with_annotations(test_annotations()) - .fetch_one(&pool) - .await; - assert!(result.is_err()); - - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + test_query_try_map_with_annotations_propagates_mapper_error!( + test_pool().await, + common::SQLITE_DIALECT + ); } // =========================================================================== @@ -2777,9 +739,7 @@ async fn query_macro_execute_with_annotations_via_pool() { .unwrap(); assert_eq!(result.rows_affected(), 1); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::SQLITE_DIALECT); } #[tokio::test] @@ -2805,9 +765,7 @@ async fn query_macro_fetch_one_with_annotations_via_pool() { assert_eq!(row.id, 2); assert_eq!(row.name, "bob"); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::SQLITE_DIALECT); } #[tokio::test] @@ -2836,9 +794,7 @@ async fn query_macro_fetch_all_with_annotations_via_pool() { .unwrap(); assert_eq!(rows.len(), 3); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::SQLITE_DIALECT); } #[tokio::test] @@ -2859,9 +815,7 @@ async fn query_macro_fetch_optional_with_annotations_via_pool() { .unwrap(); assert!(row.is_none()); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::SQLITE_DIALECT); } type MacroUser = common::MacroUser; @@ -2893,9 +847,7 @@ async fn query_as_macro_fetch_one_with_annotations_via_pool() { assert_eq!(user.id, 6); assert_eq!(user.name, "frank"); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::SQLITE_DIALECT); } #[tokio::test] @@ -2925,9 +877,7 @@ async fn query_as_macro_fetch_all_with_annotations_via_pool() { .unwrap(); assert_eq!(users.len(), 2); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::SQLITE_DIALECT); } #[tokio::test] @@ -2952,9 +902,7 @@ async fn query_as_macro_fetch_optional_with_annotations_via_pool() { .unwrap(); assert!(user.is_none()); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::SQLITE_DIALECT); } #[tokio::test] @@ -2979,9 +927,7 @@ async fn query_scalar_macro_fetch_one_with_annotations_via_pool() { .unwrap(); assert_eq!(name, "irene"); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::SQLITE_DIALECT); } #[tokio::test] @@ -3010,9 +956,7 @@ async fn query_scalar_macro_fetch_all_with_annotations_via_pool() { .unwrap(); assert_eq!(ids, vec![10, 11]); - let spans = tel.spans(); - assert_eq!(spans.len(), 1); - assert_annotated_span(&spans[0]); + common::assert_one_annotated_span(&tel, &common::SQLITE_DIALECT); } // ===========================================================================