prax_sqlx/
pool.rs

1//! Connection pool management for SQLx.
2
3use crate::config::{DatabaseBackend, SqlxConfig};
4use crate::error::{SqlxError, SqlxResult};
5
6/// A wrapper around SQLx connection pools supporting multiple databases.
7#[derive(Clone)]
8pub enum SqlxPool {
9    /// PostgreSQL connection pool
10    #[cfg(feature = "postgres")]
11    Postgres(sqlx::PgPool),
12    /// MySQL connection pool
13    #[cfg(feature = "mysql")]
14    MySql(sqlx::MySqlPool),
15    /// SQLite connection pool
16    #[cfg(feature = "sqlite")]
17    Sqlite(sqlx::SqlitePool),
18}
19
20impl SqlxPool {
21    /// Create a new pool from configuration.
22    pub async fn connect(config: &SqlxConfig) -> SqlxResult<Self> {
23        match config.backend {
24            #[cfg(feature = "postgres")]
25            DatabaseBackend::Postgres => {
26                let pool = sqlx::postgres::PgPoolOptions::new()
27                    .max_connections(config.max_connections)
28                    .min_connections(config.min_connections)
29                    .acquire_timeout(config.connect_timeout)
30                    .idle_timeout(config.idle_timeout)
31                    .max_lifetime(config.max_lifetime)
32                    .connect(&config.url)
33                    .await?;
34                Ok(Self::Postgres(pool))
35            }
36            #[cfg(feature = "mysql")]
37            DatabaseBackend::MySql => {
38                let pool = sqlx::mysql::MySqlPoolOptions::new()
39                    .max_connections(config.max_connections)
40                    .min_connections(config.min_connections)
41                    .acquire_timeout(config.connect_timeout)
42                    .idle_timeout(config.idle_timeout)
43                    .max_lifetime(config.max_lifetime)
44                    .connect(&config.url)
45                    .await?;
46                Ok(Self::MySql(pool))
47            }
48            #[cfg(feature = "sqlite")]
49            DatabaseBackend::Sqlite => {
50                let pool = sqlx::sqlite::SqlitePoolOptions::new()
51                    .max_connections(config.max_connections)
52                    .min_connections(config.min_connections)
53                    .acquire_timeout(config.connect_timeout)
54                    .idle_timeout(config.idle_timeout)
55                    .max_lifetime(config.max_lifetime)
56                    .connect(&config.url)
57                    .await?;
58                Ok(Self::Sqlite(pool))
59            }
60            #[allow(unreachable_patterns)]
61            _ => Err(SqlxError::config(format!(
62                "Database backend {:?} not enabled. Enable the corresponding feature.",
63                config.backend
64            ))),
65        }
66    }
67
68    /// Get the database backend type.
69    pub fn backend(&self) -> DatabaseBackend {
70        match self {
71            #[cfg(feature = "postgres")]
72            Self::Postgres(_) => DatabaseBackend::Postgres,
73            #[cfg(feature = "mysql")]
74            Self::MySql(_) => DatabaseBackend::MySql,
75            #[cfg(feature = "sqlite")]
76            Self::Sqlite(_) => DatabaseBackend::Sqlite,
77        }
78    }
79
80    /// Close the pool.
81    pub async fn close(&self) {
82        match self {
83            #[cfg(feature = "postgres")]
84            Self::Postgres(pool) => pool.close().await,
85            #[cfg(feature = "mysql")]
86            Self::MySql(pool) => pool.close().await,
87            #[cfg(feature = "sqlite")]
88            Self::Sqlite(pool) => pool.close().await,
89        }
90    }
91
92    /// Check if the pool is closed.
93    pub fn is_closed(&self) -> bool {
94        match self {
95            #[cfg(feature = "postgres")]
96            Self::Postgres(pool) => pool.is_closed(),
97            #[cfg(feature = "mysql")]
98            Self::MySql(pool) => pool.is_closed(),
99            #[cfg(feature = "sqlite")]
100            Self::Sqlite(pool) => pool.is_closed(),
101        }
102    }
103
104    /// Get pool statistics.
105    pub fn size(&self) -> u32 {
106        match self {
107            #[cfg(feature = "postgres")]
108            Self::Postgres(pool) => pool.size(),
109            #[cfg(feature = "mysql")]
110            Self::MySql(pool) => pool.size(),
111            #[cfg(feature = "sqlite")]
112            Self::Sqlite(pool) => pool.size(),
113        }
114    }
115
116    /// Get number of idle connections.
117    pub fn num_idle(&self) -> usize {
118        match self {
119            #[cfg(feature = "postgres")]
120            Self::Postgres(pool) => pool.num_idle(),
121            #[cfg(feature = "mysql")]
122            Self::MySql(pool) => pool.num_idle(),
123            #[cfg(feature = "sqlite")]
124            Self::Sqlite(pool) => pool.num_idle(),
125        }
126    }
127
128    /// Get the underlying PostgreSQL pool.
129    #[cfg(feature = "postgres")]
130    pub fn as_postgres(&self) -> Option<&sqlx::PgPool> {
131        match self {
132            Self::Postgres(pool) => Some(pool),
133            #[allow(unreachable_patterns)]
134            _ => None,
135        }
136    }
137
138    /// Get the underlying MySQL pool.
139    #[cfg(feature = "mysql")]
140    pub fn as_mysql(&self) -> Option<&sqlx::MySqlPool> {
141        match self {
142            Self::MySql(pool) => Some(pool),
143            #[allow(unreachable_patterns)]
144            _ => None,
145        }
146    }
147
148    /// Get the underlying SQLite pool.
149    #[cfg(feature = "sqlite")]
150    pub fn as_sqlite(&self) -> Option<&sqlx::SqlitePool> {
151        match self {
152            Self::Sqlite(pool) => Some(pool),
153            #[allow(unreachable_patterns)]
154            _ => None,
155        }
156    }
157}
158
159/// Pool builder for SQLx.
160pub struct SqlxPoolBuilder {
161    config: SqlxConfig,
162}
163
164impl SqlxPoolBuilder {
165    /// Create a new pool builder from a configuration.
166    pub fn new(config: SqlxConfig) -> Self {
167        Self { config }
168    }
169
170    /// Create a new pool builder from a URL.
171    pub fn from_url(url: impl Into<String>) -> SqlxResult<Self> {
172        let config = SqlxConfig::from_url(url)?;
173        Ok(Self { config })
174    }
175
176    /// Set max connections.
177    pub fn max_connections(mut self, max: u32) -> Self {
178        self.config.max_connections = max;
179        self
180    }
181
182    /// Set min connections.
183    pub fn min_connections(mut self, min: u32) -> Self {
184        self.config.min_connections = min;
185        self
186    }
187
188    /// Build and connect the pool.
189    pub async fn build(self) -> SqlxResult<SqlxPool> {
190        SqlxPool::connect(&self.config).await
191    }
192}
193
194/// Pool status information.
195#[derive(Debug, Clone)]
196pub struct PoolStatus {
197    /// Total pool size
198    pub size: u32,
199    /// Number of idle connections
200    pub idle: usize,
201    /// Whether the pool is closed
202    pub is_closed: bool,
203    /// Database backend type
204    pub backend: DatabaseBackend,
205}
206
207impl SqlxPool {
208    /// Get the pool status.
209    pub fn status(&self) -> PoolStatus {
210        PoolStatus {
211            size: self.size(),
212            idle: self.num_idle(),
213            is_closed: self.is_closed(),
214            backend: self.backend(),
215        }
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn test_pool_builder() {
225        let builder = SqlxPoolBuilder::from_url("postgres://localhost/test").unwrap();
226        let builder = builder.max_connections(20).min_connections(5);
227        assert_eq!(builder.config.max_connections, 20);
228        assert_eq!(builder.config.min_connections, 5);
229    }
230}