rusqlite/src/transaction.rs

279 lines
8.4 KiB
Rust
Raw Normal View History

use std::borrow::Cow;
use std::ops::Deref;
2015-12-13 03:06:03 +08:00
use {Result, Connection};
2014-10-20 07:56:41 +08:00
/// Old name for `TransactionBehavior`. `SqliteTransactionBehavior` is deprecated.
pub type SqliteTransactionBehavior = TransactionBehavior;
2014-11-19 23:48:40 +08:00
2014-11-04 06:11:00 +08:00
/// Options for transaction behavior. See [BEGIN
/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details.
2015-04-03 21:32:11 +08:00
#[derive(Copy,Clone)]
pub enum TransactionBehavior {
Deferred,
Immediate,
Exclusive,
2014-10-20 07:56:41 +08:00
}
/// Old name for `Transaction`. `SqliteTransaction` is deprecated.
pub type SqliteTransaction<'conn> = Transaction<'conn>;
///
2014-11-04 06:11:00 +08:00
/// Represents a transaction on a database connection.
///
/// ## Note
///
/// Transactions will roll back by default. Use the `set_commit` or `commit` methods to commit the
/// transaction.
///
/// ## Example
///
/// ```rust,no_run
2015-12-13 03:06:03 +08:00
/// # 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: &Connection) -> Result<()> {
2014-11-04 06:11:00 +08:00
/// let tx = try!(conn.transaction());
///
/// try!(do_queries_part_1(conn)); // tx causes rollback if this fails
/// try!(do_queries_part_2(conn)); // tx causes rollback if this fails
///
/// tx.commit()
/// }
/// ```
pub struct Transaction<'conn> {
conn: &'conn Connection,
2014-10-20 07:56:41 +08:00
depth: u32,
commit: bool,
finished: bool,
}
impl<'conn> Transaction<'conn> {
2014-11-04 06:11:00 +08:00
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested transactions.
pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction> {
let query = match behavior {
TransactionBehavior::Deferred => "BEGIN DEFERRED",
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
2014-10-20 07:56:41 +08:00
};
conn.execute_batch(query).map(move |_| {
Transaction {
2015-12-11 05:48:09 +08:00
conn: conn,
depth: 0,
commit: false,
finished: false,
}
2014-10-20 07:56:41 +08:00
})
}
2014-11-04 06:11:00 +08:00
/// 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
2015-12-13 03:06:03 +08:00
/// # use rusqlite::{Connection, Result};
/// # fn perform_queries_part_1_succeeds(conn: &Connection) -> bool { true }
2015-12-13 03:06:03 +08:00
/// fn perform_queries(conn: &Connection) -> Result<()> {
2014-11-04 06:11:00 +08:00
/// let tx = try!(conn.transaction());
///
/// {
/// let sp = try!(tx.savepoint());
/// if perform_queries_part_1_succeeds(conn) {
/// try!(sp.commit());
/// }
/// // otherwise, sp will rollback
/// }
///
/// tx.commit()
/// }
/// ```
pub fn savepoint(&mut self) -> Result<Transaction> {
let new_depth = self.depth + 1;
self.conn.execute_batch(&format!("SAVEPOINT sp{}", new_depth)).map(|_| {
Transaction {
2015-12-11 05:48:09 +08:00
conn: self.conn,
depth: new_depth,
2015-12-11 05:48:09 +08:00
commit: false,
finished: false,
2014-10-20 07:56:41 +08:00
}
})
}
2014-11-04 06:11:00 +08:00
/// Returns whether or not the transaction is currently set to commit.
2014-10-20 07:56:41 +08:00
pub fn will_commit(&self) -> bool {
self.commit
}
2014-11-04 06:11:00 +08:00
/// Returns whether or not the transaction is currently set to rollback.
2014-10-20 07:56:41 +08:00
pub fn will_rollback(&self) -> bool {
!self.commit
}
2014-11-04 06:11:00 +08:00
/// Set the transaction to commit at its completion.
2014-10-20 07:56:41 +08:00
pub fn set_commit(&mut self) {
self.commit = true
}
2014-11-04 06:11:00 +08:00
/// Set the transaction to rollback at its completion.
2014-10-20 07:56:41 +08:00
pub fn set_rollback(&mut self) {
self.commit = false
}
2014-11-04 06:11:00 +08:00
/// A convenience method which consumes and commits a transaction.
2015-12-13 03:06:03 +08:00
pub fn commit(mut self) -> Result<()> {
2014-10-20 07:56:41 +08:00
self.commit_()
}
2015-12-13 03:06:03 +08:00
fn commit_(&mut self) -> Result<()> {
2014-10-20 07:56:41 +08:00
self.finished = true;
let sql = if self.depth == 0 {
Cow::Borrowed("COMMIT")
2015-12-11 05:48:09 +08:00
} else {
Cow::Owned(format!("RELEASE sp{}", self.depth))
};
self.conn.execute_batch(&sql)
2014-10-20 07:56:41 +08:00
}
2014-11-04 06:11:00 +08:00
/// A convenience method which consumes and rolls back a transaction.
2015-12-13 03:06:03 +08:00
pub fn rollback(mut self) -> Result<()> {
2014-10-20 07:56:41 +08:00
self.rollback_()
}
2015-12-13 03:06:03 +08:00
fn rollback_(&mut self) -> Result<()> {
2014-10-20 07:56:41 +08:00
self.finished = true;
let sql = if self.depth == 0 {
Cow::Borrowed("ROLLBACK")
2015-12-11 05:48:09 +08:00
} else {
Cow::Owned(format!("ROLLBACK TO sp{}", self.depth))
};
self.conn.execute_batch(&sql)
2014-10-20 07:56:41 +08:00
}
2014-11-04 06:11:00 +08:00
/// Consumes the transaction, committing or rolling back according to the current setting
/// (see `will_commit`, `will_rollback`).
///
/// Functionally equivalent to the `Drop` implementation, but allows callers to see any
/// errors that occur.
2015-12-13 03:06:03 +08:00
pub fn finish(mut self) -> Result<()> {
2014-10-20 07:56:41 +08:00
self.finish_()
}
2015-12-13 03:06:03 +08:00
fn finish_(&mut self) -> Result<()> {
2014-10-20 07:56:41 +08:00
match (self.finished, self.commit) {
(true, _) => Ok(()),
(false, true) => self.commit_(),
(false, false) => self.rollback_(),
}
}
}
impl<'conn> Deref for Transaction<'conn> {
type Target = Connection;
fn deref(&self) -> &Connection {
self.conn
}
}
2014-10-20 07:56:41 +08:00
#[allow(unused_must_use)]
impl<'conn> Drop for Transaction<'conn> {
2014-10-20 07:56:41 +08:00
fn drop(&mut self) {
self.finish_();
}
}
#[cfg(test)]
2016-03-30 02:18:56 +08:00
#[cfg_attr(feature="clippy", allow(similar_names))]
2014-10-20 07:56:41 +08:00
mod test {
use Connection;
2014-10-20 07:56:41 +08:00
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
2014-10-20 07:56:41 +08:00
db.execute_batch("CREATE TABLE foo (x INTEGER)").unwrap();
db
}
#[test]
fn test_drop() {
let mut db = checked_memory_handle();
2014-10-20 07:56:41 +08:00
{
let tx = db.transaction().unwrap();
tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
2014-10-20 07:56:41 +08:00
// default: rollback
}
{
let mut tx = db.transaction().unwrap();
tx.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
2014-10-20 07:56:41 +08:00
tx.set_commit()
}
{
let tx = db.transaction().unwrap();
2015-12-11 05:48:09 +08:00
assert_eq!(2i32,
tx.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap());
2014-10-20 07:56:41 +08:00
}
}
#[test]
fn test_explicit_rollback_commit() {
let mut db = checked_memory_handle();
2014-10-20 07:56:41 +08:00
{
let tx = db.transaction().unwrap();
tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
2014-10-20 07:56:41 +08:00
tx.rollback().unwrap();
}
{
let tx = db.transaction().unwrap();
tx.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
2014-10-20 07:56:41 +08:00
tx.commit().unwrap();
}
{
let tx = db.transaction().unwrap();
2015-12-11 05:48:09 +08:00
assert_eq!(2i32,
tx.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap());
2014-10-20 07:56:41 +08:00
}
}
#[test]
fn test_savepoint() {
fn assert_current_sum(x: i32, conn: &Connection) {
let i = conn.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap();
assert_eq!(x, i);
}
let mut db = checked_memory_handle();
2014-10-20 07:56:41 +08:00
{
let mut tx = db.transaction().unwrap();
tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
assert_current_sum(1, &tx);
2014-10-20 07:56:41 +08:00
tx.set_commit();
{
let mut sp1 = tx.savepoint().unwrap();
sp1.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
assert_current_sum(3, &sp1);
// will rollback sp1
2014-10-20 07:56:41 +08:00
{
let mut sp2 = sp1.savepoint().unwrap();
sp2.execute_batch("INSERT INTO foo VALUES(4)").unwrap();
assert_current_sum(7, &sp2);
2014-10-20 07:56:41 +08:00
// will rollback sp2
{
let sp3 = sp2.savepoint().unwrap();
sp3.execute_batch("INSERT INTO foo VALUES(8)").unwrap();
assert_current_sum(15, &sp3);
2014-10-20 07:56:41 +08:00
sp3.commit().unwrap();
// committed sp3, but will be erased by sp2 rollback
}
assert_current_sum(15, &sp2);
2014-10-20 07:56:41 +08:00
}
assert_current_sum(3, &sp1);
2014-10-20 07:56:41 +08:00
}
assert_current_sum(1, &tx);
2014-10-20 07:56:41 +08:00
}
assert_current_sum(1, &db);
2014-10-20 07:56:41 +08:00
}
}