pub mod migrate;
pub mod models;
pub mod queries;
use crossbeam_queue::ArrayQueue;
use rusqlite::Connection;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use crate::error::LificError;
const READ_POOL_SIZE: usize = 8;
#[derive(Clone)]
pub struct DbPool {
writer: Arc<Mutex<Connection>>,
readers: Arc<ArrayQueue<Connection>>,
path: PathBuf,
}
pub struct ReadConn {
conn: Option<Connection>,
pool: Arc<ArrayQueue<Connection>>,
}
impl std::ops::Deref for ReadConn {
type Target = Connection;
fn deref(&self) -> &Connection {
self.conn.as_ref().unwrap()
}
}
impl Drop for ReadConn {
fn drop(&mut self) {
if let Some(conn) = self.conn.take() {
let _ = self.pool.push(conn);
}
}
}
impl DbPool {
pub fn read(&self) -> Result<ReadConn, LificError> {
match self.readers.pop() {
Some(conn) => Ok(ReadConn {
conn: Some(conn),
pool: Arc::clone(&self.readers),
}),
None => {
let conn = open_read_connection(&self.path)?;
Ok(ReadConn {
conn: Some(conn),
pool: Arc::clone(&self.readers),
})
}
}
}
pub fn write(&self) -> Result<std::sync::MutexGuard<'_, Connection>, LificError> {
self.writer
.lock()
.map_err(|e| LificError::Internal(format!("write lock poisoned: {e}")))
}
}
fn apply_pragmas(conn: &Connection) -> Result<(), LificError> {
conn.execute_batch(
"PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
PRAGMA cache_size = -8000;
PRAGMA mmap_size = 67108864;",
)?;
Ok(())
}
fn open_read_connection(path: &Path) -> Result<Connection, LificError> {
let conn = Connection::open_with_flags(
path,
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY
| rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX
| rusqlite::OpenFlags::SQLITE_OPEN_URI,
)?;
conn.execute_batch(
"PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
PRAGMA cache_size = -4000;
PRAGMA mmap_size = 67108864;",
)?;
Ok(conn)
}
#[cfg(test)]
pub fn open_memory() -> Result<DbPool, LificError> {
let conn = Connection::open_in_memory()?;
conn.execute_batch(
"PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;",
)?;
migrate::run(&conn)?;
drop(conn);
let name = format!(
"file:lific_test_{}?mode=memory&cache=shared",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
);
let writer = Connection::open_with_flags(
&name,
rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE
| rusqlite::OpenFlags::SQLITE_OPEN_CREATE
| rusqlite::OpenFlags::SQLITE_OPEN_URI,
)?;
writer.execute_batch("PRAGMA foreign_keys = ON;")?;
migrate::run(&writer)?;
let readers = ArrayQueue::new(READ_POOL_SIZE);
for _ in 0..READ_POOL_SIZE {
let conn = Connection::open_with_flags(
&name,
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY | rusqlite::OpenFlags::SQLITE_OPEN_URI,
)?;
conn.execute_batch("PRAGMA foreign_keys = ON;")?;
let _ = readers.push(conn);
}
Ok(DbPool {
writer: Arc::new(Mutex::new(writer)),
readers: Arc::new(readers),
path: PathBuf::from(&name),
})
}
pub fn open(path: &Path) -> Result<DbPool, LificError> {
let writer = Connection::open(path)?;
apply_pragmas(&writer)?;
migrate::run(&writer)?;
let readers = ArrayQueue::new(READ_POOL_SIZE);
for _ in 0..READ_POOL_SIZE {
let conn = open_read_connection(path)?;
let _ = readers.push(conn);
}
Ok(DbPool {
writer: Arc::new(Mutex::new(writer)),
readers: Arc::new(readers),
path: path.to_path_buf(),
})
}