use std::future::Future;
use crate::error::{SqlxError, SqlxResult};
use crate::pool::SqlxPool;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IsolationLevel {
ReadUncommitted,
ReadCommitted,
RepeatableRead,
Serializable,
}
impl IsolationLevel {
pub fn as_sql(&self) -> &'static str {
match self {
IsolationLevel::ReadUncommitted => "READ UNCOMMITTED",
IsolationLevel::ReadCommitted => "READ COMMITTED",
IsolationLevel::RepeatableRead => "REPEATABLE READ",
IsolationLevel::Serializable => "SERIALIZABLE",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessMode {
ReadWrite,
ReadOnly,
}
impl AccessMode {
pub fn as_sql(&self) -> &'static str {
match self {
AccessMode::ReadWrite => "READ WRITE",
AccessMode::ReadOnly => "READ ONLY",
}
}
}
#[derive(Debug, Clone)]
pub struct TransactionOptions {
pub isolation_level: Option<IsolationLevel>,
pub access_mode: Option<AccessMode>,
pub deferrable: Option<bool>,
}
impl Default for TransactionOptions {
fn default() -> Self {
Self {
isolation_level: None,
access_mode: None,
deferrable: None,
}
}
}
impl TransactionOptions {
pub fn new() -> Self {
Self::default()
}
pub fn isolation_level(mut self, level: IsolationLevel) -> Self {
self.isolation_level = Some(level);
self
}
pub fn access_mode(mut self, mode: AccessMode) -> Self {
self.access_mode = Some(mode);
self
}
pub fn deferrable(mut self, deferrable: bool) -> Self {
self.deferrable = Some(deferrable);
self
}
pub fn read_only() -> Self {
Self::new().access_mode(AccessMode::ReadOnly)
}
pub fn serializable() -> Self {
Self::new().isolation_level(IsolationLevel::Serializable)
}
}
#[cfg(feature = "postgres")]
pub async fn with_transaction_pg<F, Fut, T>(pool: &sqlx::PgPool, f: F) -> SqlxResult<T>
where
F: FnOnce(sqlx::Transaction<'static, sqlx::Postgres>) -> Fut,
Fut: Future<Output = Result<T, SqlxError>>,
{
let tx = pool.begin().await?;
match f(tx).await {
Ok(result) => Ok(result),
Err(e) => Err(e),
}
}
#[cfg(feature = "mysql")]
pub async fn with_transaction_mysql<F, Fut, T>(pool: &sqlx::MySqlPool, f: F) -> SqlxResult<T>
where
F: FnOnce(sqlx::Transaction<'static, sqlx::MySql>) -> Fut,
Fut: Future<Output = Result<T, SqlxError>>,
{
let tx = pool.begin().await?;
match f(tx).await {
Ok(result) => Ok(result),
Err(e) => Err(e),
}
}
#[cfg(feature = "sqlite")]
pub async fn with_transaction_sqlite<F, Fut, T>(pool: &sqlx::SqlitePool, f: F) -> SqlxResult<T>
where
F: FnOnce(sqlx::Transaction<'static, sqlx::Sqlite>) -> Fut,
Fut: Future<Output = Result<T, SqlxError>>,
{
let tx = pool.begin().await?;
match f(tx).await {
Ok(result) => Ok(result),
Err(e) => Err(e),
}
}
pub async fn with_transaction<F, T>(pool: &SqlxPool, f: F) -> SqlxResult<T>
where
F: FnOnce(&SqlxPool) -> futures::future::BoxFuture<'_, Result<T, SqlxError>>,
{
f(pool).await
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_isolation_level_sql() {
assert_eq!(IsolationLevel::ReadUncommitted.as_sql(), "READ UNCOMMITTED");
assert_eq!(IsolationLevel::ReadCommitted.as_sql(), "READ COMMITTED");
assert_eq!(IsolationLevel::RepeatableRead.as_sql(), "REPEATABLE READ");
assert_eq!(IsolationLevel::Serializable.as_sql(), "SERIALIZABLE");
}
#[test]
fn test_access_mode_sql() {
assert_eq!(AccessMode::ReadWrite.as_sql(), "READ WRITE");
assert_eq!(AccessMode::ReadOnly.as_sql(), "READ ONLY");
}
#[test]
fn test_transaction_options_builder() {
let opts = TransactionOptions::new()
.isolation_level(IsolationLevel::Serializable)
.access_mode(AccessMode::ReadOnly)
.deferrable(true);
assert_eq!(opts.isolation_level, Some(IsolationLevel::Serializable));
assert_eq!(opts.access_mode, Some(AccessMode::ReadOnly));
assert_eq!(opts.deferrable, Some(true));
}
#[test]
fn test_transaction_options_presets() {
let read_only = TransactionOptions::read_only();
assert_eq!(read_only.access_mode, Some(AccessMode::ReadOnly));
let serializable = TransactionOptions::serializable();
assert_eq!(
serializable.isolation_level,
Some(IsolationLevel::Serializable)
);
}
}