#![allow(dead_code)]
use crate::backend::Backend;
use crate::connection::{AnsiTransactionManager, TransactionManager};
use crate::pg::Pg;
use crate::prelude::*;
use crate::query_builder::{AstPass, QueryBuilder, QueryFragment};
use crate::result::Error;
#[allow(missing_debug_implementations)] #[must_use = "Transaction builder does nothing unless you call `run` on it"]
#[cfg(feature = "postgres_backend")]
pub struct TransactionBuilder<'a, C> {
connection: &'a mut C,
isolation_level: Option<IsolationLevel>,
read_mode: Option<ReadMode>,
deferrable: Option<Deferrable>,
}
impl<'a, C> TransactionBuilder<'a, C>
where
C: Connection<Backend = Pg, TransactionManager = AnsiTransactionManager>,
{
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
)]
pub(crate) fn new(connection: &'a mut C) -> Self {
Self {
connection,
isolation_level: None,
read_mode: None,
deferrable: None,
}
}
pub fn read_only(mut self) -> Self {
self.read_mode = Some(ReadMode::ReadOnly);
self
}
pub fn read_write(mut self) -> Self {
self.read_mode = Some(ReadMode::ReadWrite);
self
}
pub fn deferrable(mut self) -> Self {
self.deferrable = Some(Deferrable::Deferrable);
self
}
pub fn not_deferrable(mut self) -> Self {
self.deferrable = Some(Deferrable::NotDeferrable);
self
}
pub fn read_committed(mut self) -> Self {
self.isolation_level = Some(IsolationLevel::ReadCommitted);
self
}
pub fn repeatable_read(mut self) -> Self {
self.isolation_level = Some(IsolationLevel::RepeatableRead);
self
}
pub fn serializable(mut self) -> Self {
self.isolation_level = Some(IsolationLevel::Serializable);
self
}
pub fn run<T, E, F>(&mut self, f: F) -> Result<T, E>
where
F: FnOnce(&mut C) -> Result<T, E>,
E: From<Error>,
{
let mut query_builder = <Pg as Backend>::QueryBuilder::default();
self.to_sql(&mut query_builder, &Pg)?;
let sql = query_builder.finish();
AnsiTransactionManager::begin_transaction_sql(&mut *self.connection, &sql)?;
match f(&mut *self.connection) {
Ok(value) => {
AnsiTransactionManager::commit_transaction(&mut *self.connection)?;
Ok(value)
}
Err(user_error) => {
match AnsiTransactionManager::rollback_transaction(&mut *self.connection) {
Ok(()) => Err(user_error),
Err(Error::BrokenTransactionManager) => {
Err(user_error)
}
Err(rollback_error) => Err(rollback_error.into()),
}
}
}
}
}
impl<C> QueryFragment<Pg> for TransactionBuilder<'_, C> {
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> {
out.push_sql("BEGIN TRANSACTION");
if let Some(ref isolation_level) = self.isolation_level {
isolation_level.walk_ast(out.reborrow())?;
}
if let Some(ref read_mode) = self.read_mode {
read_mode.walk_ast(out.reborrow())?;
}
if let Some(ref deferrable) = self.deferrable {
deferrable.walk_ast(out.reborrow())?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
enum IsolationLevel {
ReadCommitted,
RepeatableRead,
Serializable,
}
impl QueryFragment<Pg> for IsolationLevel {
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> {
out.push_sql(" ISOLATION LEVEL ");
match *self {
IsolationLevel::ReadCommitted => out.push_sql("READ COMMITTED"),
IsolationLevel::RepeatableRead => out.push_sql("REPEATABLE READ"),
IsolationLevel::Serializable => out.push_sql("SERIALIZABLE"),
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
enum ReadMode {
ReadOnly,
ReadWrite,
}
impl QueryFragment<Pg> for ReadMode {
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> {
match *self {
ReadMode::ReadOnly => out.push_sql(" READ ONLY"),
ReadMode::ReadWrite => out.push_sql(" READ WRITE"),
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
enum Deferrable {
Deferrable,
NotDeferrable,
}
impl QueryFragment<Pg> for Deferrable {
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> {
match *self {
Deferrable::Deferrable => out.push_sql(" DEFERRABLE"),
Deferrable::NotDeferrable => out.push_sql(" NOT DEFERRABLE"),
}
Ok(())
}
}
#[cfg(test)]
#[diesel_test_helper::test]
fn test_transaction_builder_generates_correct_sql() {
extern crate dotenvy;
macro_rules! assert_sql {
($query:expr, $sql:expr) => {
let mut query_builder = <Pg as Backend>::QueryBuilder::default();
$query.to_sql(&mut query_builder, &Pg).unwrap();
let sql = query_builder.finish();
assert_eq!(sql, $sql);
};
}
let database_url = dotenvy::var("PG_DATABASE_URL")
.or_else(|_| dotenvy::var("DATABASE_URL"))
.expect("DATABASE_URL must be set in order to run tests");
let mut conn = PgConnection::establish(&database_url).unwrap();
assert_sql!(conn.build_transaction(), "BEGIN TRANSACTION");
assert_sql!(
conn.build_transaction().read_only(),
"BEGIN TRANSACTION READ ONLY"
);
assert_sql!(
conn.build_transaction().read_write(),
"BEGIN TRANSACTION READ WRITE"
);
assert_sql!(
conn.build_transaction().deferrable(),
"BEGIN TRANSACTION DEFERRABLE"
);
assert_sql!(
conn.build_transaction().not_deferrable(),
"BEGIN TRANSACTION NOT DEFERRABLE"
);
assert_sql!(
conn.build_transaction().read_committed(),
"BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED"
);
assert_sql!(
conn.build_transaction().repeatable_read(),
"BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ"
);
assert_sql!(
conn.build_transaction().serializable(),
"BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE"
);
assert_sql!(
conn.build_transaction()
.serializable()
.deferrable()
.read_only(),
"BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE"
);
}