use std::ops::Deref;
use crate::{connection::Connection, statement::Statement, Result};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum TransactionBehavior {
Deferred,
Immediate,
Exclusive,
Concurrent,
}
impl TransactionBehavior {
pub(crate) fn begin_sql(self) -> &'static str {
match self {
TransactionBehavior::Deferred => "BEGIN DEFERRED",
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
TransactionBehavior::Concurrent => "BEGIN CONCURRENT",
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DropBehavior {
Rollback,
Commit,
Ignore,
Panic,
}
impl From<DropBehavior> for u8 {
fn from(behavior: DropBehavior) -> Self {
match behavior {
DropBehavior::Rollback => 0,
DropBehavior::Commit => 1,
DropBehavior::Ignore => 2,
DropBehavior::Panic => 3,
}
}
}
impl From<u8> for DropBehavior {
fn from(value: u8) -> Self {
match value {
0 => DropBehavior::Rollback,
1 => DropBehavior::Commit,
2 => DropBehavior::Ignore,
3 => DropBehavior::Panic,
_ => panic!("Invalid drop behavior: {value}"),
}
}
}
#[derive(Debug)]
pub struct Transaction<'conn> {
conn: &'conn Connection,
drop_behavior: DropBehavior,
in_progress: bool,
}
impl Transaction<'_> {
#[inline]
pub async fn new(
conn: &mut Connection,
behavior: TransactionBehavior,
) -> Result<Transaction<'_>> {
Self::new_unchecked(conn, behavior).await
}
#[inline]
pub async fn new_unchecked(
conn: &Connection,
behavior: TransactionBehavior,
) -> Result<Transaction<'_>> {
conn.execute(behavior.begin_sql(), ()).await?;
Ok(Transaction {
conn,
drop_behavior: DropBehavior::Rollback,
in_progress: true,
})
}
pub async fn prepare(&self, sql: &str) -> Result<Statement> {
self.conn.prepare(sql).await
}
#[inline]
#[must_use]
pub fn drop_behavior(&self) -> DropBehavior {
self.drop_behavior
}
#[inline]
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
self.drop_behavior = drop_behavior;
}
#[inline]
pub async fn commit(mut self) -> Result<()> {
self._commit().await
}
#[inline]
async fn _commit(&mut self) -> Result<()> {
self.conn.execute("COMMIT", ()).await?;
self.in_progress = false;
Ok(())
}
#[inline]
pub async fn rollback(mut self) -> Result<()> {
self._rollback().await
}
#[inline]
async fn _rollback(&mut self) -> Result<()> {
self.conn.execute("ROLLBACK", ()).await?;
self.in_progress = false;
Ok(())
}
#[inline]
pub async fn finish(mut self) -> Result<()> {
self._finish().await
}
#[inline]
async fn _finish(&mut self) -> Result<()> {
if self.conn.is_autocommit()? {
self.in_progress = false;
return Ok(());
}
match self.drop_behavior() {
DropBehavior::Commit => {
if (self._commit().await).is_err() {
self._rollback().await
} else {
Ok(())
}
}
DropBehavior::Rollback => self._rollback().await,
DropBehavior::Ignore => {
self.in_progress = false;
Ok(())
}
DropBehavior::Panic => panic!("Transaction dropped unexpectedly."),
}
}
}
impl Deref for Transaction<'_> {
type Target = Connection;
#[inline]
fn deref(&self) -> &Connection {
self.conn
}
}
impl Drop for Transaction<'_> {
fn drop(&mut self) {
if self.in_progress {
self.conn.set_dangling_tx(self.drop_behavior);
} else {
self.conn.set_dangling_tx(DropBehavior::Ignore);
}
}
}
#[cfg(test)]
mod tests {
use super::TransactionBehavior;
#[test]
fn behavior_maps_to_begin_statement() {
assert_eq!(TransactionBehavior::Deferred.begin_sql(), "BEGIN DEFERRED");
assert_eq!(
TransactionBehavior::Immediate.begin_sql(),
"BEGIN IMMEDIATE"
);
assert_eq!(
TransactionBehavior::Exclusive.begin_sql(),
"BEGIN EXCLUSIVE"
);
assert_eq!(
TransactionBehavior::Concurrent.begin_sql(),
"BEGIN CONCURRENT"
);
}
}