1use std::time::Duration;
4
5use sqlx::Database;
6
7use crate::error::FletchError;
8
9#[derive(Debug)]
14pub struct Pool<DB: Database> {
15 inner: sqlx::Pool<DB>,
16}
17
18impl<DB: Database> Clone for Pool<DB> {
19 fn clone(&self) -> Self {
20 Self {
21 inner: self.inner.clone(),
22 }
23 }
24}
25
26impl<DB: Database> Pool<DB> {
27 pub async fn connect(url: &str) -> Result<Self, FletchError> {
31 let pool = sqlx::Pool::<DB>::connect(url).await?;
32 Ok(Self { inner: pool })
33 }
34
35 pub fn builder() -> PoolBuilder<DB> {
37 PoolBuilder {
38 options: sqlx::pool::PoolOptions::new(),
39 }
40 }
41
42 pub async fn close(&self) {
44 self.inner.close().await;
45 }
46
47 pub fn inner(&self) -> &sqlx::Pool<DB> {
52 &self.inner
53 }
54
55 pub fn size(&self) -> u32 {
57 self.inner.size()
58 }
59
60 pub fn num_idle(&self) -> usize {
62 self.inner.num_idle()
63 }
64
65 pub async fn begin(&self) -> Result<Transaction<'static, DB>, FletchError> {
67 let tx = self.inner.begin().await?;
68 Ok(Transaction { inner: tx })
69 }
70}
71
72pub struct PoolBuilder<DB: Database> {
76 options: sqlx::pool::PoolOptions<DB>,
77}
78
79impl<DB: Database> PoolBuilder<DB> {
80 pub fn max_connections(mut self, max: u32) -> Self {
82 self.options = self.options.max_connections(max);
83 self
84 }
85
86 pub fn min_connections(mut self, min: u32) -> Self {
88 self.options = self.options.min_connections(min);
89 self
90 }
91
92 pub fn idle_timeout(mut self, timeout: Duration) -> Self {
94 self.options = self.options.idle_timeout(timeout);
95 self
96 }
97
98 pub fn max_lifetime(mut self, lifetime: Duration) -> Self {
100 self.options = self.options.max_lifetime(lifetime);
101 self
102 }
103
104 pub fn acquire_timeout(mut self, timeout: Duration) -> Self {
106 self.options = self.options.acquire_timeout(timeout);
107 self
108 }
109
110 pub async fn connect(self, url: &str) -> Result<Pool<DB>, FletchError> {
112 let pool = self.options.connect(url).await?;
113 Ok(Pool { inner: pool })
114 }
115}
116
117#[derive(Debug, Clone, Default)]
131#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
132pub struct PoolConfig {
133 pub max_connections: Option<u32>,
134 pub min_connections: Option<u32>,
135 pub acquire_timeout_secs: Option<u64>,
136 pub idle_timeout_secs: Option<u64>,
137 pub max_lifetime_secs: Option<u64>,
138}
139
140impl PoolConfig {
141 pub fn from_env() -> Self {
145 Self {
146 max_connections: std::env::var("DATABASE_MAX_CONNECTIONS")
147 .ok()
148 .and_then(|v| v.parse().ok()),
149 min_connections: std::env::var("DATABASE_MIN_CONNECTIONS")
150 .ok()
151 .and_then(|v| v.parse().ok()),
152 acquire_timeout_secs: std::env::var("DATABASE_ACQUIRE_TIMEOUT_SECS")
153 .ok()
154 .and_then(|v| v.parse().ok()),
155 idle_timeout_secs: std::env::var("DATABASE_IDLE_TIMEOUT_SECS")
156 .ok()
157 .and_then(|v| v.parse().ok()),
158 max_lifetime_secs: std::env::var("DATABASE_MAX_LIFETIME_SECS")
159 .ok()
160 .and_then(|v| v.parse().ok()),
161 }
162 }
163
164 pub fn into_builder<DB: Database>(self) -> PoolBuilder<DB> {
166 let mut builder = PoolBuilder {
167 options: sqlx::pool::PoolOptions::new(),
168 };
169 if let Some(max) = self.max_connections {
170 builder = builder.max_connections(max);
171 }
172 if let Some(min) = self.min_connections {
173 builder = builder.min_connections(min);
174 }
175 if let Some(secs) = self.acquire_timeout_secs {
176 builder = builder.acquire_timeout(Duration::from_secs(secs));
177 }
178 if let Some(secs) = self.idle_timeout_secs {
179 builder = builder.idle_timeout(Duration::from_secs(secs));
180 }
181 if let Some(secs) = self.max_lifetime_secs {
182 builder = builder.max_lifetime(Duration::from_secs(secs));
183 }
184 builder
185 }
186}
187
188pub struct Transaction<'a, DB: Database> {
193 inner: sqlx::Transaction<'a, DB>,
194}
195
196impl<'a, DB: Database> Transaction<'a, DB> {
197 pub async fn commit(self) -> Result<(), FletchError> {
199 self.inner.commit().await?;
200 Ok(())
201 }
202
203 pub async fn rollback(self) -> Result<(), FletchError> {
205 self.inner.rollback().await?;
206 Ok(())
207 }
208
209 pub fn inner_mut(&mut self) -> &mut sqlx::Transaction<'a, DB> {
214 &mut self.inner
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use sqlx::Sqlite;
222
223 #[tokio::test]
224 async fn pool_connect_sqlite_memory() {
225 let pool = Pool::<Sqlite>::connect("sqlite::memory:").await.unwrap();
226 {
228 let _conn = pool.inner().acquire().await.unwrap();
229 }
230 pool.close().await;
231 }
232
233 #[tokio::test]
234 async fn pool_begin_and_commit() {
235 let pool = Pool::<Sqlite>::connect("sqlite::memory:").await.unwrap();
236
237 sqlx::query("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
239 .execute(pool.inner())
240 .await
241 .unwrap();
242
243 let mut tx = pool.begin().await.unwrap();
245 sqlx::query("INSERT INTO test (id, name) VALUES (1, 'Alice')")
246 .execute(&mut **tx.inner_mut())
247 .await
248 .unwrap();
249 tx.commit().await.unwrap();
250
251 let row: (i64, String) = sqlx::query_as("SELECT id, name FROM test WHERE id = 1")
253 .fetch_one(pool.inner())
254 .await
255 .unwrap();
256 assert_eq!(row, (1, "Alice".to_string()));
257
258 pool.close().await;
259 }
260
261 #[tokio::test]
262 async fn transaction_auto_rollback_on_drop() {
263 let pool = Pool::<Sqlite>::connect("sqlite::memory:").await.unwrap();
264
265 sqlx::query("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
266 .execute(pool.inner())
267 .await
268 .unwrap();
269
270 {
272 let mut tx = pool.begin().await.unwrap();
273 sqlx::query("INSERT INTO test (id, name) VALUES (1, 'Alice')")
274 .execute(&mut **tx.inner_mut())
275 .await
276 .unwrap();
277 }
279
280 let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM test")
282 .fetch_one(pool.inner())
283 .await
284 .unwrap();
285 assert_eq!(count.0, 0);
286
287 pool.close().await;
288 }
289
290 #[tokio::test]
291 async fn transaction_explicit_rollback() {
292 let pool = Pool::<Sqlite>::connect("sqlite::memory:").await.unwrap();
293
294 sqlx::query("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
295 .execute(pool.inner())
296 .await
297 .unwrap();
298
299 let mut tx = pool.begin().await.unwrap();
300 sqlx::query("INSERT INTO test (id, name) VALUES (1, 'Alice')")
301 .execute(&mut **tx.inner_mut())
302 .await
303 .unwrap();
304 tx.rollback().await.unwrap();
305
306 let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM test")
307 .fetch_one(pool.inner())
308 .await
309 .unwrap();
310 assert_eq!(count.0, 0);
311
312 pool.close().await;
313 }
314
315 #[tokio::test]
316 async fn pool_builder_connects_with_options() {
317 let pool = Pool::<Sqlite>::builder()
318 .max_connections(1)
319 .connect("sqlite::memory:")
320 .await
321 .unwrap();
322
323 let conn = pool.inner().acquire().await.unwrap();
325 drop(conn);
326 pool.close().await;
327 }
328
329 #[test]
330 fn pool_config_default_is_all_none() {
331 let config = PoolConfig::default();
332 assert!(config.max_connections.is_none());
333 assert!(config.min_connections.is_none());
334 assert!(config.acquire_timeout_secs.is_none());
335 assert!(config.idle_timeout_secs.is_none());
336 assert!(config.max_lifetime_secs.is_none());
337 }
338
339 #[test]
340 fn pool_config_into_builder_applies_fields() {
341 let config = PoolConfig {
342 max_connections: Some(20),
343 min_connections: Some(2),
344 acquire_timeout_secs: Some(10),
345 idle_timeout_secs: Some(300),
346 max_lifetime_secs: Some(1800),
347 };
348
349 let builder = config.into_builder::<Sqlite>();
350 let _ = builder;
353 }
354
355 #[tokio::test]
356 async fn pool_config_into_builder_connects() {
357 let config = PoolConfig {
358 max_connections: Some(1),
359 min_connections: None,
360 acquire_timeout_secs: None,
361 idle_timeout_secs: None,
362 max_lifetime_secs: None,
363 };
364
365 let pool = config
366 .into_builder::<Sqlite>()
367 .connect("sqlite::memory:")
368 .await
369 .unwrap();
370
371 let conn = pool.inner().acquire().await.unwrap();
372 drop(conn);
373 pool.close().await;
374 }
375
376 #[tokio::test]
377 async fn pool_introspection() {
378 let pool = Pool::<Sqlite>::builder()
379 .max_connections(5)
380 .connect("sqlite::memory:")
381 .await
382 .unwrap();
383
384 let conn = pool.inner().acquire().await.unwrap();
386
387 assert!(pool.size() >= 1);
388 assert!(pool.num_idle() < usize::try_from(pool.size()).unwrap());
390
391 drop(conn);
392 pool.close().await;
393 }
394}