Skip to main content

fr_rust/db/
db.rs

1use deadpool_postgres::{BuildError, Manager, ManagerConfig, Pool, PoolError, RecyclingMethod, Runtime};
2use thiserror::Error;
3use tokio_postgres::{Config, Error as PgError, NoTls, Row, types::ToSql};
4
5/// Unified error type for connection pooling, configuration, and database queries.
6#[derive(Debug, Error)]
7pub enum DbError {
8    #[error("Failed to acquire connection from pool: {0}")]
9    Pool(#[from] PoolError),
10
11    #[error("Database query error: {0}")]
12    Query(#[from] PgError),
13
14    #[error("Invalid database configuration: {0}")]
15    Config(String),
16
17    #[error("Failed to build connection pool: {0}")]
18    Build(#[from] BuildError),
19}
20
21/// A highly optimized, easily clonable Database Pool wrapper.
22#[derive(Clone)]
23pub struct DbPool {
24    pool: Pool,
25}
26
27impl DbPool {
28    /// Create a new PostgreSQL connection pool without panicking.
29    /// Takes `&str` to avoid unnecessary allocations and allows configuring max_size.
30    pub fn new(database_url: &str, max_connections: usize) -> Result<Self, DbError> {
31        let config: Config = database_url
32            .parse()
33            .map_err(|e| DbError::Config(format!("{}", e)))?;
34
35        let mgr_config = ManagerConfig {
36            recycling_method: RecyclingMethod::Fast,
37        };
38
39        let manager = Manager::from_config(config, NoTls, mgr_config);
40
41        // Build the optimized pool, propagating any errors instead of panicking
42        let pool = Pool::builder(manager)
43            .max_size(max_connections)
44            .runtime(Runtime::Tokio1)
45            .build()?;
46
47        Ok(DbPool { pool })
48    }
49
50    /// Execute INSERT / UPDATE / DELETE
51    /// Returns affected rows
52    #[inline]
53    pub async fn execute(
54        &self,
55        query: &str,
56        params: &[&(dyn ToSql + Sync)],
57    ) -> Result<u64, DbError> {
58        let client = self.pool.get().await?;
59        let affected = client.execute(query, params).await?;
60        Ok(affected)
61    }
62
63    /// Get ONE row
64    #[inline]
65    pub async fn query_one(
66        &self,
67        query: &str,
68        params: &[&(dyn ToSql + Sync)],
69    ) -> Result<Row, DbError> {
70        let client = self.pool.get().await?;
71        let row = client.query_one(query, params).await?;
72        Ok(row)
73    }
74
75    /// Get ONE row or none
76    #[inline]
77    pub async fn query_opt(
78        &self,
79        query: &str,
80        params: &[&(dyn ToSql + Sync)],
81    ) -> Result<Option<Row>, DbError> {
82        let client = self.pool.get().await?;
83        let row_opt = client.query_opt(query, params).await?;
84        Ok(row_opt)
85    }
86
87    /// Get MANY rows
88    #[inline]
89    pub async fn query(
90        &self,
91        query: &str,
92        params: &[&(dyn ToSql + Sync)],
93    ) -> Result<Vec<Row>, DbError> {
94        let client = self.pool.get().await?;
95        let rows = client.query(query, params).await?;
96        Ok(rows)
97    }
98}