Skip to content

ACID Transactions ​

PocketDB supports multi-collection, multi-document ACID transactions. Operations are buffered in-memory and applied atomically on commit.

Installation ​

Add PocketDB to your Cargo.toml:

toml
[dependencies]
pocketdb = "0.1"

Or via the command line:

bash
cargo add pocketdb

Basic Usage ​

Begin a Transaction ​

rust
let mut tx = db.begin_transaction()?;

Get a Collection Within the Transaction ​

rust
let mut users = tx.collection("users")?;
let mut orders = tx.collection("orders")?;

// Perform operations — they're buffered until commit
users.insert(doc! { "name" => "Alice" })?;
orders.insert(doc! { "item" => "Widget", "qty" => 2 })?;

Commit ​

All operations are applied atomically:

rust
tx.commit()?;

Rollback ​

Discard all buffered changes:

rust
tx.rollback()?;

Complete Transaction Example ​

rust
use pocketdb::{Database, doc, filter, PocketValue};
use pocketdb::query::UpdateExpression;

fn transfer_funds(
    db: &Database,
    from: &str,
    to: &str,
    amount: i64,
) -> pocketdb::Result<()> {
    let mut tx = db.begin_transaction()?;

    // Debit the sender
    {
        let mut accounts = tx.collection("accounts")?;
        accounts.update_one(
            &filter! { "name" => == from },
            UpdateExpression::new().inc("balance", PocketValue::Int(-amount)),
        )?;
    }

    // Credit the receiver
    {
        let mut accounts = tx.collection("accounts")?;
        accounts.update_one(
            &filter! { "name" => == to },
            UpdateExpression::new().inc("balance", PocketValue::Int(amount)),
        )?;
    }

    // Log the transfer
    {
        let mut logs = tx.collection("transfer_logs")?;
        logs.insert(doc! {
            "from" => from,
            "to" => to,
            "amount" => amount
        })?;
    }

    // Commit all changes atomically
    tx.commit()?;
    Ok(())
}

Multi-Collection Operations ​

Transactions can span multiple collections:

rust
let mut tx = db.begin_transaction()?;

// Order processing
{
    let mut orders = tx.collection("orders")?;
    orders.insert(doc! {
        "customer" => "Alice",
        "item" => "Laptop",
        "qty" => 1
    })?;
}

// Update inventory
{
    let mut inventory = tx.collection("inventory")?;
    inventory.update_one(
        &filter! { "item" => == "Laptop" },
        UpdateExpression::new().inc("stock", PocketValue::Int(-1)),
    )?;
}

tx.commit()?;

Error Handling ​

If any operation fails, the transaction can be rolled back:

rust
let mut tx = db.begin_transaction()?;

{
    let mut col = tx.collection("users")?;
    col.insert(doc! { "name" => "Alice" })?;
}

// If an error occurs, rollback instead of commit
if let Err(e) = tx.commit() {
    eprintln!("Transaction failed: {}", e);
    // Changes are discarded
}

Snapshot Isolation ​

PocketDB uses snapshot isolation for transactions:

  • Read Stability: Once a transaction reads data, it sees a consistent snapshot
  • Write Atomicity: All writes in a transaction are applied together or not at all
  • No Dirty Reads: Other transactions cannot see uncommitted changes
  • No Phantom Reads: Each transaction operates on a consistent snapshot of data
rust
// Transaction 1 reads Alice's balance
let mut tx1 = db.begin_transaction()?;
{
    let accounts = tx1.collection("accounts")?;
    let alice = accounts.find_one(&filter! { "name" => == "Alice" });
    // Alice's balance is guaranteed to stay consistent within this transaction
}

// Transaction 2 modifies Alice's balance
let mut tx2 = db.begin_transaction()?;
{
    let mut accounts = tx2.collection("accounts")?;
    accounts.update_one(
        &filter! { "name" => == "Alice" },
        UpdateExpression::new().inc("balance", PocketValue::Int(100)),
    )?;
}
tx2.commit()?;

// tx1 still sees the original snapshot

Transaction Lifetime ​

A transaction must be either committed or rolled back:

rust
let mut tx = db.begin_transaction()?;

// Do work...
{
    let mut col = tx.collection("users")?;
    col.insert(doc! { "name" => "Bob" })?;
}

// Must call commit() or rollback()
// Dropping without commit/rollback discards changes
tx.commit()?;

WAL Integration ​

Transactions are recorded in the Write-Ahead Log for durability:

  1. begin_transaction() writes a WAL begin record
  2. Operations are buffered in memory
  3. commit() executes all operations and writes a WAL commit record
  4. On crash recovery, incomplete transactions are automatically rolled back

See Crash Recovery for details on WAL behavior.

Released under the MIT License. Built by Muhammad Fiaz.