mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-26 11:31:37 +08:00
Merge pull request #693 from thomcc/unchecked-transaction
Allow opting out of compile-time transaction checking
This commit is contained in:
commit
e04426176f
@ -92,10 +92,23 @@ pub struct Savepoint<'conn> {
|
||||
impl Transaction<'_> {
|
||||
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested
|
||||
/// transactions.
|
||||
///
|
||||
/// Even though we don't mutate the connection, we take a `&mut Connection`
|
||||
/// so as to prevent nested or concurrent transactions on the same
|
||||
/// connection.
|
||||
/// so as to prevent nested transactions on the same connection. For cases
|
||||
/// where this is unacceptable, [`Transaction::new_unchecked`] is available.
|
||||
pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>> {
|
||||
Self::new_unchecked(conn, behavior)
|
||||
}
|
||||
|
||||
/// Begin a new transaction, failing if a transaction is open.
|
||||
///
|
||||
/// If a transaction is already open, this will return an error. Where
|
||||
/// possible, [`Transaction::new`] should be preferred, as it provides a
|
||||
/// compile-time guarantee that transactions are not nested.
|
||||
pub fn new_unchecked(
|
||||
conn: &Connection,
|
||||
behavior: TransactionBehavior,
|
||||
) -> Result<Transaction<'_>> {
|
||||
let query = match behavior {
|
||||
TransactionBehavior::Deferred => "BEGIN DEFERRED",
|
||||
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
|
||||
@ -369,6 +382,41 @@ impl Connection {
|
||||
Transaction::new(self, behavior)
|
||||
}
|
||||
|
||||
/// Begin a new transaction with the default behavior (DEFERRED).
|
||||
///
|
||||
/// Attempt to open a nested transaction will result in a SQLite error.
|
||||
/// `Connection::transaction` prevents this at compile time by taking `&mut
|
||||
/// self`, but `Connection::unchecked_transaction()` may be used to defer
|
||||
/// the checking until runtime.
|
||||
///
|
||||
/// See [`Connection::transaction`] and [`Transaction::new_unchecked`]
|
||||
/// (which can be used if the default transaction behavior is undesirable).
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// # use std::rc::Rc;
|
||||
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
||||
/// fn perform_queries(conn: Rc<Connection>) -> Result<()> {
|
||||
/// let tx = conn.unchecked_transaction()?;
|
||||
///
|
||||
/// do_queries_part_1(&tx)?; // tx causes rollback if this fails
|
||||
/// do_queries_part_2(&tx)?; // tx causes rollback if this fails
|
||||
///
|
||||
/// tx.commit()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Will return `Err` if the underlying SQLite call fails. The specific
|
||||
/// error returned if transactions are nested is currently unspecified.
|
||||
pub fn unchecked_transaction(&self) -> Result<Transaction<'_>> {
|
||||
Transaction::new_unchecked(self, TransactionBehavior::Deferred)
|
||||
}
|
||||
|
||||
/// Begin a new savepoint with the default behavior (DEFERRED).
|
||||
///
|
||||
/// The savepoint defaults to rolling back when it is dropped. If you want
|
||||
@ -413,7 +461,7 @@ impl Connection {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::DropBehavior;
|
||||
use crate::{Connection, NO_PARAMS};
|
||||
use crate::{Connection, Error, NO_PARAMS};
|
||||
|
||||
fn checked_memory_handle() -> Connection {
|
||||
let db = Connection::open_in_memory().unwrap();
|
||||
@ -443,6 +491,44 @@ mod test {
|
||||
);
|
||||
}
|
||||
}
|
||||
fn assert_nested_tx_error(e: crate::Error) {
|
||||
if let Error::SqliteFailure(e, Some(m)) = &e {
|
||||
assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR);
|
||||
// FIXME: Not ideal...
|
||||
assert_eq!(e.code, crate::ErrorCode::Unknown);
|
||||
assert!(m.contains("transaction"));
|
||||
} else {
|
||||
panic!("Unexpected error type: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unchecked_nesting() {
|
||||
let db = checked_memory_handle();
|
||||
|
||||
{
|
||||
let tx = db.unchecked_transaction().unwrap();
|
||||
let e = tx.unchecked_transaction().unwrap_err();
|
||||
assert_nested_tx_error(e);
|
||||
// default: rollback
|
||||
}
|
||||
{
|
||||
let tx = db.unchecked_transaction().unwrap();
|
||||
tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
|
||||
// Ensure this doesn't interfere with ongoing transaction
|
||||
let e = tx.unchecked_transaction().unwrap_err();
|
||||
assert_nested_tx_error(e);
|
||||
|
||||
tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
|
||||
tx.commit().unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
2i32,
|
||||
db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_explicit_rollback_commit() {
|
||||
|
Loading…
Reference in New Issue
Block a user