use futures::future::BoxFuture;
use sqlx::{PgPool, Postgres, Transaction};
use super::{Result, WebhookError};
#[derive(Debug, Clone, Copy, Default)]
pub enum WebhookIsolation {
#[default]
ReadCommitted,
RepeatableRead,
Serializable,
}
impl WebhookIsolation {
#[must_use]
pub fn as_sql(self) -> &'static str {
match self {
Self::ReadCommitted => "READ COMMITTED",
Self::RepeatableRead => "REPEATABLE READ",
Self::Serializable => "SERIALIZABLE",
}
}
}
pub async fn execute_in_transaction<F, T>(
pool: &PgPool,
isolation: WebhookIsolation,
f: F,
) -> Result<T>
where
F: for<'c> FnOnce(&'c mut Transaction<'_, Postgres>) -> BoxFuture<'c, Result<T>>,
{
let mut tx = pool.begin().await.map_err(|e| WebhookError::Database(e.to_string()))?;
sqlx::query(&format!("SET TRANSACTION ISOLATION LEVEL {}", isolation.as_sql()))
.execute(&mut *tx)
.await
.map_err(|e| WebhookError::Database(e.to_string()))?;
let result = f(&mut tx).await;
match result {
Ok(value) => {
tx.commit().await.map_err(|e| WebhookError::Database(e.to_string()))?;
Ok(value)
},
Err(e) => {
let _ = tx.rollback().await;
Err(e)
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_isolation_level_sql() {
assert_eq!(WebhookIsolation::ReadCommitted.as_sql(), "READ COMMITTED");
assert_eq!(WebhookIsolation::RepeatableRead.as_sql(), "REPEATABLE READ");
assert_eq!(WebhookIsolation::Serializable.as_sql(), "SERIALIZABLE");
}
#[test]
fn test_default_isolation_level() {
let default = WebhookIsolation::default();
assert_eq!(default.as_sql(), "READ COMMITTED");
}
}