use crate::{Connection, Result};
use std::ops::Deref;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DropBehavior {
Rollback,
Commit,
Ignore,
Panic,
}
#[derive(Debug)]
pub struct Transaction<'conn> {
conn: &'conn Connection,
drop_behavior: DropBehavior,
}
impl Transaction<'_> {
#[inline]
pub fn new(conn: &mut Connection) -> Result<Transaction<'_>> {
Self::new_unchecked(conn)
}
#[inline]
pub fn new_unchecked(conn: &Connection) -> Result<Transaction<'_>> {
let query = "BEGIN TRANSACTION";
conn.execute_batch(query).map(move |_| Transaction {
conn,
drop_behavior: DropBehavior::Rollback,
})
}
#[inline]
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 fn commit(mut self) -> Result<()> {
self.commit_()
}
#[inline]
fn commit_(&mut self) -> Result<()> {
self.conn.execute_batch("COMMIT")
}
#[inline]
pub fn rollback(mut self) -> Result<()> {
self.rollback_()
}
#[inline]
fn rollback_(&mut self) -> Result<()> {
self.conn.execute_batch("ROLLBACK")
}
#[inline]
pub fn finish(mut self) -> Result<()> {
self.finish_()
}
#[inline]
fn finish_(&mut self) -> Result<()> {
match self.drop_behavior() {
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback_()),
DropBehavior::Rollback => self.rollback_(),
DropBehavior::Ignore => Ok(()),
DropBehavior::Panic => panic!("Transaction dropped unexpectedly."),
}
}
}
impl Deref for Transaction<'_> {
type Target = Connection;
#[inline]
fn deref(&self) -> &Connection {
self.conn
}
}
#[allow(unused_must_use)]
impl Drop for Transaction<'_> {
#[inline]
fn drop(&mut self) {
self.finish_();
}
}
impl Connection {
#[inline]
pub fn transaction(&mut self) -> Result<Transaction<'_>> {
Transaction::new(self)
}
pub fn unchecked_transaction(&self) -> Result<Transaction<'_>> {
Transaction::new_unchecked(self)
}
}
#[cfg(test)]
mod test {
use super::DropBehavior;
use crate::{Connection, Result};
fn checked_no_autocommit_memory_handle() -> Result<Connection> {
let db = Connection::open_in_memory()?;
db.execute_batch(
r"
CREATE TABLE foo (x INTEGER);
-- SET AUTOCOMMIT TO OFF;
",
)?;
Ok(db)
}
#[test]
fn test_drop() -> Result<()> {
let mut db = checked_no_autocommit_memory_handle()?;
{
let tx = db.transaction()?;
assert!(tx.execute_batch("INSERT INTO foo VALUES(1)").is_ok());
}
{
let mut tx = db.transaction()?;
tx.execute_batch("INSERT INTO foo VALUES(2)")?;
tx.set_drop_behavior(DropBehavior::Commit);
}
{
let tx = db.transaction()?;
assert_eq!(
2i32,
tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
}
Ok(())
}
#[test]
fn test_unchecked_nesting() -> Result<()> {
let db = checked_no_autocommit_memory_handle()?;
{
db.unchecked_transaction()?;
}
{
let tx = db.unchecked_transaction()?;
tx.execute_batch("INSERT INTO foo VALUES(1)")?;
tx.execute_batch("INSERT INTO foo VALUES(1)")?;
tx.commit()?;
}
assert_eq!(
2i32,
db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
Ok(())
}
#[test]
fn test_explicit_rollback_commit() -> Result<()> {
let mut db = checked_no_autocommit_memory_handle()?;
{
let tx = db.transaction()?;
tx.execute_batch("INSERT INTO foo VALUES(1)")?;
tx.rollback()?;
}
{
let tx = db.transaction()?;
tx.execute_batch("INSERT INTO foo VALUES(4)")?;
tx.commit()?;
}
{
let tx = db.transaction()?;
assert_eq!(
4i32,
tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
);
}
Ok(())
}
#[test]
fn test_rc() -> Result<()> {
use std::rc::Rc;
let mut conn = Connection::open_in_memory()?;
let rc_txn = Rc::new(conn.transaction()?);
Rc::try_unwrap(rc_txn).unwrap();
Ok(())
}
}