mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-23 00:39:20 +08:00
256 lines
7.5 KiB
Rust
256 lines
7.5 KiB
Rust
use {Result, Connection};
|
|
|
|
/// Old name for `TransactionBehavior`. `SqliteTransactionBehavior` is deprecated.
|
|
pub type SqliteTransactionBehavior = TransactionBehavior;
|
|
|
|
/// Options for transaction behavior. See [BEGIN
|
|
/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details.
|
|
#[derive(Copy,Clone)]
|
|
pub enum TransactionBehavior {
|
|
Deferred,
|
|
Immediate,
|
|
Exclusive,
|
|
}
|
|
|
|
/// Old name for `Transaction`. `SqliteTransaction` is deprecated.
|
|
pub type SqliteTransaction<'conn> = Transaction<'conn>;
|
|
|
|
///
|
|
/// 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
|
|
/// # 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<()> {
|
|
/// 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,
|
|
depth: u32,
|
|
commit: bool,
|
|
finished: bool,
|
|
}
|
|
|
|
impl<'conn> Transaction<'conn> {
|
|
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested transactions.
|
|
pub fn new(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(|_| {
|
|
Transaction {
|
|
conn: conn,
|
|
depth: 0,
|
|
commit: false,
|
|
finished: false,
|
|
}
|
|
})
|
|
}
|
|
|
|
/// 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: &Connection) -> Result<()> {
|
|
/// 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<'a>(&'a self) -> Result<Transaction<'a>> {
|
|
self.conn.execute_batch("SAVEPOINT sp").map(|_| {
|
|
Transaction {
|
|
conn: self.conn,
|
|
depth: self.depth + 1,
|
|
commit: false,
|
|
finished: false,
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Returns whether or not the transaction is currently set to commit.
|
|
pub fn will_commit(&self) -> bool {
|
|
self.commit
|
|
}
|
|
|
|
/// Returns whether or not the transaction is currently set to rollback.
|
|
pub fn will_rollback(&self) -> bool {
|
|
!self.commit
|
|
}
|
|
|
|
/// Set the transaction to commit at its completion.
|
|
pub fn set_commit(&mut self) {
|
|
self.commit = true
|
|
}
|
|
|
|
/// Set the transaction to rollback at its completion.
|
|
pub fn set_rollback(&mut self) {
|
|
self.commit = false
|
|
}
|
|
|
|
/// A convenience method which consumes and commits a transaction.
|
|
pub fn commit(mut self) -> Result<()> {
|
|
self.commit_()
|
|
}
|
|
|
|
fn commit_(&mut self) -> Result<()> {
|
|
self.finished = true;
|
|
self.conn.execute_batch(if self.depth == 0 {
|
|
"COMMIT"
|
|
} else {
|
|
"RELEASE sp"
|
|
})
|
|
}
|
|
|
|
/// A convenience method which consumes and rolls back a transaction.
|
|
pub fn rollback(mut self) -> Result<()> {
|
|
self.rollback_()
|
|
}
|
|
|
|
fn rollback_(&mut self) -> Result<()> {
|
|
self.finished = true;
|
|
self.conn.execute_batch(if self.depth == 0 {
|
|
"ROLLBACK"
|
|
} else {
|
|
"ROLLBACK TO sp"
|
|
})
|
|
}
|
|
|
|
/// 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.
|
|
pub fn finish(mut self) -> Result<()> {
|
|
self.finish_()
|
|
}
|
|
|
|
fn finish_(&mut self) -> Result<()> {
|
|
match (self.finished, self.commit) {
|
|
(true, _) => Ok(()),
|
|
(false, true) => self.commit_(),
|
|
(false, false) => self.rollback_(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(unused_must_use)]
|
|
impl<'conn> Drop for Transaction<'conn> {
|
|
fn drop(&mut self) {
|
|
self.finish_();
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use Connection;
|
|
|
|
fn checked_memory_handle() -> Connection {
|
|
let db = Connection::open_in_memory().unwrap();
|
|
db.execute_batch("CREATE TABLE foo (x INTEGER)").unwrap();
|
|
db
|
|
}
|
|
|
|
#[test]
|
|
fn test_drop() {
|
|
let db = checked_memory_handle();
|
|
{
|
|
let _tx = db.transaction().unwrap();
|
|
db.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
|
|
// default: rollback
|
|
}
|
|
{
|
|
let mut tx = db.transaction().unwrap();
|
|
db.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
|
|
tx.set_commit()
|
|
}
|
|
{
|
|
let _tx = db.transaction().unwrap();
|
|
assert_eq!(2i32,
|
|
db.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_explicit_rollback_commit() {
|
|
let db = checked_memory_handle();
|
|
{
|
|
let tx = db.transaction().unwrap();
|
|
db.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
|
|
tx.rollback().unwrap();
|
|
}
|
|
{
|
|
let tx = db.transaction().unwrap();
|
|
db.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
|
|
tx.commit().unwrap();
|
|
}
|
|
{
|
|
let _tx = db.transaction().unwrap();
|
|
assert_eq!(2i32,
|
|
db.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_savepoint() {
|
|
let db = checked_memory_handle();
|
|
{
|
|
let mut tx = db.transaction().unwrap();
|
|
db.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
|
|
tx.set_commit();
|
|
{
|
|
let mut sp1 = tx.savepoint().unwrap();
|
|
db.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
|
|
sp1.set_commit();
|
|
{
|
|
let sp2 = sp1.savepoint().unwrap();
|
|
db.execute_batch("INSERT INTO foo VALUES(4)").unwrap();
|
|
// will rollback sp2
|
|
{
|
|
let sp3 = sp2.savepoint().unwrap();
|
|
db.execute_batch("INSERT INTO foo VALUES(8)").unwrap();
|
|
sp3.commit().unwrap();
|
|
// committed sp3, but will be erased by sp2 rollback
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert_eq!(3i32,
|
|
db.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap());
|
|
}
|
|
}
|