use std::sync::{Arc, Mutex, MutexGuard};
use crate::blocking_trait_def::DbConnectionBlocking;
use crate::error::DbError;
use crate::trait_def::DbConnection;
use toolu_orm_core::row::FromRow;
use toolu_orm_core::value::Value;
pub struct RusqliteConnection {
inner: Arc<Mutex<rusqlite::Connection>>,
}
impl RusqliteConnection {
#[must_use]
pub fn from_connection(conn: rusqlite::Connection) -> Self {
Self {
inner: Arc::new(Mutex::new(conn)),
}
}
pub async fn open_in_memory() -> Result<Self, DbError> {
let conn = tokio::task::spawn_blocking(|| {
rusqlite::Connection::open_in_memory().map_err(|e| DbError::Connection(e.to_string()))
})
.await
.map_err(|e| DbError::Connection(e.to_string()))??;
Ok(Self::from_connection(conn))
}
pub async fn open(path: &str) -> Result<Self, DbError> {
let path = path.to_owned();
let conn = tokio::task::spawn_blocking(move || {
rusqlite::Connection::open(&path).map_err(|e| DbError::Connection(e.to_string()))
})
.await
.map_err(|e| DbError::Connection(e.to_string()))??;
Ok(Self::from_connection(conn))
}
fn handle(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
fn lock(&self) -> Result<MutexGuard<'_, rusqlite::Connection>, DbError> {
self.inner.lock().map_err(|e| {
DbError::Connection(format!(
"the rusqlite connection lock is poisoned by an earlier panic: {e}"
))
})
}
}
fn to_sql_params(params: &[Value]) -> Vec<&dyn rusqlite::types::ToSql> {
params
.iter()
.map(|v| v as &dyn rusqlite::types::ToSql)
.collect()
}
fn join_failure(error: &tokio::task::JoinError) -> DbError {
DbError::Connection(format!(
"the rusqlite blocking task did not complete: {error}"
))
}
impl DbConnectionBlocking for RusqliteConnection {
fn execute_sql(&self, sql: &str, params: Vec<Value>) -> Result<u64, DbError> {
let guard = self.lock()?;
let affected = guard
.execute(sql, to_sql_params(¶ms).as_slice())
.map_err(|e| DbError::Query(e.to_string()))?;
Ok(affected as u64)
}
fn query_map<T: FromRow>(&self, sql: &str, params: Vec<Value>) -> Result<Vec<T>, DbError> {
let guard = self.lock()?;
let mut stmt = guard
.prepare(sql)
.map_err(|e| DbError::Query(e.to_string()))?;
let mut rows = stmt
.query(to_sql_params(¶ms).as_slice())
.map_err(|e| DbError::Query(e.to_string()))?;
let mut results = Vec::new();
while let Some(row) = rows.next().map_err(|e| DbError::Query(e.to_string()))? {
#[cfg(all(feature = "rusqlite", any(feature = "postgres", feature = "libsql"),))]
results.push(T::from_rusqlite_row(row).map_err(DbError::from)?);
#[cfg(all(
feature = "rusqlite",
not(feature = "postgres"),
not(feature = "libsql"),
))]
results.push(T::from_row(row).map_err(DbError::from)?);
}
Ok(results)
}
fn execute_batch(&self, sql: &str) -> Result<(), DbError> {
let guard = self.lock()?;
guard
.execute_batch(sql)
.map_err(|e| DbError::Query(e.to_string()))
}
}
#[async_trait::async_trait]
impl DbConnection for RusqliteConnection {
async fn execute_sql(&self, sql: &str, params: Vec<Value>) -> Result<u64, DbError> {
let handle = self.handle();
let sql = sql.to_owned();
tokio::task::spawn_blocking(move || DbConnectionBlocking::execute_sql(&handle, &sql, params))
.await
.map_err(|e| join_failure(&e))?
}
async fn query_map<T: FromRow + Send + 'static>(
&self,
sql: &str,
params: Vec<Value>,
) -> Result<Vec<T>, DbError> {
let handle = self.handle();
let sql = sql.to_owned();
tokio::task::spawn_blocking(move || DbConnectionBlocking::query_map(&handle, &sql, params))
.await
.map_err(|e| join_failure(&e))?
}
async fn execute_batch(&self, sql: &str) -> Result<(), DbError> {
let handle = self.handle();
let sql = sql.to_owned();
tokio::task::spawn_blocking(move || DbConnectionBlocking::execute_batch(&handle, &sql))
.await
.map_err(|e| join_failure(&e))?
}
}