dittolive_ditto/store/transactions/
mod.rs

1//! # DQL Transactions
2//!
3//! ```rust
4//! # use dittolive_ditto::prelude::*;
5//! # #[tokio::main]
6//! # async fn main() {
7//! # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
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`], 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 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
66//! # use dittolive_ditto::prelude::*;
67//! # #[tokio::main]
68//! # async fn main() {
69//! # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
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
95//! # use dittolive_ditto::prelude::*;
96//! # use serde_json::json;
97//! # use serde_cbor::Value;
98//! # #[tokio::main]
99//! # async fn main() {
100//! # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
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//!     # txn.execute(("INSERT INTO accounts DOCUMENTS (:doc)", json!({"doc": {"_id": "alice_id", "balance": 100}}))).await.unwrap();
112//!     let result = txn.execute((
113//!         "SELECT * FROM accounts WHERE _id = :id",
114//!         json!({"id": "alice_id"}),
115//!     )).await?;  // DittoError potentially returned here
116//!
117//!     let first = result.get_item(0).unwrap().value();
118//!     let Value::Integer(balance) = first.get("balance").unwrap() else {
119//!         panic!();
120//!     };
121//!
122//!     if *balance < 10 {
123//!         return Err(MyError::InsufficientFunds);
124//!     }
125//!
126//!     // ...
127//!
128//!     Ok(TransactionCompletionAction::Commit)
129//! }).await.unwrap();
130//! # }
131//! ```
132//!
133//! The error type has to implement [`From<DittoError>`]. If you don't have your own custom error
134//! type, just use [`DittoError`].
135//!
136//! ### Limitations
137//!
138//! Because Rust's `?` operator will try to convert errors using [`Into`], it can sometimes
139//! struggle to infer the error type. In general, if you are using `?`, you will likely run into
140//! inference errors unless you specify the return type. For example:
141//! - on the call to `transaction`: `ditto.store().transaction::<_, MyError>(/* ... */)`
142//! - on an `Ok` or `Err` return value: `Ok::<_, MyError>(/* ... */)`
143//! - on the closure itself: `async |txn| -> Result<_, MyError> { /* ... */ }`
144//!
145//! See also:
146//! - [`ditto.store().transaction()`][`transaction`]
147//! - [`ditto.store().transaction_with_options()`][`transaction_with_options`]
148//!
149//! [`transaction`]: crate::prelude::Store::transaction
150//! [`transaction_with_options`]: crate::prelude::Store::transaction_with_options
151//! [`DittoError`]: crate::prelude::DittoError
152
153// Separate modules for clean separation between API and implementations
154//
155// Some function signatures are duplicated, but it's worth it IMO for the readability
156mod api;
157mod impls;
158
159pub use api::{
160    CreateTransactionOptions, Transaction, TransactionCompletionAction, TransactionInfo,
161};