Skip to content

Transaction Examples ​

This guide demonstrates ACID transaction patterns in PocketDB.

Setup ​

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

fn main() -> pocketdb::Result<()> {
    let db = Database::open("transactions_example.pdb")?;
    // ... examples ...

    Ok(())
}

Basic Transaction ​

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

// Perform operations within the transaction
{
    let mut users = tx.collection("users")?;
    users.insert(doc! { "name" => "Alice", "balance" => 1000 })?;
    users.insert(doc! { "name" => "Bob", "balance" => 500 })?;
}

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

Money Transfer ​

Transfer money between accounts atomically:

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

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

    // Credit 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,
            "timestamp" => std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_millis() as u64
        })?;
    }

    tx.commit()?;
    Ok(())
}

// Usage
transfer(&db, "Alice", "Bob", 200)?;

Rollback on Error ​

rust
let result = (|| -> pocketdb::Result<()> {
    let mut tx = db.begin_transaction()?;

    {
        let mut orders = tx.collection("orders")?;
        orders.insert(doc! { "item" => "Widget", "qty" => 5 })?;
    }

    // Simulate an error
    let _forced_error = Err::<(), _>(pocketdb::PocketError::Other("Simulated failure".to_string()));

    // This code is unreachable in this example
    tx.commit()?;
    Ok(())
})();

if let Err(e) = result {
    eprintln!("Transaction failed: {}", e);
    // Changes are automatically discarded on rollback
}

Multi-Collection Transaction ​

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

// Create order
{
    let mut orders = tx.collection("orders")?;
    orders.insert(doc! {
        "customer_id" => "cust_123",
        "items" => ["Widget", "Gadget"],
        "total" => 150
    })?;
}

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

// Update customer stats
{
    let mut customers = tx.collection("customers")?;
    customers.update_one(
        &filter! { "_id" => == PocketValue::String("cust_123".to_string()) },
        UpdateExpression::new()
            .inc("order_count", PocketValue::Int(1))
            .inc("total_spent", PocketValue::Int(150)),
    )?;
}

tx.commit()?;

Transaction with Query ​

Read data within a transaction:

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

// Read current balance
let balance = {
    let accounts = tx.collection("accounts")?;
    let account = accounts.find_one(&filter! { "name" => == "Alice" });
    account
        .and_then(|d| d.get("balance"))
        .and_then(|v| v.as_i64())
        .unwrap_or(0)
};

println!("Alice's balance: {}", balance);

// Conditionally update
if balance >= 100 {
    let mut accounts = tx.collection("accounts")?;
    accounts.update_one(
        &filter! { "name" => == "Alice" },
        UpdateExpression::new().inc("balance", PocketValue::Int(-100)),
    )?;
    tx.commit()?;
    println!("Deducted $100 from Alice's account");
} else {
    tx.rollback()?;
    println!("Insufficient funds");
}

Batch Processing ​

rust
fn process_batch(db: &Database, items: Vec<(&str, i64)>) -> pocketdb::Result<()> {
    let mut tx = db.begin_transaction()?;

    for (name, amount) in items {
        let mut accounts = tx.collection("accounts")?;
        accounts.update_one(
            &filter! { "name" => == name },
            UpdateExpression::new().inc("balance", PocketValue::Int(amount)),
        )?;
    }

    tx.commit()?;
    Ok(())
}

let batch = vec![
    ("Alice", 100),
    ("Bob", -50),
    ("Charlie", 200),
];

process_batch(&db, batch)?;

Error Handling Patterns ​

Check Commit Result ​

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

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

match tx.commit() {
    Ok(()) => println!("Transaction committed successfully"),
    Err(e) => eprintln!("Commit failed: {}", e),
}

Conditional Operations ​

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

{
    let mut accounts = tx.collection("accounts")?;

    // Check balance before transfer
    let balance = accounts.find_one(&filter! { "name" => == "Alice" })
        .and_then(|d| d.get("balance"))
        .and_then(|v| v.as_i64())
        .unwrap_or(0);

    if balance < 100 {
        success = false;
    } else {
        accounts.update_one(
            &filter! { "name" => == "Alice" },
            UpdateExpression::new().inc("balance", PocketValue::Int(-100)),
        )?;
    }
}

if success {
    tx.commit()?;
} else {
    tx.rollback()?;
    println!("Insufficient funds — transaction rolled back");
}

Released under the MIT License. Built by Muhammad Fiaz.