dittolive_ditto/store/transactions/
mod.rs

1//! # DQL Transactions
2//!
3//! ```rust,no_run
4//! # use dittolive_ditto::prelude::*;
5//! # #[tokio::main]
6//! # async fn main() {
7//! # let ditto: Ditto = todo!();
8//! let result: Result<_, DittoError> = ditto
9//!     .store()
10//!     .transaction(async |txn| {
11//!         let _qr1 = txn.execute("SELECT * FROM foo").await.unwrap();
12//!         let _qr2 = txn.execute("SELECT * FROM bar").await.unwrap();
13//!
14//!         Ok(TransactionCompletionAction::Commit)
15//!     })
16//!     .await;
17//!
18//! assert_eq!(result.unwrap(), TransactionCompletionAction::Commit);
19//! # }
20//! ```
21//!
22//! Note that it is an error to try to store the transaction outside the scope of the callback:
23//! ```rust,compile_fail
24//! # use dittolive_ditto::prelude::*;
25//! # #[tokio::main]
26//! # async fn main() {
27//! # let ditto: Ditto = todo!();
28//! let mut stowaway = None;
29//!
30//! let _result: Result<_, DittoError> = ditto
31//!     .store()
32//!     .transaction(async |txn| {
33//!         stowaway = Some(txn);
34//!         Ok(TransactionCompletionAction::Commit)
35//!     })
36//!     .await;
37//!
38//! # }
39//! ```
40//! # Return type
41//!
42//! Both [`.transaction()`][`transaction`] and
43//! [`.transaction_with_options()`][`transaction_with_options`] allow the caller to choose both the
44//! success and error type of the returned [`Result`]. For advice on choosing an error type, see
45//! the "errors" section below.
46//!
47//! The return type may be any type that implements [`Any`][core::any::Any], which is every
48//! `'static` type. If the type is
49//! [`TransactionCompletionAction'][api::TransactionCompletionAction], then it is used to determine
50//! the behaviour of the transaction:
51//! - if the value is [`TransactionCompletionAction::Commit`], the transaction is committed
52//! - if the value is [`TransactionCompletionAction::Rollback`], the transaction is rolled back
53//! - if the value is any other type, the transaction is implicitly committed
54//! - if the closure returns an error, it is implicitly rolled back
55//!
56//! If you want to use [`TransactionCompletionAction`], be careful to make sure that you are
57//! actually returning a [`TransactionCompletionAction`], and not a type that looks similar. In
58//! particular, a [`&'static TransactionCompletionAction`]  will be treated like a "normal value",
59//! which could lead to the following surprising scenario:
60//!
61//! The type check is performed by casting converting the returned `Ok` value to a [`&dyn
62//! Any`][core::any::Any] and then calling `.downcast_ref()` on it. Consider reading the docs for
63//! the [`Any`][core::any::Any], as well as the docs for the [`core::any`] module to understand
64//! more about how this type-check works.
65//! ```rust,no_run
66//! # use dittolive_ditto::prelude::*;
67//! # #[tokio::main]
68//! # async fn main() {
69//! # let ditto: Ditto = todo!();
70//! # fn something_went_wrong() -> bool { true }
71//! // WARNING - this looks like it should roll back but actually commits
72//! let action = ditto
73//!     .store()
74//!     .transaction(async |txn| {
75//!         // do some stuff
76//!
77//!         // something bad has happened, we should rollback here
78//!         if something_went_wrong() {
79//!             // WARNING - this does NOT roll back
80//!             return Ok::<_, DittoError>(&TransactionCompletionAction::Rollback);
81//!         }
82//!
83//!         Ok::<_, DittoError>(&TransactionCompletionAction::Commit)
84//!     })
85//!     .await
86//!     .unwrap();
87//! # }
88//! ```
89//! # Errors
90//!
91//! Both [`transaction`] and [`transaction_with_options`] are generic over their error type. This
92//! is because transactions can return [`DittoError`], but you may also want to use a custom error
93//! type. For example:
94//! ```rust,no_run
95//! # use dittolive_ditto::prelude::*;
96//! # use serde_json::json;
97//! # use serde_cbor::Value;
98//! # #[tokio::main]
99//! # async fn main() {
100//! # let ditto: Ditto = todo!();
101//! #[derive(Debug, thiserror::Error)]
102//! enum MyError {
103//!     #[error("{0}")]
104//!     Ditto(#[from] DittoError),
105//!
106//!     #[error("insufficient funds")]
107//!     InsufficientFunds,
108//! }
109//!
110//! ditto.store().transaction::<_, MyError>(async |txn| {
111//!     let result = txn.execute((
112//!         "SELECT * FROM accounts WHERE _id = :id",
113//!         json!({"id": "alice_id"}),
114//!     )).await?;  // DittoError potentially returned here
115//!
116//!     let first = result.get_item(0).unwrap().value();
117//!     let Value::Integer(balance) = first.get("balance").unwrap() else {
118//!         panic!();
119//!     };
120//!
121//!     if *balance < 10 {
122//!         return Err(MyError::InsufficientFunds);
123//!     }
124//!
125//!     // ...
126//!
127//!     Ok(TransactionCompletionAction::Commit)
128//! }).await.unwrap();
129//! # }
130//! ```
131//!
132//! The error type has to implement [`From<DittoError>`]. If you don't have your own custom error
133//! type, just use [`DittoError`].
134//!
135//! ### Limitations
136//!
137//! Because Rust's `?` operator will try to convert errors using [`Into`], it can sometimes
138//! struggle to infer the error type. In general, if you are using `?`, you will likely run into
139//! inference errors unless you specify the return type. For example:
140//! - on the call to `transaction`: `ditto.store().transaction::<_, MyError>(/* ... */)`
141//! - on an `Ok` or `Err` return value: `Ok::<_, MyError>(/* ... */)`
142//! - on the closure itself: `async |txn| -> Result<_, MyError> { /* ... */ }`
143//!
144//! See also:
145//! - [`ditto.store().transaction()`][`transaction`]
146//! - [`ditto.store().transaction_with_options()`][`transaction_with_options`]
147//!
148//! [`transaction`]: crate::prelude::Store::transaction
149//! [`transaction_with_options`]: crate::prelude::Store::transaction_with_options
150//! [`DittoError`]: crate::prelude::DittoError
151
152// Separate modules for clean separation between API and implementations
153//
154// Some function signatures are duplicated, but it's worth it IMO for the readability
155mod api;
156mod impls;
157
158pub use api::{
159    CreateTransactionOptions, Transaction, TransactionCompletionAction, TransactionInfo,
160};