Skip to main content

sqlx_otel/
database.rs

1/// Per-backend contract providing the database system name, connect-attribute extraction,
2/// and `rows_affected` projection.
3///
4/// Implemented by this crate for [`sqlx::Sqlite`], [`sqlx::Postgres`], and [`sqlx::MySql`]
5/// behind their respective feature flags. The trait exists so the generic wrapper types
6/// (`Pool`, `PoolConnection`, `Transaction`) can resolve connection attributes once at
7/// pool construction and project `rows_affected` from the per-backend `QueryResult` types.
8///
9/// **The trait is sealed.** It is an internal generic-dispatch contract, not an extension
10/// point. Additional backends would require both an upstream `sqlx::Database` impl and a release
11/// of this crate.
12///
13/// [`sqlx::Sqlite`]: https://docs.rs/sqlx/latest/sqlx/struct.Sqlite.html
14/// [`sqlx::Postgres`]: https://docs.rs/sqlx/latest/sqlx/struct.Postgres.html
15/// [`sqlx::MySql`]: https://docs.rs/sqlx/latest/sqlx/struct.MySql.html
16pub trait Database: sqlx::Database + sealed::Sealed {
17    /// The OpenTelemetry `db.system.name` value for this backend (e.g. `"postgresql"`,
18    /// `"sqlite"`, `"mysql"`).
19    const SYSTEM: &'static str;
20
21    /// Extract host, port, and database namespace from the backend's connect options.
22    ///
23    /// Returns `(host, port, namespace)` where any component may be `None` if the backend
24    /// does not support it (e.g. Sqlite has no host or port).
25    fn connection_attributes(
26        pool: &sqlx::Pool<Self>,
27    ) -> (Option<String>, Option<u16>, Option<String>);
28
29    /// Extract the number of rows affected from a `QueryResult`.
30    ///
31    /// Each `SQLx` backend defines its own `QueryResult` type with an inherent
32    /// `rows_affected()` method. This trait method provides a uniform interface for the
33    /// instrumentation layer.
34    fn rows_affected(result: &<Self as sqlx::Database>::QueryResult) -> u64;
35}
36
37/// Sealing module for [`Database`]. The supertrait bound on `Database: sealed::Sealed`
38/// prevents downstream impls because only this crate can implement [`Sealed`](self::sealed::Sealed)
39/// for the backend types.
40mod sealed {
41    /// Marker trait that prevents external impls of [`Database`](super::Database).
42    pub trait Sealed {}
43
44    #[cfg(feature = "sqlite")]
45    impl Sealed for sqlx::Sqlite {}
46
47    #[cfg(feature = "postgres")]
48    impl Sealed for sqlx::Postgres {}
49
50    #[cfg(feature = "mysql")]
51    impl Sealed for sqlx::MySql {}
52}
53
54/// Extract `(host, port, namespace)` from a network-style backend's connect options by
55/// rendering them to a URL and parsing the components.
56///
57/// Used by the Postgres and `MySQL` impls of [`Database::connection_attributes`] – both
58/// share the same URL-based extraction logic. `SQLite` supplies a filename instead and
59/// does not need this helper.
60#[cfg(any(feature = "postgres", feature = "mysql"))]
61fn url_based_connection_attributes<O: sqlx::ConnectOptions>(
62    options: &O,
63) -> (Option<String>, Option<u16>, Option<String>) {
64    let url = options.to_url_lossy();
65    let host = url.host_str().map(String::from);
66    let port = url.port();
67    let namespace = url
68        .path_segments()
69        .and_then(|mut segments| segments.next().map(String::from));
70    (host, port, namespace)
71}
72
73#[cfg(feature = "sqlite")]
74#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
75impl Database for sqlx::Sqlite {
76    const SYSTEM: &'static str = "sqlite";
77
78    fn connection_attributes(
79        pool: &sqlx::Pool<Self>,
80    ) -> (Option<String>, Option<u16>, Option<String>) {
81        let namespace = pool
82            .connect_options()
83            .get_filename()
84            .to_str()
85            .map(String::from);
86        (None, None, namespace)
87    }
88
89    fn rows_affected(result: &sqlx::sqlite::SqliteQueryResult) -> u64 {
90        result.rows_affected()
91    }
92}
93
94#[cfg(feature = "postgres")]
95#[cfg_attr(docsrs, doc(cfg(feature = "postgres")))]
96impl Database for sqlx::Postgres {
97    const SYSTEM: &'static str = "postgresql";
98
99    fn connection_attributes(
100        pool: &sqlx::Pool<Self>,
101    ) -> (Option<String>, Option<u16>, Option<String>) {
102        url_based_connection_attributes(pool.connect_options().as_ref())
103    }
104
105    fn rows_affected(result: &sqlx::postgres::PgQueryResult) -> u64 {
106        result.rows_affected()
107    }
108}
109
110#[cfg(feature = "mysql")]
111#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))]
112impl Database for sqlx::MySql {
113    const SYSTEM: &'static str = "mysql";
114
115    fn connection_attributes(
116        pool: &sqlx::Pool<Self>,
117    ) -> (Option<String>, Option<u16>, Option<String>) {
118        url_based_connection_attributes(pool.connect_options().as_ref())
119    }
120
121    fn rows_affected(result: &sqlx::mysql::MySqlQueryResult) -> u64 {
122        result.rows_affected()
123    }
124}