use std::ops::Deref;
use {Result, Connection};
#[deprecated(since = "0.6.0", note = "Use TransactionBehavior instead")]
pub type SqliteTransactionBehavior = TransactionBehavior;
#[derive(Copy,Clone)]
pub enum TransactionBehavior {
Deferred,
Immediate,
Exclusive,
}
#[derive(Copy,Clone,PartialEq,Eq)]
pub enum DropBehavior {
Rollback,
Commit,
Ignore,
}
#[deprecated(since = "0.6.0", note = "Use Transaction instead")]
pub type SqliteTransaction<'conn> = Transaction<'conn>;
pub struct Transaction<'conn> {
conn: &'conn Connection,
drop_behavior: DropBehavior,
committed: bool,
}
pub struct Savepoint<'conn> {
conn: &'conn Connection,
name: String,
depth: u32,
drop_behavior: DropBehavior,
committed: bool,
}
impl<'conn> Transaction<'conn> {
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",
};
conn.execute_batch(query).map(move |_| {
Transaction {
conn: conn,
drop_behavior: DropBehavior::Rollback,
committed: false,
}
})
}
pub fn savepoint(&mut self) -> Result<Savepoint> {
Savepoint::with_depth(self.conn, 1)
}
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint> {
Savepoint::with_depth_and_name(self.conn, 1, name)
}
pub fn drop_behavior(&self) -> DropBehavior {
self.drop_behavior
}
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
self.drop_behavior = drop_behavior
}
pub fn commit(mut self) -> Result<()> {
self.commit_()
}
fn commit_(&mut self) -> Result<()> {
self.committed = true;
self.conn.execute_batch("COMMIT")
}
pub fn rollback(mut self) -> Result<()> {
self.rollback_()
}
fn rollback_(&mut self) -> Result<()> {
self.committed = true;
self.conn.execute_batch("ROLLBACK")
}
pub fn finish(mut self) -> Result<()> {
self.finish_()
}
fn finish_(&mut self) -> Result<()> {
if self.committed {
return Ok(());
}
match self.drop_behavior() {
DropBehavior::Commit => self.commit_(),
DropBehavior::Rollback => self.rollback_(),
DropBehavior::Ignore => Ok(()),
}
}
}
impl<'conn> Deref for Transaction<'conn> {
type Target = Connection;
fn deref(&self) -> &Connection {
self.conn
}
}
#[allow(unused_must_use)]
impl<'conn> Drop for Transaction<'conn> {
fn drop(&mut self) {
self.finish_();
}
}
impl<'conn> Savepoint<'conn> {
fn with_depth_and_name<T: Into<String>>(conn: &Connection,
depth: u32,
name: T)
-> Result<Savepoint> {
let name = name.into();
conn.execute_batch(&format!("SAVEPOINT {}", name)).map(|_| {
Savepoint {
conn: conn,
name: name,
depth: depth,
drop_behavior: DropBehavior::Rollback,
committed: false,
}
})
}
fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint> {
let name = format!("_rusqlite_sp_{}", depth);
Savepoint::with_depth_and_name(conn, depth, name)
}
pub fn new(conn: &mut Connection) -> Result<Savepoint> {
Savepoint::with_depth(conn, 0)
}
pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint> {
Savepoint::with_depth_and_name(conn, 0, name)
}
pub fn savepoint(&mut self) -> Result<Savepoint> {
Savepoint::with_depth(self.conn, self.depth + 1)
}
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint> {
Savepoint::with_depth_and_name(self.conn, self.depth + 1, name)
}
pub fn drop_behavior(&self) -> DropBehavior {
self.drop_behavior
}
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
self.drop_behavior = drop_behavior
}
pub fn commit(mut self) -> Result<()> {
self.commit_()
}
fn commit_(&mut self) -> Result<()> {
self.committed = true;
self.conn.execute_batch(&format!("RELEASE {}", self.name))
}
pub fn rollback(&mut self) -> Result<()> {
self.conn.execute_batch(&format!("ROLLBACK TO {}", self.name))
}
pub fn finish(mut self) -> Result<()> {
self.finish_()
}
fn finish_(&mut self) -> Result<()> {
if self.committed {
return Ok(());
}
match self.drop_behavior() {
DropBehavior::Commit => self.commit_(),
DropBehavior::Rollback => self.rollback(),
DropBehavior::Ignore => Ok(()),
}
}
}
impl<'conn> Deref for Savepoint<'conn> {
type Target = Connection;
fn deref(&self) -> &Connection {
self.conn
}
}
#[allow(unused_must_use)]
impl<'conn> Drop for Savepoint<'conn> {
fn drop(&mut self) {
self.finish_();
}
}
impl Connection {
pub fn transaction(&mut self) -> Result<Transaction> {
Transaction::new(self, TransactionBehavior::Deferred)
}
pub fn transaction_with_behavior(&mut self,
behavior: TransactionBehavior)
-> Result<Transaction> {
Transaction::new(self, behavior)
}
pub fn savepoint(&mut self) -> Result<Savepoint> {
Savepoint::new(self)
}
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint> {
Savepoint::with_name(self, name)
}
}
#[cfg(test)]
mod test {
use Connection;
use super::DropBehavior;
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 mut db = checked_memory_handle();
{
let tx = db.transaction().unwrap();
tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
}
{
let mut tx = db.transaction().unwrap();
tx.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
tx.set_drop_behavior(DropBehavior::Commit)
}
{
let tx = db.transaction().unwrap();
assert_eq!(2i32,
tx.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap());
}
}
#[test]
fn test_explicit_rollback_commit() {
let mut db = checked_memory_handle();
{
let mut tx = db.transaction().unwrap();
{
let mut sp = tx.savepoint().unwrap();
sp.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
sp.rollback().unwrap();
sp.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
sp.commit().unwrap();
}
tx.commit().unwrap();
}
{
let tx = db.transaction().unwrap();
tx.execute_batch("INSERT INTO foo VALUES(4)").unwrap();
tx.commit().unwrap();
}
{
let tx = db.transaction().unwrap();
assert_eq!(6i32,
tx.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap());
}
}
#[test]
fn test_savepoint() {
let mut db = checked_memory_handle();
{
let mut tx = db.transaction().unwrap();
tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
assert_current_sum(1, &tx);
tx.set_drop_behavior(DropBehavior::Commit);
{
let mut sp1 = tx.savepoint().unwrap();
sp1.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
assert_current_sum(3, &sp1);
{
let mut sp2 = sp1.savepoint().unwrap();
sp2.execute_batch("INSERT INTO foo VALUES(4)").unwrap();
assert_current_sum(7, &sp2);
{
let sp3 = sp2.savepoint().unwrap();
sp3.execute_batch("INSERT INTO foo VALUES(8)").unwrap();
assert_current_sum(15, &sp3);
sp3.commit().unwrap();
}
assert_current_sum(15, &sp2);
}
assert_current_sum(3, &sp1);
}
assert_current_sum(1, &tx);
}
assert_current_sum(1, &db);
}
#[test]
fn test_ignore_drop_behavior() {
let mut db = checked_memory_handle();
let mut tx = db.transaction().unwrap();
{
let mut sp1 = tx.savepoint().unwrap();
insert(1, &sp1);
sp1.rollback().unwrap();
insert(2, &sp1);
{
let mut sp2 = sp1.savepoint().unwrap();
sp2.set_drop_behavior(DropBehavior::Ignore);
insert(4, &sp2);
}
assert_current_sum(6, &sp1);
sp1.commit().unwrap();
}
assert_current_sum(6, &tx);
}
#[test]
fn test_savepoint_names() {
let mut db = checked_memory_handle();
{
let mut sp1 = db.savepoint_with_name("my_sp").unwrap();
insert(1, &sp1);
assert_current_sum(1, &sp1);
{
let mut sp2 = sp1.savepoint_with_name("my_sp").unwrap();
sp2.set_drop_behavior(DropBehavior::Commit);
insert(2, &sp2);
assert_current_sum(3, &sp2);
sp2.rollback().unwrap();
assert_current_sum(1, &sp2);
insert(4, &sp2);
}
assert_current_sum(5, &sp1);
sp1.rollback().unwrap();
{
let mut sp2 = sp1.savepoint_with_name("my_sp").unwrap();
sp2.set_drop_behavior(DropBehavior::Ignore);
insert(8, &sp2);
}
assert_current_sum(8, &sp1);
sp1.commit().unwrap();
}
assert_current_sum(8, &db);
}
fn insert(x: i32, conn: &Connection) {
conn.execute("INSERT INTO foo VALUES(?)", &[&x]).unwrap();
}
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);
}
}