use rusqlite::Connection;
pub trait StorageBackend {
fn execute_sql(
&self,
sql: &str,
params: &[&dyn rusqlite::types::ToSql],
) -> Result<usize, crate::errors::AppError>;
fn query_one<T, F>(
&self,
sql: &str,
params: &[&dyn rusqlite::types::ToSql],
f: F,
) -> Result<Option<T>, crate::errors::AppError>
where
F: FnOnce(&rusqlite::Row<'_>) -> Result<T, rusqlite::Error>;
fn as_connection(&self) -> &Connection;
}
pub struct SqliteBackend {
conn: Connection,
}
impl SqliteBackend {
pub fn new(conn: Connection) -> Self {
Self { conn }
}
pub fn into_inner(self) -> Connection {
self.conn
}
}
impl StorageBackend for SqliteBackend {
fn execute_sql(
&self,
sql: &str,
params: &[&dyn rusqlite::types::ToSql],
) -> Result<usize, crate::errors::AppError> {
self.conn
.execute(sql, params)
.map_err(crate::errors::AppError::Database)
}
fn query_one<T, F>(
&self,
sql: &str,
params: &[&dyn rusqlite::types::ToSql],
f: F,
) -> Result<Option<T>, crate::errors::AppError>
where
F: FnOnce(&rusqlite::Row<'_>) -> Result<T, rusqlite::Error>,
{
match self.conn.query_row(sql, params, f) {
Ok(val) => Ok(Some(val)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(crate::errors::AppError::Database(e)),
}
}
fn as_connection(&self) -> &Connection {
&self.conn
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sqlite_backend_wraps_connection() {
let conn = Connection::open_in_memory().unwrap();
conn.execute_batch("CREATE TABLE test (id INTEGER PRIMARY KEY, val TEXT)")
.unwrap();
let backend = SqliteBackend::new(conn);
let affected = backend
.execute_sql(
"INSERT INTO test (val) VALUES (?1)",
&[&"hello" as &dyn rusqlite::types::ToSql],
)
.unwrap();
assert_eq!(affected, 1);
let result: Option<String> = backend
.query_one("SELECT val FROM test WHERE id = 1", &[], |r| r.get(0))
.unwrap();
assert_eq!(result, Some("hello".to_string()));
}
}