Skip to main content

scud/db/
mod.rs

1//! SQLite database for event logging, transcript storage, and session history.
2
3use anyhow::Result;
4use chrono::{DateTime, Utc};
5use rusqlite::{Connection, OpenFlags};
6use std::path::{Path, PathBuf};
7use std::sync::Mutex;
8
9pub mod events;
10pub mod schema;
11pub mod sessions;
12pub mod transcripts;
13
14/// Database connection wrapper with lazy initialization
15pub struct Database {
16    path: PathBuf,
17    conn: Mutex<Option<Connection>>,
18}
19
20impl Database {
21    pub fn new(project_root: &Path) -> Self {
22        let path = project_root.join(".scud").join("scud.db");
23        Self {
24            path,
25            conn: Mutex::new(None),
26        }
27    }
28
29    /// Get or create database connection
30    pub fn connection(&self) -> Result<std::sync::MutexGuard<'_, Option<Connection>>> {
31        let mut guard = self.conn.lock().unwrap();
32        if guard.is_none() {
33            let conn = Connection::open_with_flags(
34                &self.path,
35                OpenFlags::SQLITE_OPEN_READ_WRITE
36                    | OpenFlags::SQLITE_OPEN_CREATE
37                    | OpenFlags::SQLITE_OPEN_FULL_MUTEX,
38            )?;
39            // Enable WAL mode for better concurrent access
40            // Note: foreign_keys not enforced - events can be written before session records
41            conn.execute_batch("PRAGMA journal_mode=WAL;")?;
42            *guard = Some(conn);
43        }
44        Ok(guard)
45    }
46
47    /// Initialize database with schema
48    pub fn initialize(&self) -> Result<()> {
49        let guard = self.connection()?;
50        let conn = guard.as_ref().unwrap();
51        schema::create_tables(conn)?;
52        Ok(())
53    }
54
55    pub fn path(&self) -> &Path {
56        &self.path
57    }
58
59    /// Get events for a session with optional limits
60    pub fn get_events_for_session_limited(
61        &self,
62        session_id: &str,
63        limit: Option<usize>,
64        since: Option<DateTime<Utc>>,
65    ) -> Result<Vec<crate::commands::swarm::events::AgentEvent>> {
66        let guard = self.connection()?;
67        let conn = guard.as_ref().unwrap();
68        events::get_events_for_session_limited(conn, session_id, limit, since)
69    }
70}