use anyhow::Result; use sqlx::{ sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions}, ConnectOptions, Connection, SqlitePool, }; use std::str::FromStr; use std::time::Duration; use tracing::info; pub type Pool = SqlitePool; pub async fn init_pool(database_url: &str, max_connections: u32) -> Result { info!("Connecting to database: {}", database_url); // Per-connection options so EVERY pooled connection gets them: WAL for // reader/writer concurrency, foreign keys on, and a busy_timeout so a // transient SQLITE_BUSY under concurrent access waits-and-retries. let opts = SqliteConnectOptions::from_str(database_url)? .create_if_missing(true) .journal_mode(SqliteJournalMode::Wal) .foreign_keys(true) .busy_timeout(Duration::from_secs(5)); // Establish WAL on the file via ONE connection BEFORE the pool opens. // Switching a fresh DB to WAL is a one-time file-level change; letting // several pooled connections do it concurrently at warm-up races that // switch and can surface a spurious lock. Serialize it here so every // pooled connection thereafter only re-asserts an already-WAL file. { let mut conn = opts.clone().connect().await?; sqlx::query("PRAGMA journal_mode=WAL") .execute(&mut conn) .await?; conn.close().await?; } let pool = SqlitePoolOptions::new() .max_connections(max_connections) .connect_with(opts) .await?; Ok(pool) } pub async fn run_migrations(pool: &Pool) -> Result<()> { info!("Running database migrations"); sqlx::migrate!("./migrations").run(pool).await?; Ok(()) }