//! SQLite database connection initialisation and schema migrations. use std::sync::{Arc, Mutex}; /// A shared SQLite connection wrapped for thread-safe access. #[derive(Clone)] pub struct DbConn { conn: Arc>, } impl DbConn { /// Execute a closure with a reference to the underlying connection. pub fn with(&self, f: F) -> anyhow::Result where F: FnOnce(&rusqlite::Connection) -> anyhow::Result, { let conn = self .conn .lock() .map_err(|e| anyhow::anyhow!("db lock poisoned: {e}"))?; f(&conn) } } const SCHEMA_SQL: &str = r#" CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, title TEXT NOT NULL DEFAULT '', model TEXT NOT NULL DEFAULT '', workspace_roots TEXT NOT NULL DEFAULT '[]', message_count INTEGER NOT NULL DEFAULT 0, token_count INTEGER NOT NULL DEFAULT 0, archived INTEGER NOT NULL DEFAULT 0, summary TEXT ); CREATE TABLE IF NOT EXISTS settings ( id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL DEFAULT '{}', updated_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS conversations ( session_id TEXT PRIMARY KEY, data TEXT NOT NULL DEFAULT '{}', updated_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS memories ( name TEXT PRIMARY KEY, data TEXT NOT NULL DEFAULT '{}', updated_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS edit_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, entry TEXT NOT NULL, created_at INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_edit_logs_session ON edit_logs (session_id); "#; /// Initialise a shared SQLite connection at the given path. pub fn init_db(db_path: &str) -> anyhow::Result { let conn = rusqlite::Connection::open(db_path) .map_err(|e| anyhow::anyhow!("failed to open SQLite database at '{db_path}': {e}"))?; conn.execute_batch("PRAGMA journal_mode = WAL;")?; conn.execute_batch("PRAGMA busy_timeout = 5000;")?; Ok(DbConn { conn: Arc::new(Mutex::new(conn)), }) } /// Run embedded SQL schema migrations. pub fn run_migrations(db: &DbConn) -> anyhow::Result<()> { db.with(|conn| { conn.execute_batch(SCHEMA_SQL) .map_err(|e| anyhow::anyhow!("failed to execute database schema migrations: {e}")) })?; Ok(()) }