use crate::error::{OrmError, OrmResult};
use sqlx::{PgPool, Postgres, Transaction};
use std::future::Future;
pub async fn transaction<F, Fut, R>(pool: &PgPool, f: F) -> OrmResult<R>
where
F: FnOnce(Transaction<'static, Postgres>) -> Fut,
Fut: Future<Output = OrmResult<R>>,
{
let tx = pool.begin().await.map_err(OrmError::from_sqlx)?;
match f(tx).await {
Ok(result) => Ok(result),
Err(err) => {
Err(err)
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub enum IsolationLevel {
#[default]
ReadCommitted,
RepeatableRead,
Serializable,
ReadUncommitted,
}
impl IsolationLevel {
pub fn as_sql(&self) -> &'static str {
match self {
IsolationLevel::ReadCommitted => "READ COMMITTED",
IsolationLevel::RepeatableRead => "REPEATABLE READ",
IsolationLevel::Serializable => "SERIALIZABLE",
IsolationLevel::ReadUncommitted => "READ UNCOMMITTED",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct TransactionOptions {
pub isolation: IsolationLevel,
pub read_only: bool,
}
pub async fn transaction_with<F, Fut, R>(
pool: &PgPool,
opts: TransactionOptions,
f: F,
) -> OrmResult<R>
where
F: FnOnce(Transaction<'static, Postgres>) -> Fut,
Fut: Future<Output = OrmResult<R>>,
{
let mut tx = pool.begin().await.map_err(OrmError::from_sqlx)?;
let iso_sql = format!(
"SET TRANSACTION ISOLATION LEVEL {}",
opts.isolation.as_sql()
);
sqlx::query(&iso_sql)
.execute(&mut *tx)
.await
.map_err(OrmError::from_sqlx)?;
if opts.read_only {
sqlx::query("SET TRANSACTION READ ONLY")
.execute(&mut *tx)
.await
.map_err(OrmError::from_sqlx)?;
}
match f(tx).await {
Ok(result) => Ok(result),
Err(err) => Err(err),
}
}