dittolive_ditto/store/transactions/mod.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
//! # DQL Transactions
//!
//! ```rust,no_run
//! # use dittolive_ditto::prelude::*;
//! # #[tokio::main]
//! # async fn main() {
//! # let ditto: Ditto = todo!();
//! 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:
//! ```rust,compile_fail
//! # use dittolive_ditto::prelude::*;
//! # #[tokio::main]
//! # async fn main() {
//! # let ditto: Ditto = todo!();
//! let mut stowaway = None;
//!
//! let _result: Result<_, DittoError> = ditto
//! .store()
//! .transaction(async |txn| {
//! stowaway = Some(txn);
//! Ok(TransactionCompletionAction::Commit)
//! })
//! .await;
//!
//! # }
//! ```
//! # Return type
//!
//! Both [`.transaction()`][`transaction`] and
//! [`.transaction_with_options()`][`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`][core::any::Any], which is every
//! `'static` type. If the type is
//! [`TransactionCompletionAction'][api::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`][core::any::Any] and then calling `.downcast_ref()` on it. Consider reading the docs for
//! the [`Any`][core::any::Any], as well as the docs for the [`core::any`] module to understand
//! more about how this type-check works.
//! ```rust,no_run
//! # use dittolive_ditto::prelude::*;
//! # #[tokio::main]
//! # async fn main() {
//! # let ditto: Ditto = todo!();
//! # fn something_went_wrong() -> bool { true }
//! // 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:
//! ```rust,no_run
//! # use dittolive_ditto::prelude::*;
//! # use serde_json::json;
//! # use serde_cbor::Value;
//! # #[tokio::main]
//! # async fn main() {
//! # let ditto: Ditto = todo!();
//! #[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:
//! - [`ditto.store().transaction()`][`transaction`]
//! - [`ditto.store().transaction_with_options()`][`transaction_with_options`]
//!
//! [`transaction`]: crate::prelude::Store::transaction
//! [`transaction_with_options`]: crate::prelude::Store::transaction_with_options
//! [`DittoError`]: crate::prelude::DittoError
// Separate modules for clean separation between API and implementations
//
// Some function signatures are duplicated, but it's worth it IMO for the readability
mod api;
mod impls;
pub use api::{
CreateTransactionOptions, Transaction, TransactionCompletionAction, TransactionInfo,
};