Skip to main content

storekit/
open.rs

1use crate::migrate::{self, Migrations};
2use rusqlite::Connection;
3use std::path::Path;
4use std::sync::{Arc, Mutex};
5use std::time::Duration;
6
7pub type Db = Arc<Mutex<Connection>>;
8
9pub const DESK_PEN_WAIT: Duration = Duration::from_secs(1);
10
11pub const JOB_PEN_WAIT: Duration = Duration::from_secs(30);
12
13pub const SLOW_SQL_ALARM: Duration = Duration::from_millis(250);
14
15pub fn open(
16    path: &Path,
17    migrations: &Migrations,
18    mark: Option<fn(&str, Duration)>,
19) -> rusqlite::Result<Connection> {
20    let mut connection = Connection::open(path)?;
21    connection.profile(mark);
22    connection.busy_timeout(DESK_PEN_WAIT)?;
23    connection.pragma_update(None, "foreign_keys", "ON")?;
24    connection.pragma_update(None, "journal_mode", "WAL")?;
25    connection.pragma_update(None, "synchronous", "NORMAL")?;
26    connection.pragma_update(None, "cache_size", "-32768")?;
27    connection.pragma_update(None, "mmap_size", "268435456")?;
28    migrate::run(&connection, migrations)?;
29    Ok(connection)
30}
31
32pub fn open_companion(
33    path: &Path,
34    mark: Option<fn(&str, Duration)>,
35) -> rusqlite::Result<Connection> {
36    let mut connection = Connection::open(path)?;
37    connection.profile(mark);
38    connection.busy_timeout(JOB_PEN_WAIT)?;
39    connection.pragma_update(None, "foreign_keys", "ON")?;
40    connection.pragma_update(None, "journal_mode", "WAL")?;
41    connection.pragma_update(None, "synchronous", "NORMAL")?;
42    Ok(connection)
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use crate::migrate::{Migrations, Step};
49    use std::path::PathBuf;
50
51    fn scratch(tag: &str) -> tempfile::TempDir {
52        let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tmp/testscratch");
53        std::fs::create_dir_all(&root).unwrap();
54        tempfile::Builder::new()
55            .prefix(tag)
56            .tempdir_in(root)
57            .unwrap()
58    }
59
60    #[test]
61    fn the_pen_waits_and_slow_alarm_keep_the_estate_ruling() {
62        assert_eq!(DESK_PEN_WAIT, Duration::from_secs(1));
63        assert_eq!(JOB_PEN_WAIT, Duration::from_secs(30));
64        assert_eq!(SLOW_SQL_ALARM, Duration::from_millis(250));
65    }
66
67    #[test]
68    fn open_applies_the_desk_pragmas_and_migrates() {
69        static STEPS: &[Step] = &[Step::new(1, "CREATE TABLE desk(id INTEGER);", None)];
70        let migrations = Migrations {
71            version: 1,
72            steps: STEPS,
73            before: None,
74        };
75        let holder = scratch("open-pragmas-");
76        let connection = open(&holder.path().join("desk.db"), &migrations, None).unwrap();
77        assert_eq!(pragma::<String>(&connection, "journal_mode"), "wal");
78        assert_eq!(pragma::<i64>(&connection, "foreign_keys"), 1);
79        assert_eq!(pragma::<i64>(&connection, "synchronous"), 1);
80        assert_eq!(pragma::<i64>(&connection, "cache_size"), -32_768);
81        assert_eq!(pragma::<i64>(&connection, "mmap_size"), 268_435_456);
82    }
83
84    #[test]
85    fn open_companion_never_migrates() {
86        let holder = scratch("companion-no-migrate-");
87        let connection = open_companion(&holder.path().join("desk.db"), None).unwrap();
88        assert!(
89            connection
90                .query_row("SELECT value FROM schema_meta", [], |row| row
91                    .get::<_, String>(0))
92                .is_err()
93        );
94    }
95
96    fn pragma<T: rusqlite::types::FromSql>(connection: &Connection, name: &str) -> T {
97        connection
98            .query_row(&format!("PRAGMA {name}"), [], |row| row.get(0))
99            .unwrap()
100    }
101}