Skip to main content

sql_middleware/postgres/
config.rs

1use super::typed::PgManager;
2use crate::middleware::{
3    ConfigAndPool, DatabaseType, MiddlewarePool, MiddlewarePoolOptions, SqlMiddlewareDbError,
4};
5
6/// Minimal Postgres configuration (keeps the public API backward-compatible
7/// with the old `deadpool_postgres::Config` usage).
8#[derive(Clone, Debug, Default)]
9pub struct PgConfig {
10    pub dbname: Option<String>,
11    pub host: Option<String>,
12    pub port: Option<u16>,
13    pub user: Option<String>,
14    pub password: Option<String>,
15}
16
17impl PgConfig {
18    #[must_use]
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    #[must_use]
24    pub fn to_tokio_config(&self) -> tokio_postgres::Config {
25        let mut cfg = tokio_postgres::Config::new();
26        if let Some(dbname) = &self.dbname {
27            cfg.dbname(dbname);
28        }
29        if let Some(host) = &self.host {
30            cfg.host(host);
31        }
32        if let Some(port) = self.port {
33            cfg.port(port);
34        }
35        if let Some(user) = &self.user {
36            cfg.user(user);
37        }
38        if let Some(password) = &self.password {
39            cfg.password(password);
40        }
41        cfg
42    }
43}
44
45/// Options for configuring a Postgres pool.
46#[derive(Clone)]
47pub struct PostgresOptions {
48    pub config: PgConfig,
49    pub translate_placeholders: bool,
50    pub pool_options: MiddlewarePoolOptions,
51}
52
53impl PostgresOptions {
54    #[must_use]
55    pub fn new(config: PgConfig) -> Self {
56        Self {
57            config,
58            translate_placeholders: false,
59            pool_options: MiddlewarePoolOptions::default(),
60        }
61    }
62
63    #[must_use]
64    pub fn with_translation(mut self, translate_placeholders: bool) -> Self {
65        self.translate_placeholders = translate_placeholders;
66        self
67    }
68
69    #[must_use]
70    pub fn with_pool_options(mut self, pool_options: MiddlewarePoolOptions) -> Self {
71        self.pool_options = pool_options;
72        self
73    }
74
75    #[must_use]
76    pub fn with_test_on_check_out(mut self, test_on_check_out: bool) -> Self {
77        self.pool_options.test_on_check_out = test_on_check_out;
78        self
79    }
80}
81
82/// Fluent builder for Postgres options.
83#[derive(Clone)]
84pub struct PostgresOptionsBuilder {
85    opts: PostgresOptions,
86}
87
88impl PostgresOptionsBuilder {
89    #[must_use]
90    pub fn new(config: PgConfig) -> Self {
91        Self {
92            opts: PostgresOptions::new(config),
93        }
94    }
95
96    #[must_use]
97    pub fn translation(mut self, translate_placeholders: bool) -> Self {
98        self.opts.translate_placeholders = translate_placeholders;
99        self
100    }
101
102    #[must_use]
103    pub fn pool_options(mut self, pool_options: MiddlewarePoolOptions) -> Self {
104        self.opts.pool_options = pool_options;
105        self
106    }
107
108    #[must_use]
109    pub fn test_on_check_out(mut self, test_on_check_out: bool) -> Self {
110        self.opts.pool_options.test_on_check_out = test_on_check_out;
111        self
112    }
113
114    #[must_use]
115    pub fn finish(self) -> PostgresOptions {
116        self.opts
117    }
118
119    /// Build a `ConfigAndPool` for `PostgreSQL`.
120    ///
121    /// # Errors
122    ///
123    /// Returns `SqlMiddlewareDbError` if pool creation fails.
124    pub async fn build(self) -> Result<ConfigAndPool, SqlMiddlewareDbError> {
125        ConfigAndPool::new_postgres(self.finish()).await
126    }
127}
128
129impl ConfigAndPool {
130    #[must_use]
131    pub fn postgres_builder(pg_config: PgConfig) -> PostgresOptionsBuilder {
132        PostgresOptionsBuilder::new(pg_config)
133    }
134
135    /// Asynchronous initializer for `ConfigAndPool` with Postgres.
136    ///
137    /// # Errors
138    /// Returns `SqlMiddlewareDbError::ConfigError` if required config fields are missing or `SqlMiddlewareDbError::ConnectionError` if pool creation fails.
139    #[allow(clippy::unused_async)]
140    pub async fn new_postgres(opts: PostgresOptions) -> Result<Self, SqlMiddlewareDbError> {
141        let pg_config = opts.config;
142        let translate_placeholders = opts.translate_placeholders;
143        let pool_options = opts.pool_options;
144
145        // Validate all required config fields are present
146        if pg_config.dbname.is_none() {
147            return Err(SqlMiddlewareDbError::ConfigError(
148                "dbname is required".to_string(),
149            ));
150        }
151
152        if pg_config.host.is_none() {
153            return Err(SqlMiddlewareDbError::ConfigError(
154                "host is required".to_string(),
155            ));
156        }
157        if pg_config.port.is_none() {
158            return Err(SqlMiddlewareDbError::ConfigError(
159                "port is required".to_string(),
160            ));
161        }
162        if pg_config.user.is_none() {
163            return Err(SqlMiddlewareDbError::ConfigError(
164                "user is required".to_string(),
165            ));
166        }
167        if pg_config.password.is_none() {
168            return Err(SqlMiddlewareDbError::ConfigError(
169                "password is required".to_string(),
170            ));
171        }
172
173        // Attempt to create connection pool
174        let manager = PgManager::new(pg_config.to_tokio_config()).with_pool_options(pool_options);
175        let pg_pool = manager.build_pool().await?;
176
177        Ok(ConfigAndPool {
178            pool: MiddlewarePool::Postgres(pg_pool),
179            db_type: DatabaseType::Postgres,
180            translate_placeholders,
181            statement_cache_mode: crate::types::StatementCacheMode::Cached,
182        })
183    }
184}