1use std::path::Path;
13use std::time::Duration;
14
15use rusqlite::{Connection, OpenFlags};
16
17use crate::error::{StoreError, StoreResult};
18use crate::schema::{schema_version, SCHEMA_SQL};
19
20#[derive(Debug, Clone, Default)]
22pub struct StoreOptions {
23 pub readonly: bool,
26}
27
28pub fn open_database(path: &Path, options: &StoreOptions) -> StoreResult<Connection> {
34 if !options.readonly {
35 if let Some(parent) = path.parent() {
36 if !parent.as_os_str().is_empty() {
37 std::fs::create_dir_all(parent)?;
38 }
39 }
40 }
41
42 let flags = if options.readonly {
43 OpenFlags::SQLITE_OPEN_READ_ONLY
44 | OpenFlags::SQLITE_OPEN_URI
45 | OpenFlags::SQLITE_OPEN_NO_MUTEX
46 } else {
47 OpenFlags::default()
48 };
49
50 let conn = Connection::open_with_flags(path, flags)?;
51 configure(&conn, options.readonly)?;
52 ensure_schema(&conn, options.readonly)?;
53 Ok(conn)
54}
55
56pub fn open_in_memory() -> StoreResult<Connection> {
59 let conn = Connection::open_in_memory()?;
60 configure(&conn, false)?;
61 ensure_schema(&conn, false)?;
62 Ok(conn)
63}
64
65fn configure(conn: &Connection, readonly: bool) -> StoreResult<()> {
66 if !readonly {
67 let _mode: String = conn.query_row("PRAGMA journal_mode = WAL", [], |row| row.get(0))?;
70 }
71 conn.pragma_update(None, "foreign_keys", "ON")?;
72 conn.busy_timeout(Duration::from_millis(5000))?;
73 conn.pragma_update(None, "synchronous", "NORMAL")?;
74 conn.pragma_update(None, "cache_size", -64000)?;
75 Ok(())
76}
77
78fn ensure_schema(conn: &Connection, readonly: bool) -> StoreResult<()> {
81 let contract = schema_version();
82 let user_version: i64 = conn.query_row("PRAGMA user_version", [], |row| row.get(0))?;
83 let has_tables: bool = conn.query_row(
84 "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'observations')",
85 [],
86 |row| row.get(0),
87 )?;
88
89 if !has_tables {
90 if readonly {
91 return Err(StoreError::UninitializedDatabase);
92 }
93 conn.execute_batch(SCHEMA_SQL)?;
94 return Ok(());
95 }
96
97 if user_version < contract.min_compatible || user_version == 0 {
100 return Err(StoreError::SchemaTooOld {
101 found: user_version,
102 min_compatible: contract.min_compatible,
103 });
104 }
105 if user_version > contract.version {
106 return Err(StoreError::SchemaTooNew {
107 found: user_version,
108 supported: contract.version,
109 });
110 }
111 Ok(())
112}