Skip to main content

rs_zero/db/
pool.rs

1#[cfg(any(feature = "db-sqlite", feature = "db-postgres", feature = "db-mysql"))]
2use sqlx::Pool as SqlxPool;
3#[cfg(feature = "db-mysql")]
4use sqlx::{MySql, mysql::MySqlPoolOptions};
5#[cfg(feature = "db-postgres")]
6use sqlx::{Postgres, postgres::PgPoolOptions};
7#[cfg(feature = "db-sqlite")]
8use sqlx::{Sqlite, sqlite::SqlitePoolOptions};
9
10use crate::db::{DatabaseConfig, DatabaseError, DatabaseKind, DatabaseResult};
11
12/// SQLite database pool.
13#[cfg(feature = "db-sqlite")]
14pub type SqliteDatabasePool = SqlxPool<Sqlite>;
15
16/// PostgreSQL database pool.
17#[cfg(feature = "db-postgres")]
18pub type PostgresDatabasePool = SqlxPool<Postgres>;
19
20/// MySQL database pool.
21#[cfg(feature = "db-mysql")]
22pub type MySqlDatabasePool = SqlxPool<MySql>;
23
24/// Database pool used by the enabled default adapter.
25#[cfg(feature = "db-sqlite")]
26pub type DatabasePool = SqliteDatabasePool;
27
28/// Database pool used when PostgreSQL is the enabled adapter.
29#[cfg(all(not(feature = "db-sqlite"), feature = "db-postgres"))]
30pub type DatabasePool = PostgresDatabasePool;
31
32/// Database pool used when MySQL is the only enabled adapter.
33#[cfg(all(
34    not(feature = "db-sqlite"),
35    not(feature = "db-postgres"),
36    feature = "db-mysql"
37))]
38pub type DatabasePool = MySqlDatabasePool;
39
40/// Connects a SQLx pool for the default enabled adapter.
41#[cfg(feature = "db-sqlite")]
42pub async fn connect_pool(config: &DatabaseConfig) -> DatabaseResult<DatabasePool> {
43    connect_sqlite_pool(config).await
44}
45
46/// Connects a SQLx pool for the default enabled adapter.
47#[cfg(all(not(feature = "db-sqlite"), feature = "db-postgres"))]
48pub async fn connect_pool(config: &DatabaseConfig) -> DatabaseResult<DatabasePool> {
49    connect_postgres_pool(config).await
50}
51
52/// Connects a SQLx pool for the default enabled adapter.
53#[cfg(all(
54    not(feature = "db-sqlite"),
55    not(feature = "db-postgres"),
56    feature = "db-mysql"
57))]
58pub async fn connect_pool(config: &DatabaseConfig) -> DatabaseResult<DatabasePool> {
59    connect_mysql_pool(config).await
60}
61
62/// Connects a SQLite SQLx pool.
63#[cfg(feature = "db-sqlite")]
64pub async fn connect_sqlite_pool(config: &DatabaseConfig) -> DatabaseResult<SqliteDatabasePool> {
65    if config.kind != DatabaseKind::Sqlite {
66        return Err(DatabaseError::Unsupported(
67            "sqlite database kind required".to_string(),
68        ));
69    }
70    Ok(SqlitePoolOptions::new()
71        .max_connections(config.max_connections)
72        .acquire_timeout(config.connect_timeout)
73        .connect(&config.url)
74        .await?)
75}
76
77/// Connects a PostgreSQL SQLx pool.
78#[cfg(feature = "db-postgres")]
79pub async fn connect_postgres_pool(
80    config: &DatabaseConfig,
81) -> DatabaseResult<PostgresDatabasePool> {
82    if config.kind != DatabaseKind::Postgres {
83        return Err(DatabaseError::Unsupported(
84            "postgres database kind required".to_string(),
85        ));
86    }
87    Ok(PgPoolOptions::new()
88        .max_connections(config.max_connections)
89        .acquire_timeout(config.connect_timeout)
90        .connect(&config.url)
91        .await?)
92}
93
94/// Connects a MySQL SQLx pool.
95#[cfg(feature = "db-mysql")]
96pub async fn connect_mysql_pool(config: &DatabaseConfig) -> DatabaseResult<MySqlDatabasePool> {
97    if config.kind != DatabaseKind::Mysql {
98        return Err(DatabaseError::Unsupported(
99            "mysql database kind required".to_string(),
100        ));
101    }
102    Ok(MySqlPoolOptions::new()
103        .max_connections(config.max_connections)
104        .acquire_timeout(config.connect_timeout)
105        .connect(&config.url)
106        .await?)
107}
108
109/// Runs a lightweight health check on the default pool type.
110pub async fn health_check(pool: &DatabasePool) -> DatabaseResult<()> {
111    sqlx::query("SELECT 1").execute(pool).await?;
112    Ok(())
113}
114
115/// Runs a lightweight health check and records SQL metrics.
116#[cfg(feature = "observability")]
117pub async fn health_check_with_metrics(
118    pool: &DatabasePool,
119    metrics: &crate::observability::MetricsRegistry,
120) -> DatabaseResult<()> {
121    crate::observability::observe_sql_query(
122        Some(metrics),
123        default_database_kind_label(),
124        "db",
125        "health_check",
126        "select",
127        health_check(pool),
128    )
129    .await
130}
131
132/// Runs a lightweight PostgreSQL health check.
133#[cfg(feature = "db-postgres")]
134pub async fn health_check_postgres(pool: &PostgresDatabasePool) -> DatabaseResult<()> {
135    sqlx::query("SELECT 1").execute(pool).await?;
136    Ok(())
137}
138
139/// Runs a lightweight MySQL health check.
140#[cfg(feature = "db-mysql")]
141pub async fn health_check_mysql(pool: &MySqlDatabasePool) -> DatabaseResult<()> {
142    sqlx::query("SELECT 1").execute(pool).await?;
143    Ok(())
144}
145
146#[cfg(all(feature = "observability", feature = "db-sqlite"))]
147fn default_database_kind_label() -> &'static str {
148    "sqlite"
149}
150
151#[cfg(all(
152    feature = "observability",
153    not(feature = "db-sqlite"),
154    feature = "db-postgres"
155))]
156fn default_database_kind_label() -> &'static str {
157    "postgres"
158}
159
160#[cfg(all(
161    feature = "observability",
162    not(feature = "db-sqlite"),
163    not(feature = "db-postgres"),
164    feature = "db-mysql"
165))]
166fn default_database_kind_label() -> &'static str {
167    "mysql"
168}
169
170#[cfg(test)]
171mod tests {
172    use crate::db::{DatabaseConfig, DatabaseKind};
173    #[cfg(feature = "db-sqlite")]
174    use crate::db::{connect_pool, health_check};
175
176    #[cfg(feature = "db-sqlite")]
177    #[tokio::test]
178    async fn sqlite_pool_passes_health_check() {
179        let pool = connect_pool(&DatabaseConfig::default())
180            .await
181            .expect("pool");
182        health_check(&pool).await.expect("health");
183    }
184
185    #[test]
186    fn postgres_config_uses_postgres_kind() {
187        let config = DatabaseConfig::postgres("postgres://localhost/app");
188        assert_eq!(config.kind, DatabaseKind::Postgres);
189    }
190
191    #[test]
192    fn mysql_config_uses_mysql_kind() {
193        let config = DatabaseConfig::mysql("mysql://localhost/app");
194        assert_eq!(config.kind, DatabaseKind::Mysql);
195    }
196}