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