coven_database/
sqlite_temporary.rs1use coven_foundation::files::TemporaryFolder;
9use coven_foundation::locks::lock_unpoisoned;
10use std::path::{Path, PathBuf};
11use std::sync::Mutex;
12
13struct Temporary {
15 directory: Option<TemporaryFolder>,
16 opened: bool,
17}
18
19static TEMPORARY: Mutex<Temporary> = Mutex::new(Temporary { directory: None, opened: false });
20
21#[derive(Debug, thiserror::Error)]
23pub enum TemporaryDirectoryError {
24 #[error("the temporary folder is named before any store opens")]
27 AfterOpening,
28 #[error("the temporary folder is already {}", directory.display())]
30 AlreadyNamed {
31 directory: PathBuf,
33 },
34 #[error("{} is not a folder SQLite can keep temporary tables in", directory.display())]
37 NotAFolder {
38 directory: PathBuf,
40 },
41}
42
43pub fn set_temporary_directory(directory: &Path) -> Result<(), TemporaryDirectoryError> {
46 let mut temporary = lock_unpoisoned(&TEMPORARY);
47 if let Some(named) = &temporary.directory {
48 if named.path() == directory {
49 return Ok(());
50 }
51 return Err(TemporaryDirectoryError::AlreadyNamed { directory: named.path().to_owned() });
52 }
53 if temporary.opened {
54 return Err(TemporaryDirectoryError::AfterOpening);
55 }
56 let unusable = || TemporaryDirectoryError::NotAFolder { directory: directory.to_owned() };
57 let folder = TemporaryFolder::at(directory).ok_or_else(unusable)?;
58 let text = folder.path().to_str().ok_or_else(unusable)?;
59 let text = std::ffi::CString::new(text).map_err(|_nul| unusable())?;
60 unsafe {
64 let copy = rusqlite::ffi::sqlite3_mprintf(c"%s".as_ptr(), text.as_ptr());
65 if copy.is_null() {
66 return Err(unusable());
67 }
68 rusqlite::ffi::sqlite3_temp_directory = copy;
69 }
70 temporary.directory = Some(folder);
71 Ok(())
72}
73
74pub(crate) fn opening() -> Result<(), crate::DbError> {
83 if unsafe { rusqlite::ffi::sqlite3_compileoption_used(c"ENABLE_MEMORY_MANAGEMENT".as_ptr()) }
85 == 0
86 {
87 return Err(crate::DbError::Invariant {
88 what: "SQLite keeps every connection's unused pages in one pool (SQLITE_ENABLE_MEMORY_MANAGEMENT)",
89 });
90 }
91 for option in [c"TEMP_STORE=2", c"TEMP_STORE=3"] {
92 if unsafe { rusqlite::ffi::sqlite3_compileoption_used(option.as_ptr()) } != 0 {
94 return Err(crate::DbError::Invariant {
95 what: "SQLite keeps temporary tables in files unless a connection asks for memory (SQLITE_TEMP_STORE below 2)",
96 });
97 }
98 }
99 lock_unpoisoned(&TEMPORARY).opened = true;
100 Ok(())
101}