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 pocketdbBasic 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 snapshotTransaction 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:
begin_transaction()writes a WAL begin record- Operations are buffered in memory
commit()executes all operations and writes a WAL commit record- On crash recovery, incomplete transactions are automatically rolled back
See Crash Recovery for details on WAL behavior.