use super::{Dialect, ExecError};
use crate::sql::{Pool, PoolId};
pub struct PoolTx<'a> {
pub(crate) conn: TxConn<'a>,
pool: PoolId,
}
pub(crate) trait BeginTx {
fn begin_tx(&mut self) -> futures_util::future::BoxFuture<'_, Result<TxConn<'_>, sqlx::Error>>;
}
macro_rules! begin_tx {
($feature:literal, $conn:ty, $variant:ident) => {
#[cfg(feature = $feature)]
impl BeginTx for $conn {
fn begin_tx(
&mut self,
) -> futures_util::future::BoxFuture<'_, Result<TxConn<'_>, sqlx::Error>> {
use sqlx::Connection as _;
Box::pin(async move { Ok(TxConn::$variant(self.begin().await?)) })
}
}
};
}
begin_tx!("postgres", sqlx::PgConnection, Postgres);
begin_tx!("mysql", sqlx::MySqlConnection, Mysql);
begin_tx!("sqlite", sqlx::SqliteConnection, Sqlite);
#[non_exhaustive]
pub enum TxConn<'a> {
#[cfg(feature = "postgres")]
Postgres(sqlx::Transaction<'a, sqlx::Postgres>),
#[cfg(feature = "mysql")]
Mysql(sqlx::Transaction<'a, sqlx::MySql>),
#[cfg(feature = "sqlite")]
Sqlite(sqlx::Transaction<'a, sqlx::Sqlite>),
}
impl<'a> PoolTx<'a> {
pub(crate) fn new(conn: TxConn<'a>, pool: PoolId) -> Self {
Self { conn, pool }
}
pub(crate) async fn begin_on<C: BeginTx + ?Sized>(
conn: &'a mut C,
pool: &PoolId,
) -> Result<Self, ExecError> {
Ok(Self::new(conn.begin_tx().await?, pool.clone()))
}
pub fn conn(&mut self) -> &mut TxConn<'a> {
&mut self.conn
}
pub(crate) fn pool_id(&self) -> &PoolId {
&self.pool
}
pub async fn commit(self) -> Result<(), sqlx::Error> {
match self.conn {
#[cfg(feature = "postgres")]
TxConn::Postgres(tx) => tx.commit().await,
#[cfg(feature = "mysql")]
TxConn::Mysql(tx) => tx.commit().await,
#[cfg(feature = "sqlite")]
TxConn::Sqlite(tx) => tx.commit().await,
}
}
pub async fn rollback(self) -> Result<(), sqlx::Error> {
match self.conn {
#[cfg(feature = "postgres")]
TxConn::Postgres(tx) => tx.rollback().await,
#[cfg(feature = "mysql")]
TxConn::Mysql(tx) => tx.rollback().await,
#[cfg(feature = "sqlite")]
TxConn::Sqlite(tx) => tx.rollback().await,
}
}
pub(crate) async fn execute_unprepared(&mut self, sql: &str) -> Result<(), sqlx::Error> {
use sqlx::Executor as _;
match &mut self.conn {
#[cfg(feature = "postgres")]
TxConn::Postgres(tx) => (&mut **tx).execute(sqlx::raw_sql(sql)).await.map(drop),
#[cfg(feature = "mysql")]
TxConn::Mysql(tx) => (&mut **tx).execute(sqlx::raw_sql(sql)).await.map(drop),
#[cfg(feature = "sqlite")]
TxConn::Sqlite(tx) => (&mut **tx).execute(sqlx::raw_sql(sql)).await.map(drop),
}
}
pub(crate) async fn still_open(&mut self) -> bool {
match &mut self.conn {
#[cfg(feature = "postgres")]
TxConn::Postgres(_) => self.execute_unprepared("SELECT 1").await.is_ok(),
#[cfg(feature = "mysql")]
TxConn::Mysql(tx) => {
use sqlx::Connection as _;
match (**tx).begin().await {
Ok(probe) => probe.commit().await.is_ok(),
Err(_) => false,
}
}
#[cfg(feature = "sqlite")]
TxConn::Sqlite(_) => true,
}
}
pub(crate) async fn on_sqlite_rollback(
&mut self,
f: impl FnMut() + Send + 'static,
) -> Result<(), sqlx::Error> {
match &mut self.conn {
#[cfg(feature = "sqlite")]
TxConn::Sqlite(tx) => {
(**tx).lock_handle().await?.set_rollback_hook(f);
Ok(())
}
#[allow(unreachable_patterns)]
_ => {
drop(f);
Ok(())
}
}
}
pub(crate) async fn clear_sqlite_rollback(&mut self) {
#[cfg(feature = "sqlite")]
#[allow(irrefutable_let_patterns)]
if let TxConn::Sqlite(tx) = &mut self.conn {
if let Ok(mut h) = (**tx).lock_handle().await {
h.remove_rollback_hook();
}
}
}
#[must_use]
pub fn dialect(&self) -> &'static dyn Dialect {
match &self.conn {
#[cfg(feature = "postgres")]
TxConn::Postgres(_) => crate::sql::postgres::DIALECT,
#[cfg(feature = "mysql")]
TxConn::Mysql(_) => crate::sql::mysql::DIALECT,
#[cfg(feature = "sqlite")]
TxConn::Sqlite(_) => crate::sql::sqlite::DIALECT,
}
}
}
pub async fn transaction_pool(pool: &Pool) -> Result<PoolTx<'static>, ExecError> {
let conn = match pool {
#[cfg(feature = "postgres")]
Pool::Postgres(pg) => TxConn::Postgres(pg.begin().await?),
#[cfg(feature = "mysql")]
Pool::Mysql(my) => TxConn::Mysql(my.begin().await?),
#[cfg(feature = "sqlite")]
Pool::Sqlite(sq) => TxConn::Sqlite(sq.begin().await?),
};
Ok(PoolTx::new(conn, PoolId::of(pool)))
}
pub async fn write_transaction(pool: &Pool) -> Result<PoolTx<'static>, ExecError> {
match pool {
#[cfg(feature = "sqlite")]
Pool::Sqlite(sq) => Ok(PoolTx::new(
TxConn::Sqlite(sq.begin_with("BEGIN IMMEDIATE").await?),
PoolId::of(pool),
)),
#[allow(unreachable_patterns)]
_ => transaction_pool(pool).await,
}
}