Skip to main content

tuillem_db/
lib.rs

1pub mod messages;
2pub mod search;
3pub mod sessions;
4
5use rusqlite::Connection;
6use thiserror::Error;
7
8#[derive(Error, Debug)]
9pub enum DbError {
10    #[error("sqlite error: {0}")]
11    Sqlite(#[from] rusqlite::Error),
12    #[error("migration error: {0}")]
13    Migration(String),
14    #[error("not found: {0}")]
15    NotFound(String),
16}
17
18pub struct Db {
19    conn: Connection,
20}
21
22impl Db {
23    pub fn open(path: &str) -> Result<Self, DbError> {
24        let conn = Connection::open(path)?;
25        conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?;
26        let db = Self { conn };
27        db.migrate()?;
28        Ok(db)
29    }
30
31    pub fn open_in_memory() -> Result<Self, DbError> {
32        let conn = Connection::open_in_memory()?;
33        conn.execute_batch("PRAGMA foreign_keys=ON;")?;
34        let db = Self { conn };
35        db.migrate()?;
36        Ok(db)
37    }
38
39    pub fn conn(&self) -> &Connection {
40        &self.conn
41    }
42
43    fn migrate(&self) -> Result<(), DbError> {
44        let current_version = self.current_schema_version();
45        if current_version < 1 {
46            let sql = include_str!("../migrations/001_initial.sql");
47            self.conn.execute_batch(sql)?;
48        }
49        Ok(())
50    }
51
52    fn current_schema_version(&self) -> i64 {
53        self.conn
54            .query_row("SELECT MAX(version) FROM schema_version", [], |row| {
55                row.get(0)
56            })
57            .unwrap_or(0)
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_open_in_memory() {
67        let db = Db::open_in_memory().expect("should open in-memory db");
68        let version: i64 = db
69            .conn()
70            .query_row("SELECT MAX(version) FROM schema_version", [], |row| {
71                row.get(0)
72            })
73            .unwrap();
74        assert_eq!(version, 1);
75    }
76
77    #[test]
78    fn test_migrate_idempotent() {
79        let db = Db::open_in_memory().expect("should open in-memory db");
80        // Running migrate again should not fail
81        db.migrate().expect("idempotent migration should succeed");
82        let version: i64 = db
83            .conn()
84            .query_row("SELECT MAX(version) FROM schema_version", [], |row| {
85                row.get(0)
86            })
87            .unwrap();
88        assert_eq!(version, 1);
89    }
90}