mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-23 00:39:20 +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<'_> {
|
impl Transaction<'_> {
|
||||||
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested
|
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested
|
||||||
/// transactions.
|
/// transactions.
|
||||||
|
///
|
||||||
/// Even though we don't mutate the connection, we take a `&mut Connection`
|
/// Even though we don't mutate the connection, we take a `&mut Connection`
|
||||||
/// so as to prevent nested or concurrent transactions on the same
|
/// so as to prevent nested transactions on the same connection. For cases
|
||||||
/// connection.
|
/// where this is unacceptable, [`Transaction::new_unchecked`] is available.
|
||||||
pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>> {
|
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 {
|
let query = match behavior {
|
||||||
TransactionBehavior::Deferred => "BEGIN DEFERRED",
|
TransactionBehavior::Deferred => "BEGIN DEFERRED",
|
||||||
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
|
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
|
||||||
@ -369,6 +382,41 @@ impl Connection {
|
|||||||
Transaction::new(self, behavior)
|
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).
|
/// Begin a new savepoint with the default behavior (DEFERRED).
|
||||||
///
|
///
|
||||||
/// The savepoint defaults to rolling back when it is dropped. If you want
|
/// The savepoint defaults to rolling back when it is dropped. If you want
|
||||||
@ -413,7 +461,7 @@ impl Connection {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::DropBehavior;
|
use super::DropBehavior;
|
||||||
use crate::{Connection, NO_PARAMS};
|
use crate::{Connection, Error, NO_PARAMS};
|
||||||
|
|
||||||
fn checked_memory_handle() -> Connection {
|
fn checked_memory_handle() -> Connection {
|
||||||
let db = Connection::open_in_memory().unwrap();
|
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]
|
#[test]
|
||||||
fn test_explicit_rollback_commit() {
|
fn test_explicit_rollback_commit() {
|
||||||
|
Loading…
Reference in New Issue
Block a user