use std::sync::Arc;
use prax_query::error::{QueryError, QueryResult};
use prax_query::filter::FilterValue;
use prax_query::row::FromRow;
use prax_query::traits::{BoxFuture, Model, QueryEngine};
use rusqlite::types::Value as SqlValue;
use tokio_rusqlite::Connection as RusqliteConnection;
use tracing::trace;
use crate::connection::SqliteConnection;
use crate::pool::SqlitePool;
use crate::row_ref::SqliteRowRef;
use crate::types::filter_value_to_sqlite;
#[derive(Clone)]
pub struct SqliteEngine {
pool: SqlitePool,
tx_conn: Option<Arc<SqliteConnection>>,
}
impl SqliteEngine {
pub fn new(pool: SqlitePool) -> Self {
Self {
pool,
tx_conn: None,
}
}
pub fn pool(&self) -> &SqlitePool {
&self.pool
}
async fn resolve_conn(&self) -> QueryResult<(RusqliteConnection, Option<SqliteConnection>)> {
if let Some(tx) = &self.tx_conn {
Ok((tx.inner().clone(), None))
} else {
let conn = self
.pool
.get()
.await
.map_err(|e| QueryError::connection(e.to_string()).with_source(e))?;
let handle = conn.inner().clone();
Ok((handle, Some(conn)))
}
}
fn bind(params: &[FilterValue]) -> Vec<SqlValue> {
params.iter().map(filter_value_to_sqlite).collect()
}
async fn query_rows<T: Model + FromRow>(
&self,
sql: String,
params: Vec<FilterValue>,
) -> QueryResult<Vec<T>> {
trace!(sql = %sql, "sqlite query_rows");
let (handle, _guard) = self.resolve_conn().await?;
let bound = Self::bind(¶ms);
let snapshots: Vec<SqliteRowRef> = handle
.call(move |c| {
let mut stmt = c.prepare(&sql)?;
let refs: Vec<&dyn rusqlite::ToSql> =
bound.iter().map(|v| v as &dyn rusqlite::ToSql).collect();
let mut rows = stmt.query(refs.as_slice())?;
let mut out = Vec::new();
while let Some(row) = rows.next()? {
out.push(SqliteRowRef::from_rusqlite(row).map_err(|e| {
rusqlite::Error::ToSqlConversionFailure(Box::new(std::io::Error::other(
e.to_string(),
)))
})?);
}
Ok(out)
})
.await
.map_err(|e| QueryError::database(e.to_string()).with_source(e))?;
snapshots
.into_iter()
.map(|r| {
T::from_row(&r).map_err(|e| {
let msg = e.to_string();
QueryError::deserialization(msg).with_source(e)
})
})
.collect()
}
async fn query_first_row<T: Model + FromRow>(
&self,
sql: String,
params: Vec<FilterValue>,
) -> QueryResult<Option<T>> {
trace!(sql = %sql, "sqlite query_first_row");
let (handle, _guard) = self.resolve_conn().await?;
let bound = Self::bind(¶ms);
let snapshot: Option<SqliteRowRef> = handle
.call(move |c| {
let mut stmt = c.prepare(&sql)?;
let refs: Vec<&dyn rusqlite::ToSql> =
bound.iter().map(|v| v as &dyn rusqlite::ToSql).collect();
let mut rows = stmt.query(refs.as_slice())?;
match rows.next()? {
Some(row) => Ok(Some(SqliteRowRef::from_rusqlite(row).map_err(|e| {
rusqlite::Error::ToSqlConversionFailure(Box::new(std::io::Error::other(
e.to_string(),
)))
})?)),
None => Ok(None),
}
})
.await
.map_err(|e| QueryError::database(e.to_string()).with_source(e))?;
snapshot
.map(|r| {
T::from_row(&r).map_err(|e| {
let msg = e.to_string();
QueryError::deserialization(msg).with_source(e)
})
})
.transpose()
}
async fn exec_raw(&self, sql: String, params: Vec<FilterValue>) -> QueryResult<u64> {
let (handle, _guard) = self.resolve_conn().await?;
let bound = Self::bind(¶ms);
let n = handle
.call(move |c| {
let refs: Vec<&dyn rusqlite::ToSql> =
bound.iter().map(|v| v as &dyn rusqlite::ToSql).collect();
Ok(c.execute(&sql, refs.as_slice())?)
})
.await
.map_err(|e| QueryError::database(e.to_string()).with_source(e))?;
Ok(n as u64)
}
async fn count_rows(&self, sql: String, params: Vec<FilterValue>) -> QueryResult<u64> {
let (handle, _guard) = self.resolve_conn().await?;
let bound = Self::bind(¶ms);
let n = handle
.call(move |c| {
let refs: Vec<&dyn rusqlite::ToSql> =
bound.iter().map(|v| v as &dyn rusqlite::ToSql).collect();
let mut stmt = c.prepare(&sql)?;
let n: i64 = stmt.query_row(refs.as_slice(), |r| r.get(0))?;
Ok(n)
})
.await
.map_err(|e| QueryError::database(e.to_string()).with_source(e))?;
Ok(n as u64)
}
}
impl QueryEngine for SqliteEngine {
fn dialect(&self) -> &dyn prax_query::dialect::SqlDialect {
&prax_query::dialect::Sqlite
}
fn query_many<T: Model + FromRow + Send + 'static>(
&self,
sql: &str,
params: Vec<FilterValue>,
) -> BoxFuture<'_, QueryResult<Vec<T>>> {
let sql = sql.to_string();
Box::pin(self.query_rows::<T>(sql, params))
}
fn query_one<T: Model + FromRow + Send + 'static>(
&self,
sql: &str,
params: Vec<FilterValue>,
) -> BoxFuture<'_, QueryResult<T>> {
let sql = sql.to_string();
Box::pin(async move {
self.query_first_row::<T>(sql, params)
.await?
.ok_or_else(|| QueryError::not_found(T::MODEL_NAME))
})
}
fn query_optional<T: Model + FromRow + Send + 'static>(
&self,
sql: &str,
params: Vec<FilterValue>,
) -> BoxFuture<'_, QueryResult<Option<T>>> {
let sql = sql.to_string();
Box::pin(self.query_first_row::<T>(sql, params))
}
fn execute_insert<T: Model + FromRow + Send + 'static>(
&self,
sql: &str,
params: Vec<FilterValue>,
) -> BoxFuture<'_, QueryResult<T>> {
let sql = sql.to_string();
Box::pin(async move {
self.query_first_row::<T>(sql, params)
.await?
.ok_or_else(|| QueryError::not_found(T::MODEL_NAME))
})
}
fn execute_update<T: Model + FromRow + Send + 'static>(
&self,
sql: &str,
params: Vec<FilterValue>,
) -> BoxFuture<'_, QueryResult<Vec<T>>> {
let sql = sql.to_string();
Box::pin(self.query_rows::<T>(sql, params))
}
fn execute_delete(
&self,
sql: &str,
params: Vec<FilterValue>,
) -> BoxFuture<'_, QueryResult<u64>> {
let sql = sql.to_string();
Box::pin(self.exec_raw(sql, params))
}
fn execute_raw(&self, sql: &str, params: Vec<FilterValue>) -> BoxFuture<'_, QueryResult<u64>> {
let sql = sql.to_string();
Box::pin(self.exec_raw(sql, params))
}
fn count(&self, sql: &str, params: Vec<FilterValue>) -> BoxFuture<'_, QueryResult<u64>> {
let sql = sql.to_string();
Box::pin(self.count_rows(sql, params))
}
fn aggregate_query(
&self,
sql: &str,
params: Vec<FilterValue>,
) -> BoxFuture<'_, QueryResult<Vec<std::collections::HashMap<String, FilterValue>>>> {
let sql = sql.to_string();
Box::pin(async move {
trace!(sql = %sql, "sqlite aggregate_query");
let (handle, _guard) = self.resolve_conn().await?;
let bound = Self::bind(¶ms);
let rows: Vec<std::collections::HashMap<String, FilterValue>> = handle
.call(move |c| {
let mut stmt = c.prepare(&sql)?;
let column_names: Vec<String> =
stmt.column_names().iter().map(|s| s.to_string()).collect();
let refs: Vec<&dyn rusqlite::ToSql> =
bound.iter().map(|v| v as &dyn rusqlite::ToSql).collect();
let mut rows = stmt.query(refs.as_slice())?;
let mut out = Vec::new();
while let Some(row) = rows.next()? {
let mut map = std::collections::HashMap::new();
for (i, name) in column_names.iter().enumerate() {
let v: SqlValue = row.get(i).unwrap_or(SqlValue::Null);
let fv = match v {
SqlValue::Null => FilterValue::Null,
SqlValue::Integer(n) => FilterValue::Int(n),
SqlValue::Real(f) => FilterValue::Float(f),
SqlValue::Text(s) => FilterValue::String(s),
SqlValue::Blob(_) => FilterValue::Null,
};
map.insert(name.clone(), fv);
}
out.push(map);
}
Ok(out)
})
.await
.map_err(|e| QueryError::database(e.to_string()).with_source(e))?;
Ok(rows)
})
}
fn transaction<'a, R, Fut, F>(&'a self, f: F) -> BoxFuture<'a, QueryResult<R>>
where
F: FnOnce(Self) -> Fut + Send + 'a,
Fut: std::future::Future<Output = QueryResult<R>> + Send + 'a,
R: Send + 'a,
Self: Clone,
{
Box::pin(async move {
if self.tx_conn.is_some() {
return Err(QueryError::internal(
"nested transactions not yet implemented \
(call .transaction() on the outer engine only, or \
issue SAVEPOINT via execute_raw)",
));
}
let conn = self
.pool
.get()
.await
.map_err(|e| QueryError::connection(e.to_string()).with_source(e))?;
let handle = conn.inner().clone();
handle
.call(|c| {
c.execute_batch("BEGIN")?;
Ok(())
})
.await
.map_err(|e| QueryError::database(e.to_string()).with_source(e))?;
let tx_conn = Arc::new(conn);
let tx_engine = SqliteEngine {
pool: self.pool.clone(),
tx_conn: Some(tx_conn.clone()),
};
let result = f(tx_engine).await;
match result {
Ok(v) => {
handle
.call(|c| {
c.execute_batch("COMMIT")?;
Ok(())
})
.await
.map_err(|e| QueryError::database(e.to_string()).with_source(e))?;
Ok(v)
}
Err(e) => {
let _ = handle
.call(|c| {
c.execute_batch("ROLLBACK")?;
Ok(())
})
.await;
Err(e)
}
}
})
}
}