Expand description
§DQL Transactions
let result: Result<_, DittoError> = ditto
.store()
.transaction(async |txn| {
let _qr1 = txn.execute("SELECT * FROM foo").await.unwrap();
let _qr2 = txn.execute("SELECT * FROM bar").await.unwrap();
Ok(TransactionCompletionAction::Commit)
})
.await;
assert_eq!(result.unwrap(), TransactionCompletionAction::Commit);Note that it is an error to try to store the transaction outside the scope of the callback:
let mut stowaway = None;
let _result: Result<_, DittoError> = ditto
.store()
.transaction(async |txn| {
stowaway = Some(txn);
Ok(TransactionCompletionAction::Commit)
})
.await;
§Return type
Both .transaction() and
.transaction_with_options() allow the caller to choose both the
success and error type of the returned Result. For advice on choosing an error type, see
the “errors” section below.
The return type may be any type that implements Any, which is every
'static type. If the type is
`TransactionCompletionAction’, then it is used to determine
the behaviour of the transaction:
- if the value is
TransactionCompletionAction::Commit, the transaction is committed - if the value is
TransactionCompletionAction::Rollback, the transaction is rolled back - if the value is any other type, the transaction is implicitly committed
- if the closure returns an error, it is implicitly rolled back
If you want to use TransactionCompletionAction, be careful to make sure that you are
actually returning a TransactionCompletionAction, and not a type that looks similar. In
particular, a [&'static TransactionCompletionAction] will be treated like a “normal value”,
which could lead to the following surprising scenario:
The type check is performed by casting converting the returned Ok value to a &dyn Any and then calling .downcast_ref() on it. Consider reading the docs for
the Any, as well as the docs for the core::any module to understand
more about how this type-check works.
// WARNING - this looks like it should roll back but actually commits
let action = ditto
.store()
.transaction(async |txn| {
// do some stuff
// something bad has happened, we should rollback here
if something_went_wrong() {
// WARNING - this does NOT roll back
return Ok::<_, DittoError>(&TransactionCompletionAction::Rollback);
}
Ok::<_, DittoError>(&TransactionCompletionAction::Commit)
})
.await
.unwrap();§Errors
Both transaction and transaction_with_options are generic over their error type. This
is because transactions can return DittoError, but you may also want to use a custom error
type. For example:
#[derive(Debug, thiserror::Error)]
enum MyError {
#[error("{0}")]
Ditto(#[from] DittoError),
#[error("insufficient funds")]
InsufficientFunds,
}
ditto.store().transaction::<_, MyError>(async |txn| {
let result = txn.execute((
"SELECT * FROM accounts WHERE _id = :id",
json!({"id": "alice_id"}),
)).await?; // DittoError potentially returned here
let first = result.get_item(0).unwrap().value();
let Value::Integer(balance) = first.get("balance").unwrap() else {
panic!();
};
if *balance < 10 {
return Err(MyError::InsufficientFunds);
}
// ...
Ok(TransactionCompletionAction::Commit)
}).await.unwrap();The error type has to implement From<DittoError>. If you don’t have your own custom error
type, just use DittoError.
§Limitations
Because Rust’s ? operator will try to convert errors using Into, it can sometimes
struggle to infer the error type. In general, if you are using ?, you will likely run into
inference errors unless you specify the return type. For example:
- on the call to
transaction:ditto.store().transaction::<_, MyError>(/* ... */) - on an
OkorErrreturn value:Ok::<_, MyError>(/* ... */) - on the closure itself:
async |txn| -> Result<_, MyError> { /* ... */ }
See also:
Structs§
- Options for customizing a transaction. Used with
Store::transaction_with_options. - Represents a transaction in the Ditto store.
- Encapsulates information about a transaction.
Enums§
- Represents an action that completes a transaction, by either committing it or rolling it back.