use super::Row;
use super::{Client, Param};
use crate::errors::Result;
use crate::{ExecuteResult, Syntax};
use async_trait::async_trait;
use std::sync::Mutex;
#[cfg(feature = "mssql")]
use crate::mssql::transaction::MssqlTransaction;
pub struct Transaction<'t> {
inner: Mutex<Option<TransT<'t>>>,
syntax: crate::Syntax,
}
impl<'t> Transaction<'t> {
pub(crate) fn new(inner: TransT<'t>) -> Self {
let syntax = match &inner {
#[cfg(feature = "sqlite")]
TransT::Sqlite(_) => Syntax::Sqlite,
#[cfg(feature = "mssql")]
TransT::Mssql(_) => Syntax::Mssql,
#[cfg(feature = "postgres")]
TransT::Postgres(_) => Syntax::Postgres,
#[cfg(feature = "mysql")]
TransT::Mysql(_) => Syntax::Mysql,
};
Self {
syntax,
inner: Mutex::new(Some(inner)),
}
}
pub async fn rollback(self) -> Result<()> {
let inner = self.take_conn();
inner.rollback().await?;
Ok(())
}
pub async fn commit(self) -> Result<()> {
let inner = self.take_conn();
inner.commit().await?;
Ok(())
}
}
impl<'t> Transaction<'t> {
fn take_conn(&self) -> TransT<'t> {
let mut placeholder = None;
let mut m = self.inner.lock().unwrap();
let inner: &mut Option<TransT<'t>> = &mut m;
assert!(inner.is_some(), "Pool was already taken");
std::mem::swap(&mut placeholder, inner);
placeholder.unwrap()
}
fn return_conn(&self, conn: TransT<'t>) {
let mut placeholder = Some(conn);
let mut m = self.inner.lock().unwrap();
let inner: &mut Option<TransT<'t>> = &mut m;
assert!(inner.is_none(), "Overriding existing pool");
std::mem::swap(&mut placeholder, inner);
}
}
pub(crate) enum TransT<'t> {
#[cfg(feature = "sqlite")]
Sqlite(sqlx::Transaction<'t, sqlx::Sqlite>),
#[cfg(feature = "postgres")]
Postgres(sqlx::Transaction<'t, sqlx::Postgres>),
#[cfg(feature = "mysql")]
Mysql(sqlx::Transaction<'t, sqlx::MySql>),
#[cfg(feature = "mssql")]
Mssql(MssqlTransaction<'t>),
}
impl<'t> TransT<'t> {
async fn rollback(self) -> Result<()> {
match self {
#[cfg(feature = "sqlite")]
TransT::Sqlite(t) => t.rollback().await?,
#[cfg(feature = "mssql")]
TransT::Mssql(t) => t.rollback().await?,
#[cfg(feature = "postgres")]
TransT::Postgres(t) => t.rollback().await?,
#[cfg(feature = "mysql")]
TransT::Mysql(t) => t.rollback().await?,
}
Ok(())
}
async fn commit(self) -> Result<()> {
match self {
#[cfg(feature = "sqlite")]
TransT::Sqlite(t) => t.commit().await?,
#[cfg(feature = "mssql")]
TransT::Mssql(t) => t.commit().await?,
#[cfg(feature = "postgres")]
TransT::Postgres(t) => t.commit().await?,
#[cfg(feature = "mysql")]
TransT::Mysql(t) => t.commit().await?,
}
Ok(())
}
}
#[cfg(feature = "mysql")]
use super::mysql::MysqlParam;
#[cfg(feature = "postgres")]
use super::postgres::PostgresParam;
#[cfg(feature = "sqlite")]
use super::sqlite::SqliteParam;
#[async_trait]
impl<'t> Client for Transaction<'t> {
fn syntax(&self) -> crate::Syntax {
self.syntax
}
async fn execute(&self, sql: &str, params: &[&(dyn Param + Sync)]) -> Result<ExecuteResult> {
let mut inner = self.take_conn();
let results = execute_inner(&mut inner, sql, params).await;
self.return_conn(inner);
results
}
async fn fetch_rows(&self, sql: &str, params: &[&(dyn Param + Sync)]) -> Result<Vec<Row>> {
let mut inner = self.take_conn();
let results = fetch_rows_inner(&mut inner, sql, params).await;
self.return_conn(inner);
results
}
async fn fetch_many<'s, 'args, 'i>(
&self,
fetches: &[crate::Fetch<'s, 'args, 'i>],
) -> Result<Vec<Vec<Row>>> {
let mut datasets = Vec::default();
let mut inner = self.take_conn();
for fetch in fetches {
let sql = fetch.sql;
let params = fetch.params;
let r = fetch_rows_inner(&mut inner, sql, params).await;
let is_err = r.is_err();
datasets.push(r);
if is_err {
break;
}
}
self.return_conn(inner);
datasets.drain(..).collect()
}
}
async fn execute_inner<'t>(
inner: &mut TransT<'t>,
sql: &str,
params: &[&(dyn Param + Sync)],
) -> Result<ExecuteResult> {
match inner {
#[cfg(feature = "sqlite")]
TransT::Sqlite(t) => {
let x: &mut <sqlx::Sqlite as sqlx::Database>::Connection = t;
let mut query = sqlx::query::<sqlx::Sqlite>(sql);
for param in params {
query = SqliteParam::add_param(*param, query)
}
let t = query.execute(x).await?;
Ok(ExecuteResult {
rows_affected: t.rows_affected(),
})
}
#[cfg(feature = "postgres")]
TransT::Postgres(t) => {
let x: &mut <sqlx::Postgres as sqlx::Database>::Connection = t;
let mut query = sqlx::query::<sqlx::Postgres>(sql);
for param in params {
query = PostgresParam::add_param(*param, query)
}
let t = query.execute(x).await?;
Ok(ExecuteResult {
rows_affected: t.rows_affected(),
})
}
#[cfg(feature = "mysql")]
TransT::Mysql(t) => {
let x: &mut <sqlx::MySql as sqlx::Database>::Connection = t;
let mut query = sqlx::query::<sqlx::MySql>(sql);
for param in params {
query = MysqlParam::add_param(*param, query)
}
let t = query.execute(x).await?;
Ok(ExecuteResult {
rows_affected: t.rows_affected(),
})
}
#[cfg(feature = "mssql")]
TransT::Mssql(inner) => {
let result = inner.execute(sql, params).await;
if result.is_err() {
inner.rollback_internal().await?;
}
result
}
}
}
async fn fetch_rows_inner<'t>(
inner: &mut TransT<'t>,
sql: &str,
params: &[&(dyn Param + Sync)],
) -> Result<Vec<Row>> {
match inner {
#[cfg(feature = "sqlite")]
TransT::Sqlite(t) => {
let x: &mut <sqlx::Sqlite as sqlx::Database>::Connection = t;
let mut query = sqlx::query::<sqlx::Sqlite>(sql);
for param in params {
query = SqliteParam::add_param(*param, query)
}
let mut raw_rows = query.fetch_all(x).await?;
let rows: Vec<Row> = raw_rows.drain(..).map(Row::from).collect();
Ok(rows)
}
#[cfg(feature = "postgres")]
TransT::Postgres(t) => {
let x: &mut <sqlx::Postgres as sqlx::Database>::Connection = t;
let mut query = sqlx::query::<sqlx::Postgres>(sql);
for param in params {
query = PostgresParam::add_param(*param, query)
}
let mut raw_rows = query.fetch_all(x).await?;
let rows: Vec<Row> = raw_rows.drain(..).map(Row::from).collect();
Ok(rows)
}
#[cfg(feature = "mysql")]
TransT::Mysql(t) => {
let x: &mut <sqlx::MySql as sqlx::Database>::Connection = t;
let mut query = sqlx::query::<sqlx::MySql>(sql);
for param in params {
query = MysqlParam::add_param(*param, query)
}
let mut raw_rows = query.fetch_all(x).await?;
let rows: Vec<Row> = raw_rows.drain(..).map(Row::from).collect();
Ok(rows)
}
#[cfg(feature = "mssql")]
TransT::Mssql(inner) => {
let result = inner.fetch_rows(sql, params).await;
if result.is_err() {
inner.rollback_internal().await?;
}
result
}
}
}