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#[cfg(feature = "db-sqlite")]
14pub type SqliteDatabasePool = SqlxPool<Sqlite>;
15
16#[cfg(feature = "db-postgres")]
18pub type PostgresDatabasePool = SqlxPool<Postgres>;
19
20#[cfg(feature = "db-mysql")]
22pub type MySqlDatabasePool = SqlxPool<MySql>;
23
24#[cfg(feature = "db-sqlite")]
26pub type DatabasePool = SqliteDatabasePool;
27
28#[cfg(all(not(feature = "db-sqlite"), feature = "db-postgres"))]
30pub type DatabasePool = PostgresDatabasePool;
31
32#[cfg(all(
34 not(feature = "db-sqlite"),
35 not(feature = "db-postgres"),
36 feature = "db-mysql"
37))]
38pub type DatabasePool = MySqlDatabasePool;
39
40#[cfg(feature = "db-sqlite")]
42pub async fn connect_pool(config: &DatabaseConfig) -> DatabaseResult<DatabasePool> {
43 connect_sqlite_pool(config).await
44}
45
46#[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#[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#[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#[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#[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
109pub async fn health_check(pool: &DatabasePool) -> DatabaseResult<()> {
111 sqlx::query("SELECT 1").execute(pool).await?;
112 Ok(())
113}
114
115#[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#[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#[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}