Skip to main content

sqlx_otel/
database.rs

1/// Per-backend contract providing the database system name and a method to extract
2/// connection-level attributes from the backend's connect options.
3///
4/// Each supported `SQLx` backend (Postgres, Sqlite, Mysql) implements this trait behind its
5/// corresponding feature flag. The trait is intentionally minimal – it exists solely to
6/// let the generic wrapper types resolve connection attributes once at pool construction
7/// time.
8pub trait Database: sqlx::Database {
9    /// The OpenTelemetry `db.system.name` value for this backend (e.g. `"postgresql"`,
10    /// `"sqlite"`, `"mysql"`).
11    const SYSTEM: &'static str;
12
13    /// Extract host, port, and database namespace from the backend's connect options.
14    ///
15    /// Returns `(host, port, namespace)` where any component may be `None` if the backend
16    /// does not support it (e.g. Sqlite has no host or port).
17    fn connection_attributes(
18        pool: &sqlx::Pool<Self>,
19    ) -> (Option<String>, Option<u16>, Option<String>);
20
21    /// Extract the number of rows affected from a `QueryResult`.
22    ///
23    /// Each `SQLx` backend defines its own `QueryResult` type with an inherent
24    /// `rows_affected()` method. This trait method provides a uniform interface for the
25    /// instrumentation layer.
26    fn rows_affected(result: &<Self as sqlx::Database>::QueryResult) -> u64;
27}
28
29/// Extract `(host, port, namespace)` from a network-style backend's connect options by
30/// rendering them to a URL and parsing the components.
31///
32/// Used by the Postgres and `MySQL` impls of [`Database::connection_attributes`] – both
33/// share the same URL-based extraction logic. `SQLite` supplies a filename instead and
34/// does not need this helper.
35#[cfg(any(feature = "postgres", feature = "mysql"))]
36fn url_based_connection_attributes<O: sqlx::ConnectOptions>(
37    options: &O,
38) -> (Option<String>, Option<u16>, Option<String>) {
39    let url = options.to_url_lossy();
40    let host = url.host_str().map(String::from);
41    let port = url.port();
42    let namespace = url
43        .path_segments()
44        .and_then(|mut segments| segments.next().map(String::from));
45    (host, port, namespace)
46}
47
48#[cfg(feature = "sqlite")]
49impl Database for sqlx::Sqlite {
50    const SYSTEM: &'static str = "sqlite";
51
52    fn connection_attributes(
53        pool: &sqlx::Pool<Self>,
54    ) -> (Option<String>, Option<u16>, Option<String>) {
55        let namespace = pool
56            .connect_options()
57            .get_filename()
58            .to_str()
59            .map(String::from);
60        (None, None, namespace)
61    }
62
63    fn rows_affected(result: &sqlx::sqlite::SqliteQueryResult) -> u64 {
64        result.rows_affected()
65    }
66}
67
68#[cfg(feature = "postgres")]
69impl Database for sqlx::Postgres {
70    const SYSTEM: &'static str = "postgresql";
71
72    fn connection_attributes(
73        pool: &sqlx::Pool<Self>,
74    ) -> (Option<String>, Option<u16>, Option<String>) {
75        url_based_connection_attributes(pool.connect_options().as_ref())
76    }
77
78    fn rows_affected(result: &sqlx::postgres::PgQueryResult) -> u64 {
79        result.rows_affected()
80    }
81}
82
83#[cfg(feature = "mysql")]
84impl Database for sqlx::MySql {
85    const SYSTEM: &'static str = "mysql";
86
87    fn connection_attributes(
88        pool: &sqlx::Pool<Self>,
89    ) -> (Option<String>, Option<u16>, Option<String>) {
90        url_based_connection_attributes(pool.connect_options().as_ref())
91    }
92
93    fn rows_affected(result: &sqlx::mysql::MySqlQueryResult) -> u64 {
94        result.rows_affected()
95    }
96}