dittolive_ditto::store

Module transactions

Source
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 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 Ok or Err return value: Ok::<_, MyError>(/* ... */)
  • on the closure itself: async |txn| -> Result<_, MyError> { /* ... */ }

See also:

Structs§

Enums§