use std::path::Path;
use std::sync::Mutex;
use rusqlite::Connection;
#[derive(Debug)]
pub enum RusqliteError {
Sqlite(rusqlite::Error),
NotFound,
Locked,
BatchTooLarge { actual: usize, maximum: usize },
DuplicateBatchKey { first: usize, duplicate: usize },
Validation(String),
}
impl std::fmt::Display for RusqliteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Sqlite(error) => write!(f, "sqlite error: {error}"),
Self::NotFound => write!(f, "not found"),
Self::Locked => write!(f, "connection mutex poisoned"),
Self::BatchTooLarge { actual, maximum } => {
write!(f, "batch size {actual} exceeds maximum of {maximum}",)
}
Self::DuplicateBatchKey { first, duplicate } => write!(
f,
"duplicate primary key in batch at positions {first} and {duplicate}",
),
Self::Validation(message) => write!(f, "validation error: {message}"),
}
}
}
impl std::error::Error for RusqliteError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Sqlite(error) => Some(error),
_ => None,
}
}
}
impl From<rusqlite::Error> for RusqliteError {
fn from(value: rusqlite::Error) -> Self {
Self::Sqlite(value)
}
}
pub struct RusqliteRuntime {
conn: Mutex<Connection>,
}
impl RusqliteRuntime {
pub fn open_in_memory() -> Result<Self, RusqliteError> {
let conn = Connection::open_in_memory()?;
configure(&conn)?;
Ok(Self {
conn: Mutex::new(conn),
})
}
pub fn open(path: impl AsRef<Path>) -> Result<Self, RusqliteError> {
let conn = Connection::open(path)?;
configure(&conn)?;
Ok(Self {
conn: Mutex::new(conn),
})
}
pub fn with_connection<F, T>(&self, f: F) -> Result<T, RusqliteError>
where
F: FnOnce(&mut Connection) -> Result<T, RusqliteError>,
{
let mut guard = self.conn.lock().map_err(|_| RusqliteError::Locked)?;
f(&mut guard)
}
}
fn configure(conn: &Connection) -> Result<(), rusqlite::Error> {
conn.pragma_update(None, "foreign_keys", "ON")?;
let _ = conn.pragma_update(None, "journal_mode", "WAL");
Ok(())
}