Transactions
A database transaction groups a series of operations into a single, atomic unit of work: either all of them are applied to the database, or none of them are. This is essential whenever a logical change spans more than one statement and you need to keep the database consistent even if something fails halfway through - the classic example being transferring money between two accounts, where the debit and the credit must either both happen or both be rolled back.
Cot exposes transactions through the Database::begin method, which returns a Transaction. The Transaction implements the same DatabaseBackend trait as Database, so every method you already use - insert, save, the query! macro, and so on - works inside a transaction by simply passing the transaction in place of the database connection.
Starting a transaction
Call begin to start a transaction, run your operations against it, and then finalize it with commit. Nothing you do inside the transaction becomes visible to other connections until you commit.
use ;
async
Committing and rolling back
A transaction ends in one of two ways:
commitpersists every change made inside the transaction.rollbackdiscards every change made inside the transaction, leaving the database exactly as it was beforebeginwas called.
Both methods consume the Transaction, so it can no longer be used afterwards. If a Transaction is dropped without being committed - for example because an error caused an early return - it is rolled back automatically, so a failed operation never leaves a half-applied change behind.
use ;
async
Running queries in a transaction
Because Transaction implements DatabaseBackend, you can pass a mutable reference to it anywhere a database backend is expected, including the query! macro and the Query interface. Crucially, a query run through the transaction sees the transaction’s own in-progress view of the data - including changes you made earlier in the same transaction that haven’t been committed yet. This lets you write a row and then immediately query it (or re-check an invariant across the table) before deciding whether to commit.
use ;
async
Nested transactions (savepoints)
Transactions can be nested. Calling begin on a Transaction starts a nested transaction backed by a database savepoint. Committing the nested transaction releases the savepoint into the enclosing transaction, while rolling it back undoes only the work done since the savepoint was created - the outer transaction keeps going and can still be committed or rolled back independently.
This is useful when part of a larger operation is allowed to fail without aborting the whole thing.
use ;
async
Raw SQL in transactions
Just like Database, a Transaction provides an escape hatch for running raw SQL when the query! macro and the Query interface aren’t enough. The raw, raw_with, raw_as, and raw_as_with methods behave exactly like their Database counterparts, except that the statements run within the transaction and are only persisted once it is committed.
Warning: These methods execute the given SQL string as-is, without any sanitization. Never build the query string by interpolating untrusted input directly into it. Use the parameterized
raw_with/raw_as_withvariants whenever the query depends on external data.
use ;
async