use std::cell::RefCell;
use std::cell::RefMut;
use std::collections::HashMap;
use std::ops::Range;
use std::path::Path;
use ic_stable_structures::memory_manager::MemoryId;
use ic_stable_structures::{DefaultMemoryImpl, memory_manager::MemoryManager};
const FS_MEMORY_RANGE: Range<u8> = 101..119;
const DEFAULT_MOUNTED_DB_ID: u8 = 120;
const DEFAULT_DB_FILE_NAME: &str = "/DB/main.db";
pub use ic_wasi_polyfill;
pub use rusqlite;
pub use rusqlite::*;
thread_local! {
pub static MEMORY_MANAGER: RefCell<MemoryManager<DefaultMemoryImpl>> = {
let m = MemoryManager::init(DefaultMemoryImpl::default());
ic_wasi_polyfill::init_with_memory_manager(&[0u8; 32], &[("SQLITE_TMPDIR", "/tmp")], &m, FS_MEMORY_RANGE);
let _ = std::fs::create_dir_all("/tmp");
RefCell::new(m)
};
pub static CONNECTION: RefCell<Option<Connection>> = RefCell::new(Some(create_connection()));
pub static CONNECTION_SETUP: RefCell<ConnectionConfig> = RefCell::new(ConnectionConfig::default());
}
#[derive(Clone, Debug)]
pub struct ConnectionConfig {
pub db_file_name: String,
pub db_file_mount_id: Option<u8>,
pub pragma_settings: HashMap<String, String>,
}
impl ConnectionConfig {
pub fn new() -> Self {
ConnectionConfig::default()
}
}
impl Default for ConnectionConfig {
fn default() -> Self {
let mut default_pragmas = HashMap::new();
default_pragmas.insert("journal_mode".to_string(), "PERSIST".to_string());
default_pragmas.insert("synchronous".to_string(), "OFF".to_string());
default_pragmas.insert("locking_mode".to_string(), "EXCLUSIVE".to_string());
default_pragmas.insert("temp_store".to_string(), "MEMORY".to_string());
default_pragmas.insert("page_size".to_string(), "4096".to_string());
default_pragmas.insert("cache_size".to_string(), "500000".to_string());
Self {
db_file_name: DEFAULT_DB_FILE_NAME.to_string(),
db_file_mount_id: Some(DEFAULT_MOUNTED_DB_ID),
pragma_settings: default_pragmas,
}
}
}
pub fn close_connection() {
CONNECTION.with(|conn| {
let mut conn_mut = conn.borrow_mut();
if let Some(c) = conn_mut.take() {
drop(c);
}
})
}
pub fn get_connection_config() -> ConnectionConfig {
CONNECTION_SETUP.with(|setup| setup.borrow().clone())
}
pub fn set_connection_config(new_setup: ConnectionConfig) {
CONNECTION_SETUP.with(|setup| {
let mut s = setup.borrow_mut();
*s = new_setup
});
}
fn create_connection() -> Connection {
MEMORY_MANAGER.with_borrow(|_m| {
});
let setup = get_connection_config();
ic_wasi_polyfill::unmount_memory_file(&setup.db_file_name);
if let Some(mount_id) = setup.db_file_mount_id {
let memory = MEMORY_MANAGER.with_borrow(|m| m.get(MemoryId::new(mount_id)));
ic_wasi_polyfill::mount_memory_file(&setup.db_file_name, Box::new(memory));
}
let _ = std::fs::remove_dir_all(format!("{}.lock", setup.db_file_name));
let path = Path::new(&setup.db_file_name).parent();
if let Some(path) = path {
let _ = std::fs::create_dir_all(path);
}
let conn =
rusqlite::Connection::open(setup.db_file_name).expect("Failed opening the database!");
for (k, v) in &setup.pragma_settings {
conn.pragma_update(None, k, v as &dyn ToSql).unwrap();
}
conn
}
pub fn with_connection<F, R>(f: F) -> R
where
F: FnOnce(RefMut<'_, Connection>) -> R,
{
CONNECTION.with(|conn| {
let mut conn_mut: RefMut<'_, Option<_>> = conn.borrow_mut();
if conn_mut.is_none() {
*conn_mut = Some(create_connection());
}
let conn_ref = RefMut::map(conn_mut, |opt| opt.as_mut().unwrap());
f(conn_ref)
})
}