Make transactions roll back by default

This commit is contained in:
Steven Fackler 2014-07-25 22:41:10 -07:00
parent 1c9d482c1d
commit cc75249620
2 changed files with 43 additions and 27 deletions

View File

@ -83,7 +83,6 @@ use std::from_str::FromStr;
use std::io::{BufferedStream, IoResult};
use std::io::net::ip::Port;
use std::mem;
use std::task;
use std::fmt;
use error::{InvalidUrl,
@ -784,8 +783,10 @@ impl PostgresConnection {
/// Returns a `PostgresTransaction` object which should be used instead of
/// the connection for the duration of the transaction. The transaction
/// is active until the `PostgresTransaction` object falls out of scope.
/// A transaction will commit by default unless the task fails or the
/// transaction is set to roll back.
///
/// # Note
/// A transaction will roll back by default. Use the `set_commit` method to
/// set the transaction to commit.
///
/// # Example
///
@ -795,13 +796,10 @@ impl PostgresConnection {
/// # let conn = PostgresConnection::connect("", &NoSsl).unwrap();
/// let trans = try!(conn.transaction());
/// try!(trans.execute("UPDATE foo SET bar = 10", []));
/// // ...
///
/// # let something_bad_happened = true;
/// if something_bad_happened {
/// trans.set_rollback();
/// }
///
/// drop(trans);
/// trans.set_commit();
/// try!(trans.finish());
/// # Ok(())
/// # }
/// ```
@ -815,7 +813,7 @@ impl PostgresConnection {
self.conn.borrow_mut().trans_depth += 1;
Ok(PostgresTransaction {
conn: self,
commit: Cell::new(true),
commit: Cell::new(false),
depth: 1,
finished: false,
})
@ -937,7 +935,7 @@ pub enum SslMode {
/// Represents a transaction on a database connection.
///
/// The transaction will commit by default.
/// The transaction will roll back by default.
pub struct PostgresTransaction<'conn> {
conn: &'conn PostgresConnection,
commit: Cell<bool>,
@ -957,12 +955,11 @@ impl<'conn> Drop for PostgresTransaction<'conn> {
impl<'conn> PostgresTransaction<'conn> {
fn finish_inner(&mut self) -> PostgresResult<()> {
debug_assert!(self.depth == self.conn.conn.borrow().trans_depth);
let rollback = task::failing() || !self.commit.get();
let query = match (rollback, self.depth != 1) {
(true, true) => "ROLLBACK TO sp",
(true, false) => "ROLLBACK",
(false, true) => "RELEASE sp",
(false, false) => "COMMIT",
let query = match (self.commit.get(), self.depth != 1) {
(false, true) => "ROLLBACK TO sp",
(false, false) => "ROLLBACK",
(true, true) => "RELEASE sp",
(true, false) => "COMMIT",
};
self.conn.conn.borrow_mut().trans_depth -= 1;
self.conn.quick_query(query).map(|_| ())
@ -1002,7 +999,7 @@ impl<'conn> PostgresTransaction<'conn> {
self.conn.conn.borrow_mut().trans_depth += 1;
Ok(PostgresTransaction {
conn: self.conn,
commit: Cell::new(true),
commit: Cell::new(false),
depth: self.depth + 1,
finished: false,
})
@ -1023,6 +1020,12 @@ impl<'conn> PostgresTransaction<'conn> {
self.commit.set(false);
}
/// A convenience method which consumes and commits a transaction.
pub fn commit(self) -> PostgresResult<()> {
self.set_commit();
self.finish()
}
/// Consumes the transaction, commiting or rolling it back as appropriate.
///
/// Functionally equivalent to the `Drop` implementation of

View File

@ -118,6 +118,7 @@ fn test_transaction_commit() {
let trans = or_fail!(conn.transaction());
or_fail!(trans.execute("INSERT INTO foo (id) VALUES ($1)", [&1i32]));
trans.set_commit();
drop(trans);
let stmt = or_fail!(conn.prepare("SELECT * FROM foo"));
@ -133,6 +134,7 @@ fn test_transaction_commit_finish() {
let trans = or_fail!(conn.transaction());
or_fail!(trans.execute("INSERT INTO foo (id) VALUES ($1)", [&1i32]));
trans.set_commit();
assert!(trans.finish().is_ok());
let stmt = or_fail!(conn.prepare("SELECT * FROM foo"));
@ -141,6 +143,21 @@ fn test_transaction_commit_finish() {
assert_eq!(vec![1i32], result.map(|row| row.get(0u)).collect());
}
#[test]
fn test_transaction_commit_method() {
let conn = or_fail!(PostgresConnection::connect("postgres://postgres@localhost", &NoSsl));
or_fail!(conn.execute("CREATE TEMPORARY TABLE foo (id INT PRIMARY KEY)", []));
let trans = or_fail!(conn.transaction());
or_fail!(trans.execute("INSERT INTO foo (id) VALUES ($1)", [&1i32]));
assert!(trans.commit().is_ok());
let stmt = or_fail!(conn.prepare("SELECT * FROM foo"));
let result = or_fail!(stmt.query([]));
assert_eq!(vec![1i32], result.map(|row| row.get(0u)).collect());
}
#[test]
fn test_transaction_rollback() {
let conn = or_fail!(PostgresConnection::connect("postgres://postgres@localhost", &NoSsl));
@ -150,7 +167,6 @@ fn test_transaction_rollback() {
let trans = or_fail!(conn.transaction());
or_fail!(trans.execute("INSERT INTO foo (id) VALUES ($1)", [&2i32]));
trans.set_rollback();
drop(trans);
let stmt = or_fail!(conn.prepare("SELECT * FROM foo"));
@ -168,7 +184,6 @@ fn test_transaction_rollback_finish() {
let trans = or_fail!(conn.transaction());
or_fail!(trans.execute("INSERT INTO foo (id) VALUES ($1)", [&2i32]));
trans.set_rollback();
assert!(trans.finish().is_ok());
let stmt = or_fail!(conn.prepare("SELECT * FROM foo"));
@ -191,7 +206,6 @@ fn test_nested_transactions() {
{
let trans2 = or_fail!(trans1.transaction());
or_fail!(trans2.execute("INSERT INTO foo (id) VALUES (3)", []));
trans2.set_rollback();
}
{
@ -201,21 +215,21 @@ fn test_nested_transactions() {
{
let trans3 = or_fail!(trans2.transaction());
or_fail!(trans3.execute("INSERT INTO foo (id) VALUES (5)", []));
trans3.set_rollback();
}
{
let trans3 = or_fail!(trans2.transaction());
or_fail!(trans3.execute("INSERT INTO foo (id) VALUES (6)", []));
assert!(trans3.commit().is_ok());
}
assert!(trans2.commit().is_ok());
}
let stmt = or_fail!(trans1.prepare("SELECT * FROM foo ORDER BY id"));
let result = or_fail!(stmt.query([]));
assert_eq!(vec![1i32, 2, 4, 6], result.map(|row| row.get(0u)).collect());
trans1.set_rollback();
}
let stmt = or_fail!(conn.prepare("SELECT * FROM foo ORDER BY id"));
@ -238,7 +252,6 @@ fn test_nested_transactions_finish() {
{
let trans2 = or_fail!(trans1.transaction());
or_fail!(trans2.execute("INSERT INTO foo (id) VALUES (3)", []));
trans2.set_rollback();
assert!(trans2.finish().is_ok());
}
@ -249,16 +262,17 @@ fn test_nested_transactions_finish() {
{
let trans3 = or_fail!(trans2.transaction());
or_fail!(trans3.execute("INSERT INTO foo (id) VALUES (5)", []));
trans3.set_rollback();
assert!(trans3.finish().is_ok());
}
{
let trans3 = or_fail!(trans2.transaction());
or_fail!(trans3.execute("INSERT INTO foo (id) VALUES (6)", []));
trans3.set_commit();
assert!(trans3.finish().is_ok());
}
trans2.set_commit();
assert!(trans2.finish().is_ok());
}
@ -270,7 +284,6 @@ fn test_nested_transactions_finish() {
assert_eq!(vec![1i32, 2, 4, 6], result.map(|row| row.get(0u)).collect());
}
trans1.set_rollback();
assert!(trans1.finish().is_ok());
}