usecrate::{Connection, Result}; use std::ops::Deref;
/// Options for transaction behavior. See [BEGIN /// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details. #[derive(Copy, Clone)] #[non_exhaustive] pubenum TransactionBehavior { /// DEFERRED means that the transaction does not actually start until the /// database is first accessed.
Deferred, /// IMMEDIATE cause the database connection to start a new write /// immediately, without waiting for a writes statement.
Immediate, /// EXCLUSIVE prevents other database connections from reading the database /// while the transaction is underway.
Exclusive,
}
/// Options for how a Transaction or Savepoint should behave when it is dropped. #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pubenum DropBehavior { /// Roll back the changes. This is the default.
Rollback,
/// Commit the changes.
Commit,
/// Do not commit or roll back changes - this will leave the transaction or /// savepoint open, so should be used with care.
Ignore,
/// Panic. Used to enforce intentional behavior during development.
Panic,
}
/// Represents a transaction on a database connection. /// /// ## Note /// /// Transactions will roll back by default. Use `commit` method to explicitly /// commit the transaction, or use `set_drop_behavior` to change what happens /// when the transaction is dropped. /// /// ## Example /// /// ```rust,no_run /// # use rusqlite::{Connection, Result}; /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } /// fn perform_queries(conn: &mut Connection) -> Result<()> { /// let tx = conn.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() /// } /// ``` #[derive(Debug)] pubstruct Transaction<'conn> {
conn: &'conn Connection,
drop_behavior: DropBehavior,
}
/// Represents a savepoint on a database connection. /// /// ## Note /// /// Savepoints will roll back by default. Use `commit` method to explicitly /// commit the savepoint, or use `set_drop_behavior` to change what happens /// when the savepoint is dropped. /// /// ## Example /// /// ```rust,no_run /// # use rusqlite::{Connection, Result}; /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } /// fn perform_queries(conn: &mut Connection) -> Result<()> { /// let sp = conn.savepoint()?; /// /// do_queries_part_1(&sp)?; // sp causes rollback if this fails /// do_queries_part_2(&sp)?; // sp causes rollback if this fails /// /// sp.commit() /// } /// ``` #[derive(Debug)] pubstruct Savepoint<'conn> {
conn: &'conn Connection,
name: String,
drop_behavior: DropBehavior,
committed: bool,
}
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 transactions on the same connection. For cases /// where this is unacceptable, [`Transaction::new_unchecked`] is available. #[inline] pubfn 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. #[inline] pubfn new_unchecked(
conn: &Connection,
behavior: TransactionBehavior,
) -> Result<Transaction<'_>> { let query = match behavior {
TransactionBehavior::Deferred => "BEGIN DEFERRED",
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
};
conn.execute_batch(query).map(move |()| Transaction {
conn,
drop_behavior: DropBehavior::Rollback,
})
}
/// Starts a new [savepoint](http://www.sqlite.org/lang_savepoint.html), allowing nested /// transactions. /// /// ## Note /// /// Just like outer level transactions, savepoint transactions rollback by /// default. /// /// ## Example /// /// ```rust,no_run /// # use rusqlite::{Connection, Result}; /// # fn perform_queries_part_1_succeeds(_conn: &Connection) -> bool { true } /// fn perform_queries(conn: &mut Connection) -> Result<()> { /// let mut tx = conn.transaction()?; /// /// { /// let sp = tx.savepoint()?; /// if perform_queries_part_1_succeeds(&sp) { /// sp.commit()?; /// } /// // otherwise, sp will rollback /// } /// /// tx.commit() /// } /// ``` #[inline] pubfn savepoint(&mutself) -> Result<Savepoint<'_>> {
Savepoint::new_(self.conn)
}
/// Create a new savepoint with a custom savepoint name. See `savepoint()`. #[inline] pubfn savepoint_with_name<T: Into<String>>(&mutself, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_name_(self.conn, name)
}
/// Get the current setting for what happens to the transaction when it is /// dropped. #[inline] #[must_use] pubfn drop_behavior(&self) -> DropBehavior { self.drop_behavior
}
/// Configure the transaction to perform the specified action when it is /// dropped. #[inline] pubfn set_drop_behavior(&mutself, drop_behavior: DropBehavior) { self.drop_behavior = drop_behavior;
}
/// A convenience method which consumes and commits a transaction. #[inline] pubfn commit(mutself) -> Result<()> { self.commit_()
}
/// Consumes the transaction, committing or rolling back according to the /// current setting (see `drop_behavior`). /// /// Functionally equivalent to the `Drop` implementation, but allows /// callers to see any errors that occur. #[inline] pubfn finish(mutself) -> Result<()> { self.finish_()
}
/// Begin a new savepoint. Can be nested. #[inline] pubfn new(conn: &mut Connection) -> Result<Savepoint<'_>> {
Savepoint::new_(conn)
}
/// Begin a new savepoint with a user-provided savepoint name. #[inline] pubfn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_name_(conn, name)
}
/// Begin a nested savepoint. #[inline] pubfn savepoint(&mutself) -> Result<Savepoint<'_>> {
Savepoint::new_(self.conn)
}
/// Begin a nested savepoint with a user-provided savepoint name. #[inline] pubfn savepoint_with_name<T: Into<String>>(&mutself, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_name_(self.conn, name)
}
/// Get the current setting for what happens to the savepoint when it is /// dropped. #[inline] #[must_use] pubfn drop_behavior(&self) -> DropBehavior { self.drop_behavior
}
/// Configure the savepoint to perform the specified action when it is /// dropped. #[inline] pubfn set_drop_behavior(&mutself, drop_behavior: DropBehavior) { self.drop_behavior = drop_behavior;
}
/// A convenience method which consumes and commits a savepoint. #[inline] pubfn commit(mutself) -> Result<()> { self.commit_()
}
/// A convenience method which rolls back a savepoint. /// /// ## Note /// /// Unlike `Transaction`s, savepoints remain active after they have been /// rolled back, and can be rolled back again or committed. #[inline] pubfn rollback(&mutself) -> Result<()> { self.conn
.execute_batch(&format!("ROLLBACK TO {}", self.name))
}
/// Consumes the savepoint, committing or rolling back according to the /// current setting (see `drop_behavior`). /// /// Functionally equivalent to the `Drop` implementation, but allows /// callers to see any errors that occur. #[inline] pubfn finish(mutself) -> Result<()> { self.finish_()
}
#[allow(unused_must_use)] impl Drop for Savepoint<'_> { #[inline] fn drop(&mutself) { self.finish_();
}
}
/// Transaction state of a database #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] #[cfg(feature = "modern_sqlite")] // 3.37.0 #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] pubenum TransactionState { /// Equivalent to SQLITE_TXN_NONE
None, /// Equivalent to SQLITE_TXN_READ
Read, /// Equivalent to SQLITE_TXN_WRITE
Write,
}
impl Connection { /// Begin a new transaction with the default behavior (DEFERRED). /// /// The transaction defaults to rolling back when it is dropped. If you /// want the transaction to commit, you must call /// [`commit`](Transaction::commit) or /// [`set_drop_behavior(DropBehavior::Commit)`](Transaction::set_drop_behavior). /// /// ## Example /// /// ```rust,no_run /// # use rusqlite::{Connection, Result}; /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } /// fn perform_queries(conn: &mut Connection) -> Result<()> { /// let tx = conn.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. #[inline] pubfn transaction(&mutself) -> Result<Transaction<'_>> {
Transaction::new(self, TransactionBehavior::Deferred)
}
/// Begin a new transaction with a specified behavior. /// /// See [`transaction`](Connection::transaction). /// /// # Failure /// /// Will return `Err` if the underlying SQLite call fails. #[inline] pubfn transaction_with_behavior(
&mutself,
behavior: TransactionBehavior,
) -> Result<Transaction<'_>> {
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. pubfn 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 /// the savepoint to commit, you must call [`commit`](Savepoint::commit) or /// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::set_drop_behavior). /// /// ## Example /// /// ```rust,no_run /// # use rusqlite::{Connection, Result}; /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } /// fn perform_queries(conn: &mut Connection) -> Result<()> { /// let sp = conn.savepoint()?; /// /// do_queries_part_1(&sp)?; // sp causes rollback if this fails /// do_queries_part_2(&sp)?; // sp causes rollback if this fails /// /// sp.commit() /// } /// ``` /// /// # Failure /// /// Will return `Err` if the underlying SQLite call fails. #[inline] pubfn savepoint(&mutself) -> Result<Savepoint<'_>> {
Savepoint::new(self)
}
/// Begin a new savepoint with a specified name. /// /// See [`savepoint`](Connection::savepoint). /// /// # Failure /// /// Will return `Err` if the underlying SQLite call fails. #[inline] pubfn savepoint_with_name<T: Into<String>>(&mutself, name: T) -> Result<Savepoint<'_>> {
Savepoint::with_name(self, name)
}
/// Determine the transaction state of a database #[cfg(feature = "modern_sqlite")] // 3.37.0 #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] pubfn transaction_state(
&self,
db_name: Option<crate::DatabaseName<'_>>,
) -> Result<TransactionState> { self.db.borrow().txn_state(db_name)
}
}
#[cfg(test)] mod test { usesuper::DropBehavior; usecrate::{Connection, Error, Result};
fn checked_memory_handle() -> Result<Connection> { let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo (x INTEGER)")?;
Ok(db)
}
#[test] fn test_unchecked_nesting() -> Result<()> { let db = checked_memory_handle()?;
{ let tx = db.unchecked_transaction()?; let e = tx.unchecked_transaction().unwrap_err();
assert_nested_tx_error(e); // default: rollback
}
{ let tx = db.unchecked_transaction()?;
tx.execute_batch("INSERT INTO foo VALUES(1)")?; // 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)")?;
tx.commit()?;
}
assert_eq!(2i32, db.one_column::<i32>("SELECT SUM(x) FROM foo")?);
Ok(())
}
#[test] fn test_explicit_rollback_commit() -> Result<()> { letmut db = checked_memory_handle()?;
{ letmut tx = db.transaction()?;
{ letmut sp = tx.savepoint()?;
sp.execute_batch("INSERT INTO foo VALUES(1)")?;
sp.rollback()?;
sp.execute_batch("INSERT INTO foo VALUES(2)")?;
sp.commit()?;
}
tx.commit()?;
}
{ let tx = db.transaction()?;
tx.execute_batch("INSERT INTO foo VALUES(4)")?;
tx.commit()?;
}
{ let tx = db.transaction()?;
assert_eq!(6i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?);
}
Ok(())
}
#[test] fn test_savepoint() -> Result<()> { letmut db = checked_memory_handle()?;
{ letmut tx = db.transaction()?;
tx.execute_batch("INSERT INTO foo VALUES(1)")?;
assert_current_sum(1, &tx)?;
tx.set_drop_behavior(DropBehavior::Commit);
{ letmut sp1 = tx.savepoint()?;
sp1.execute_batch("INSERT INTO foo VALUES(2)")?;
assert_current_sum(3, &sp1)?; // will rollback sp1
{ letmut sp2 = sp1.savepoint()?;
sp2.execute_batch("INSERT INTO foo VALUES(4)")?;
assert_current_sum(7, &sp2)?; // will rollback sp2
{ let sp3 = sp2.savepoint()?;
sp3.execute_batch("INSERT INTO foo VALUES(8)")?;
assert_current_sum(15, &sp3)?;
sp3.commit()?; // committed sp3, but will be erased by sp2 rollback
}
assert_current_sum(15, &sp2)?;
}
assert_current_sum(3, &sp1)?;
}
assert_current_sum(1, &tx)?;
}
assert_current_sum(1, &db)?;
Ok(())
}
#[test] fn test_ignore_drop_behavior() -> Result<()> { letmut db = checked_memory_handle()?;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.