Transactions
Database::transaction runs a closure in a transaction. It takes an async closure, which receives the transaction as tx. The closure returns a Result, and the transaction ends with it:
use orbit_orm::{Database, Error, Model};
let id = db
.transaction(async |tx| {
let id = Post::query()
.insert(&mut *tx, &[("title", "Hello".into())])
.await?;
Post::query()
.where_eq("id", id)
.for_update()
.first_or_fail(&mut *tx)
.await?;
Ok::<_, Error>(id)
})
.await?;
Queries in the transaction
A query that must run inside the transaction takes it as &mut *tx. A query given &db runs on the pool, outside the transaction. tx.connection() gives the transaction's connection, for SQL that Orbit does not wrap.
Commit and rollback
- When the closure returns
Ok, the transaction commits, and the value is returned. - When the closure returns
Err, the transaction rolls back, and that error is returned. A rollback that fails does not replace the error. - When the closure panics, nothing is committed. The commit runs only after the closure returns, so the transaction is rolled back.
Errors in the closure
The closure's error type must implement From<Error>, so Orbit's errors convert with ?. Error itself is the simplest choice. A unique violation is reported with the name of the index that broke. The name is None when the server does not report it:
use orbit_orm::{Database, Error, Model};
match db
.transaction(async |tx| {
let id = Post::query()
.insert(&mut *tx, &[("title", "Hello".into())])
.await?;
Ok::<_, Error>(id)
})
.await
{
Ok(id) => println!("inserted {id}"),
Err(Error::UniqueViolation { index }) => {
println!("unique index {index:?} was violated")
}
Err(other) => return Err(other),
}
The index name comes from the server's message. MySQL 8.0.19 and later prefix the table name, and Orbit removes that prefix. For a unique index called posts_title_unique, the index is Some("posts_title_unique"). See Errors for the other variants.
Deadlocks
Orbit does not retry a transaction. When the database rolls the transaction back to break a deadlock, the call returns Error::Deadlock, and the caller decides whether to run the transaction again.
Row locks
Inside a transaction, for_update holds its lock until the transaction ends. Outside one, the lock lasts only for the statement. See Queries.
Orbit