use std::{fmt::Debug, num::NonZeroU32, path::Path, sync::Mutex};
use rusqlite::{OpenFlags, Transaction, TransactionBehavior};
pub trait ConnectionOpener {
const MAX_SCHEMA_VERSION: u32;
type Error: From<rusqlite::Error>;
fn setup(_conn: &mut rusqlite::Connection) -> Result<(), Self::Error> {
Ok(())
}
fn create(tx: &mut Transaction<'_>) -> Result<(), Self::Error>;
fn upgrade(tx: &mut Transaction<'_>, to_version: NonZeroU32) -> Result<(), Self::Error>;
}
pub struct Connection {
conn: Mutex<rusqlite::Connection>,
}
impl Connection {
pub fn new<O>(path: &Path) -> Result<Self, O::Error>
where
O: ConnectionOpener,
{
let flags = OpenFlags::SQLITE_OPEN_NO_MUTEX | OpenFlags::SQLITE_OPEN_EXRESCODE | OpenFlags::SQLITE_OPEN_CREATE | OpenFlags::SQLITE_OPEN_READ_WRITE;
let mut conn = rusqlite::Connection::open_with_flags(path, flags)?;
O::setup(&mut conn)?;
let mut tx = conn.transaction_with_behavior(TransactionBehavior::Exclusive)?;
match tx.query_row_and_then("PRAGMA user_version", [], |row| row.get(0)) {
Ok(mut version @ 1..) => {
while version < O::MAX_SCHEMA_VERSION {
O::upgrade(&mut tx, NonZeroU32::new(version + 1).unwrap())?;
version += 1;
}
}
Ok(0) => O::create(&mut tx)?,
Err(err) => Err(err)?,
}
tx.execute_batch(&format!("PRAGMA user_version = {}", O::MAX_SCHEMA_VERSION))?;
tx.commit()?;
Ok(Self::with_connection(conn))
}
fn with_connection(conn: rusqlite::Connection) -> Self {
Self {
conn: Mutex::new(conn),
}
}
pub fn read<T, E>(&self, f: impl FnOnce(&Transaction<'_>) -> Result<T, E>) -> Result<T, E> {
let mut conn = self.conn.lock().unwrap();
let tx = conn
.transaction_with_behavior(TransactionBehavior::Immediate)
.unwrap();
f(&tx)
}
pub fn write<T, E>(&self, f: impl FnOnce(&mut Transaction<'_>) -> Result<T, E>) -> Result<T, E>
where
E: From<rusqlite::Error>,
{
let mut conn = self.conn.lock().unwrap();
let mut tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
let result = f(&mut tx)?;
tx.commit()?;
Ok(result)
}
}
impl Debug for Connection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Connection { .. }")
}
}