Skip to main content

atomr_persistence_sql/
config.rs

1//! Connection configuration for the unified SQL provider.
2//!
3//! Resolves connection URLs from env vars with a dev / test / prod aware
4//! fallback ladder.
5
6use std::env;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum SqlDialect {
10    Sqlite,
11    Postgres,
12    MySql,
13    MsSql,
14}
15
16impl SqlDialect {
17    pub fn scheme(self) -> &'static str {
18        match self {
19            Self::Sqlite => "sqlite",
20            Self::Postgres => "postgres",
21            Self::MySql => "mysql",
22            Self::MsSql => "mssql",
23        }
24    }
25}
26
27#[derive(Debug, Clone)]
28pub struct SqlConfig {
29    pub url: String,
30    pub dialect: SqlDialect,
31    pub max_connections: u32,
32    pub auto_migrate: bool,
33}
34
35impl SqlConfig {
36    pub fn new(url: impl Into<String>) -> Self {
37        let url = url.into();
38        let dialect = crate::dialect::detect_dialect(&url).unwrap_or(SqlDialect::Sqlite);
39        // SQLite `:memory:` tied to a single connection is the only way every
40        // caller sees the same database; we also keep connections low by default
41        // because most event-sourced workloads are write-heavy through a small
42        // pool.
43        let is_memory_sqlite =
44            dialect == SqlDialect::Sqlite && (url.contains(":memory:") || url.ends_with(":memory:"));
45        let max_connections = if is_memory_sqlite { 1 } else { 5 };
46        Self { url, dialect, max_connections, auto_migrate: true }
47    }
48
49    pub fn with_max_connections(mut self, n: u32) -> Self {
50        self.max_connections = n;
51        self
52    }
53
54    pub fn with_auto_migrate(mut self, auto: bool) -> Self {
55        self.auto_migrate = auto;
56        self
57    }
58
59    /// Resolve a config from environment variables.
60    ///
61    /// Lookup order:
62    /// 1. `ATOMR_PERSISTENCE_SQL_URL` (any environment, explicit override).
63    /// 2. `ATOMR_IT_SQL_URL` (test integration).
64    /// 3. `DATABASE_URL` (prod conventions).
65    /// 4. Dev fallback: `sqlite::memory:`.
66    pub fn from_env() -> Self {
67        let url = env::var("ATOMR_PERSISTENCE_SQL_URL")
68            .or_else(|_| env::var("ATOMR_IT_SQL_URL"))
69            .or_else(|_| env::var("DATABASE_URL"))
70            .unwrap_or_else(|_| "sqlite::memory:".to_string());
71        let auto_migrate = env::var("ATOMR_PERSISTENCE_SQL_AUTO_MIGRATE")
72            .map(|v| !matches!(v.as_str(), "0" | "false" | "no"))
73            .unwrap_or(true);
74        Self::new(url).with_auto_migrate(auto_migrate)
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn detects_sqlite_scheme() {
84        let cfg = SqlConfig::new("sqlite::memory:");
85        assert_eq!(cfg.dialect, SqlDialect::Sqlite);
86    }
87
88    #[test]
89    fn detects_postgres_scheme() {
90        let cfg = SqlConfig::new("postgres://u:p@h/db");
91        assert_eq!(cfg.dialect, SqlDialect::Postgres);
92    }
93
94    #[test]
95    fn builder_overrides() {
96        let cfg = SqlConfig::new("sqlite::memory:").with_max_connections(12).with_auto_migrate(false);
97        assert_eq!(cfg.max_connections, 12);
98        assert!(!cfg.auto_migrate);
99    }
100}