1use crate::config::{DatabaseBackend, SqlxConfig, SslMode};
4use crate::error::{SqlxError, SqlxResult};
5
6#[derive(Clone)]
8pub enum SqlxPool {
9 #[cfg(feature = "postgres")]
11 Postgres(sqlx::PgPool),
12 #[cfg(feature = "mysql")]
14 MySql(sqlx::MySqlPool),
15 #[cfg(feature = "sqlite")]
17 Sqlite(sqlx::SqlitePool),
18}
19
20#[cfg(feature = "postgres")]
24fn pg_ssl_mode(mode: SslMode) -> sqlx::postgres::PgSslMode {
25 use sqlx::postgres::PgSslMode;
26
27 match mode {
28 SslMode::Disable => PgSslMode::Disable,
29 SslMode::Prefer => PgSslMode::Prefer,
30 SslMode::Require => PgSslMode::Require,
31 SslMode::VerifyCa => PgSslMode::VerifyCa,
32 SslMode::VerifyFull => PgSslMode::VerifyFull,
33 }
34}
35
36#[cfg(feature = "mysql")]
41fn mysql_ssl_mode(mode: SslMode) -> sqlx::mysql::MySqlSslMode {
42 use sqlx::mysql::MySqlSslMode;
43
44 match mode {
45 SslMode::Disable => MySqlSslMode::Disabled,
46 SslMode::Prefer => MySqlSslMode::Preferred,
47 SslMode::Require => MySqlSslMode::Required,
48 SslMode::VerifyCa => MySqlSslMode::VerifyCa,
49 SslMode::VerifyFull => MySqlSslMode::VerifyIdentity,
50 }
51}
52
53impl SqlxPool {
54 pub async fn connect(config: &SqlxConfig) -> SqlxResult<Self> {
56 match config.backend {
57 #[cfg(feature = "postgres")]
58 DatabaseBackend::Postgres => {
59 use std::str::FromStr;
60
61 use sqlx::postgres::PgConnectOptions;
62
63 let mut options = PgConnectOptions::from_str(&config.url)?
64 .statement_cache_capacity(config.statement_cache_capacity)
65 .ssl_mode(pg_ssl_mode(config.ssl_mode));
66 if let Some(name) = &config.application_name {
67 options = options.application_name(name);
68 }
69
70 let pool = sqlx::postgres::PgPoolOptions::new()
71 .max_connections(config.max_connections)
72 .min_connections(config.min_connections)
73 .acquire_timeout(config.connect_timeout)
74 .idle_timeout(config.idle_timeout)
75 .max_lifetime(config.max_lifetime)
76 .connect_with(options)
77 .await?;
78 Ok(Self::Postgres(pool))
79 }
80 #[cfg(feature = "mysql")]
81 DatabaseBackend::MySql => {
82 use std::str::FromStr;
83
84 use sqlx::mysql::MySqlConnectOptions;
85
86 let options = MySqlConnectOptions::from_str(&config.url)?
87 .statement_cache_capacity(config.statement_cache_capacity)
88 .ssl_mode(mysql_ssl_mode(config.ssl_mode));
89 if config.application_name.is_some() {
90 tracing::warn!(
91 "application_name is not supported by the sqlx MySQL backend; ignoring"
92 );
93 }
94
95 let pool = sqlx::mysql::MySqlPoolOptions::new()
96 .max_connections(config.max_connections)
97 .min_connections(config.min_connections)
98 .acquire_timeout(config.connect_timeout)
99 .idle_timeout(config.idle_timeout)
100 .max_lifetime(config.max_lifetime)
101 .connect_with(options)
102 .await?;
103 Ok(Self::MySql(pool))
104 }
105 #[cfg(feature = "sqlite")]
106 DatabaseBackend::Sqlite => {
107 use std::str::FromStr;
108
109 use sqlx::sqlite::SqliteConnectOptions;
110
111 let options = SqliteConnectOptions::from_str(&config.url)?
112 .statement_cache_capacity(config.statement_cache_capacity);
113 if config.ssl_mode != SslMode::default() {
114 tracing::warn!("ssl_mode is not applicable to SQLite connections; ignoring");
115 }
116 if config.application_name.is_some() {
117 tracing::warn!(
118 "application_name is not supported by the sqlx SQLite backend; ignoring"
119 );
120 }
121
122 let pool = sqlx::sqlite::SqlitePoolOptions::new()
123 .max_connections(config.max_connections)
124 .min_connections(config.min_connections)
125 .acquire_timeout(config.connect_timeout)
126 .idle_timeout(config.idle_timeout)
127 .max_lifetime(config.max_lifetime)
128 .connect_with(options)
129 .await?;
130 Ok(Self::Sqlite(pool))
131 }
132 #[allow(unreachable_patterns)]
133 _ => Err(SqlxError::config(format!(
134 "Database backend {:?} not enabled. Enable the corresponding feature.",
135 config.backend
136 ))),
137 }
138 }
139
140 pub fn backend(&self) -> DatabaseBackend {
142 match self {
143 #[cfg(feature = "postgres")]
144 Self::Postgres(_) => DatabaseBackend::Postgres,
145 #[cfg(feature = "mysql")]
146 Self::MySql(_) => DatabaseBackend::MySql,
147 #[cfg(feature = "sqlite")]
148 Self::Sqlite(_) => DatabaseBackend::Sqlite,
149 }
150 }
151
152 pub async fn close(&self) {
154 match self {
155 #[cfg(feature = "postgres")]
156 Self::Postgres(pool) => pool.close().await,
157 #[cfg(feature = "mysql")]
158 Self::MySql(pool) => pool.close().await,
159 #[cfg(feature = "sqlite")]
160 Self::Sqlite(pool) => pool.close().await,
161 }
162 }
163
164 pub fn is_closed(&self) -> bool {
166 match self {
167 #[cfg(feature = "postgres")]
168 Self::Postgres(pool) => pool.is_closed(),
169 #[cfg(feature = "mysql")]
170 Self::MySql(pool) => pool.is_closed(),
171 #[cfg(feature = "sqlite")]
172 Self::Sqlite(pool) => pool.is_closed(),
173 }
174 }
175
176 pub fn size(&self) -> u32 {
178 match self {
179 #[cfg(feature = "postgres")]
180 Self::Postgres(pool) => pool.size(),
181 #[cfg(feature = "mysql")]
182 Self::MySql(pool) => pool.size(),
183 #[cfg(feature = "sqlite")]
184 Self::Sqlite(pool) => pool.size(),
185 }
186 }
187
188 pub fn num_idle(&self) -> usize {
190 match self {
191 #[cfg(feature = "postgres")]
192 Self::Postgres(pool) => pool.num_idle(),
193 #[cfg(feature = "mysql")]
194 Self::MySql(pool) => pool.num_idle(),
195 #[cfg(feature = "sqlite")]
196 Self::Sqlite(pool) => pool.num_idle(),
197 }
198 }
199
200 #[cfg(feature = "postgres")]
202 pub fn as_postgres(&self) -> Option<&sqlx::PgPool> {
203 match self {
204 Self::Postgres(pool) => Some(pool),
205 #[allow(unreachable_patterns)]
206 _ => None,
207 }
208 }
209
210 #[cfg(feature = "mysql")]
212 pub fn as_mysql(&self) -> Option<&sqlx::MySqlPool> {
213 match self {
214 Self::MySql(pool) => Some(pool),
215 #[allow(unreachable_patterns)]
216 _ => None,
217 }
218 }
219
220 #[cfg(feature = "sqlite")]
222 pub fn as_sqlite(&self) -> Option<&sqlx::SqlitePool> {
223 match self {
224 Self::Sqlite(pool) => Some(pool),
225 #[allow(unreachable_patterns)]
226 _ => None,
227 }
228 }
229}
230
231pub struct SqlxPoolBuilder {
233 config: SqlxConfig,
234}
235
236impl SqlxPoolBuilder {
237 pub fn new(config: SqlxConfig) -> Self {
239 Self { config }
240 }
241
242 pub fn from_url(url: impl Into<String>) -> SqlxResult<Self> {
244 let config = SqlxConfig::from_url(url)?;
245 Ok(Self { config })
246 }
247
248 pub fn max_connections(mut self, max: u32) -> Self {
250 self.config.max_connections = max;
251 self
252 }
253
254 pub fn min_connections(mut self, min: u32) -> Self {
256 self.config.min_connections = min;
257 self
258 }
259
260 pub async fn build(self) -> SqlxResult<SqlxPool> {
262 SqlxPool::connect(&self.config).await
263 }
264}
265
266#[derive(Debug, Clone)]
268pub struct PoolStatus {
269 pub size: u32,
271 pub idle: usize,
273 pub is_closed: bool,
275 pub backend: DatabaseBackend,
277}
278
279impl SqlxPool {
280 pub fn status(&self) -> PoolStatus {
282 PoolStatus {
283 size: self.size(),
284 idle: self.num_idle(),
285 is_closed: self.is_closed(),
286 backend: self.backend(),
287 }
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295 #[test]
296 fn test_pool_builder() {
297 let builder = SqlxPoolBuilder::from_url("postgres://localhost/test").unwrap();
298 let builder = builder.max_connections(20).min_connections(5);
299 assert_eq!(builder.config.max_connections, 20);
300 assert_eq!(builder.config.min_connections, 5);
301 }
302
303 #[cfg(feature = "postgres")]
304 #[test]
305 fn test_pg_ssl_mode_mapping() {
306 use sqlx::postgres::PgSslMode;
307
308 assert!(matches!(pg_ssl_mode(SslMode::Disable), PgSslMode::Disable));
309 assert!(matches!(pg_ssl_mode(SslMode::Prefer), PgSslMode::Prefer));
310 assert!(matches!(pg_ssl_mode(SslMode::Require), PgSslMode::Require));
311 assert!(matches!(
312 pg_ssl_mode(SslMode::VerifyCa),
313 PgSslMode::VerifyCa
314 ));
315 assert!(matches!(
316 pg_ssl_mode(SslMode::VerifyFull),
317 PgSslMode::VerifyFull
318 ));
319 }
320
321 #[cfg(feature = "mysql")]
322 #[test]
323 fn test_mysql_ssl_mode_mapping() {
324 use sqlx::mysql::MySqlSslMode;
325
326 assert!(matches!(
327 mysql_ssl_mode(SslMode::Disable),
328 MySqlSslMode::Disabled
329 ));
330 assert!(matches!(
331 mysql_ssl_mode(SslMode::Prefer),
332 MySqlSslMode::Preferred
333 ));
334 assert!(matches!(
335 mysql_ssl_mode(SslMode::Require),
336 MySqlSslMode::Required
337 ));
338 assert!(matches!(
339 mysql_ssl_mode(SslMode::VerifyCa),
340 MySqlSslMode::VerifyCa
341 ));
342 assert!(matches!(
343 mysql_ssl_mode(SslMode::VerifyFull),
344 MySqlSslMode::VerifyIdentity
345 ));
346 }
347}