Skip to main content

modo_db/
connect.rs

1use crate::config::DatabaseConfig;
2use crate::pool::DbPool;
3use sea_orm::{ConnectOptions, Database};
4use tracing::info;
5
6/// Connect to the database using the provided configuration.
7///
8/// Auto-detects the backend from the URL scheme and applies
9/// backend-specific settings (SQLite pragmas, Postgres pool tuning).
10pub async fn connect(config: &DatabaseConfig) -> Result<DbPool, modo::Error> {
11    let mut opts = ConnectOptions::new(&config.url);
12    opts.max_connections(config.max_connections)
13        .min_connections(config.min_connections);
14
15    let conn = Database::connect(opts)
16        .await
17        .map_err(|e| modo::Error::internal(format!("Database connection failed: {e}")))?;
18
19    // Apply backend-specific settings
20    if conn.get_database_backend() == sea_orm::DatabaseBackend::Sqlite {
21        apply_sqlite_pragmas(&conn).await?;
22    }
23
24    info!(url = %redact_url(&config.url), "Database connected");
25    Ok(DbPool(conn))
26}
27
28#[cfg(feature = "sqlite")]
29async fn apply_sqlite_pragmas(conn: &sea_orm::DatabaseConnection) -> Result<(), modo::Error> {
30    use sea_orm::ConnectionTrait;
31
32    conn.execute_unprepared("PRAGMA journal_mode=WAL")
33        .await
34        .map_err(|e| modo::Error::internal(format!("Failed to set WAL mode: {e}")))?;
35    conn.execute_unprepared("PRAGMA busy_timeout=5000")
36        .await
37        .map_err(|e| modo::Error::internal(format!("Failed to set busy_timeout: {e}")))?;
38    conn.execute_unprepared("PRAGMA synchronous=NORMAL")
39        .await
40        .map_err(|e| modo::Error::internal(format!("Failed to set synchronous: {e}")))?;
41    conn.execute_unprepared("PRAGMA foreign_keys=ON")
42        .await
43        .map_err(|e| modo::Error::internal(format!("Failed to enable foreign_keys: {e}")))?;
44    Ok(())
45}
46
47#[cfg(not(feature = "sqlite"))]
48async fn apply_sqlite_pragmas(_conn: &sea_orm::DatabaseConnection) -> Result<(), modo::Error> {
49    Err(modo::Error::internal(
50        "SQLite URL provided but `sqlite` feature is not enabled",
51    ))
52}
53
54/// Redact credentials from database URL for logging.
55fn redact_url(url: &str) -> String {
56    if let Some(scheme_end) = url.find("://") {
57        let authority_start = scheme_end + 3;
58        if let Some(relative_at) = url[authority_start..].find('@') {
59            let prefix = &url[..authority_start];
60            let suffix = &url[authority_start + relative_at..];
61            return format!("{prefix}***{suffix}");
62        }
63    }
64    url.to_string()
65}