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};