Skip to content

Commit

Permalink
Merge pull request #261 from libsql/transactions
Browse files Browse the repository at this point in the history
 libsql/core: Add support for transactions
  • Loading branch information
penberg authored Aug 10, 2023
2 parents 0b22760 + 8345447 commit 4872c95
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 1 deletion.
15 changes: 14 additions & 1 deletion crates/core/src/connection.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Database, Error, Params, Result, Rows, RowsFuture, Statement};
use crate::{Database, Error, Params, Result, Rows, RowsFuture, Statement, Transaction, TransactionBehavior};

use libsql_sys::ffi;
use std::ffi::c_int;
Expand Down Expand Up @@ -103,6 +103,19 @@ impl Connection {
}
}

/// Begin a new transaction in DEFERRED mode, which is the default.
pub fn transaction(&self) -> Result<Transaction> {
self.transaction_with_behavior(TransactionBehavior::Deferred)
}

/// Begin a new transaction in the given mode.
pub fn transaction_with_behavior(
&self,
tx_behavior: TransactionBehavior,
) -> Result<Transaction> {
Transaction::begin(self.clone(), tx_behavior)
}

pub fn is_autocommit(&self) -> bool {
unsafe { ffi::sqlite3_get_autocommit(self.raw) != 0 }
}
Expand Down
2 changes: 2 additions & 0 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub mod errors;
pub mod params;
pub mod rows;
pub mod statement;
pub mod transaction;

pub type Result<T> = std::result::Result<T, errors::Error>;

Expand All @@ -61,6 +62,7 @@ pub use database::Opts;
pub use errors::Error;
pub use params::Params;
pub use params::{Value, ValueRef};
pub use transaction::{Transaction, TransactionBehavior};
pub use rows::Row;
pub use rows::Rows;
pub use rows::RowsFuture;
Expand Down
93 changes: 93 additions & 0 deletions crates/core/src/transaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use crate::{Connection, Result};
use std::ops::Deref;

pub enum TransactionBehavior {
Deferred,
Immediate,
Exclusive,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DropBehavior {
Rollback,
Commit,
Ignore,
Panic,
}

pub struct Transaction {
conn: Connection,
drop_behavior: DropBehavior,
}

impl Drop for Transaction {
fn drop(&mut self) {
if self.conn.is_autocommit() {
return;
}
match self.drop_behavior {
DropBehavior::Rollback => {
self.do_rollback().unwrap();
}
DropBehavior::Commit => {
self.do_commit().unwrap();
}
DropBehavior::Ignore => {}
DropBehavior::Panic => {
if !thread::panicking() {
panic!("Transaction dropped without being committed or rolled back");
}
}
}
}
}

impl Transaction {
pub fn drop_behavior(&self) -> DropBehavior {
self.drop_behavior
}

pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
self.drop_behavior = drop_behavior
}

/// Begin a new transaction in the given mode.
pub(crate) fn begin(conn: Connection, tx_behavior: TransactionBehavior) -> Result<Self> {
let begin_stmt = match tx_behavior {
TransactionBehavior::Deferred => "BEGIN DEFERRED",
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
};
let _ = conn.execute(begin_stmt, ())?;
Ok(Self { conn, drop_behavior: DropBehavior::Rollback })
}

/// Commit the transaction.
pub fn commit(self) -> Result<()> {
self.do_commit()
}

fn do_commit(&self) -> Result<()> {
let _ = self.conn.execute("COMMIT", ())?;
Ok(())
}

/// Rollback the transaction.
pub fn rollback(self) -> Result<()> {
self.do_rollback()
}

fn do_rollback(&self) -> Result<()> {
let _ = self.conn.execute("ROLLBACK", ())?;
Ok(())
}
}

impl Deref for Transaction {
type Target = Connection;

#[inline]
fn deref(&self) -> &Connection {
&self.conn
}
}
16 changes: 16 additions & 0 deletions crates/core/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,19 @@ fn blob() {
let out = row.get::<Vec<u8>>(1).unwrap();
assert_eq!(&out, &bytes);
}

#[test]
fn transaction() {
let conn = setup();
conn.execute("INSERT INTO users (id, name) VALUES (2, 'Alice')", ())
.unwrap();
let tx = conn.transaction().unwrap();
tx.execute("INSERT INTO users (id, name) VALUES (3, 'Bob')", ())
.unwrap();
tx.rollback().unwrap();
let rows = conn.query("SELECT * FROM users", ()).unwrap().unwrap();
let row = rows.next().unwrap().unwrap();
assert_eq!(row.get::<i32>(0).unwrap(), 2);
assert_eq!(row.get::<&str>(1).unwrap(), "Alice");
assert!(rows.next().unwrap().is_none());
}

0 comments on commit 4872c95

Please sign in to comment.