use rusqlite::{Connection, Transaction};
pub(crate) enum WriteTransaction<'conn> {
Owned(Transaction<'conn>),
Inherited,
}
impl<'conn> WriteTransaction<'conn> {
pub(crate) fn begin(conn: &'conn Connection) -> rusqlite::Result<Self> {
if conn.is_autocommit() {
Ok(Self::Owned(conn.unchecked_transaction()?))
} else {
Ok(Self::Inherited)
}
}
pub(crate) fn commit(self) -> rusqlite::Result<()> {
match self {
Self::Owned(tx) => tx.commit(),
Self::Inherited => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use super::WriteTransaction;
use rusqlite::Connection;
#[test]
fn an_idle_connection_yields_an_owned_transaction() {
let conn = Connection::open_in_memory().unwrap();
let tx = WriteTransaction::begin(&conn).unwrap();
assert!(matches!(tx, WriteTransaction::Owned(_)));
assert!(!conn.is_autocommit());
tx.commit().unwrap();
assert!(conn.is_autocommit());
}
#[test]
fn an_open_transaction_is_inherited() {
let conn = Connection::open_in_memory().unwrap();
let outer = conn.unchecked_transaction().unwrap();
let tx = WriteTransaction::begin(&conn).unwrap();
assert!(matches!(tx, WriteTransaction::Inherited));
tx.commit().unwrap();
assert!(!conn.is_autocommit());
outer.commit().unwrap();
assert!(conn.is_autocommit());
}
#[test]
fn nesting_is_what_sqlite_refuses() {
let conn = Connection::open_in_memory().unwrap();
let _outer = conn.unchecked_transaction().unwrap();
let err = conn.unchecked_transaction().unwrap_err();
assert!(
err.to_string().contains("within a transaction"),
"unexpected error: {err}"
);
}
#[test]
fn the_caller_owns_the_rollback() {
let conn = Connection::open_in_memory().unwrap();
conn.execute_batch("CREATE TABLE t (v INTEGER)").unwrap();
let outer = conn.unchecked_transaction().unwrap();
let tx = WriteTransaction::begin(&conn).unwrap();
conn.execute("INSERT INTO t (v) VALUES (1)", []).unwrap();
tx.commit().unwrap();
outer.rollback().unwrap();
let rows: i64 = conn
.query_row("SELECT count(*) FROM t", [], |row| row.get(0))
.unwrap();
assert_eq!(rows, 0, "an inherited commit made the row durable");
}
}