rorm_db/transaction/
mod.rs1use std::error::Error as StdError;
4use std::fmt;
5use std::future::Future;
6use std::ops::{Deref, DerefMut};
7
8use rorm_sql::DBImpl;
9use tracing::debug;
10
11use crate::internal::any::AnyTransaction;
12pub use crate::transaction::hook::TransactionHook;
13use crate::transaction::hook_closure::{ClosureHook, OnRollback, PostCommit, PreCommit};
14use crate::transaction::hook_storage::HookStorage;
15use crate::Error;
16
17mod hook;
18mod hook_closure;
19mod hook_storage;
20
21#[must_use = "A transaction needs to be committed."]
27pub struct Transaction {
28 pub(crate) sqlx: AnyTransaction,
29 hooks: Option<HookStorage>,
30}
31
32impl Transaction {
33 pub(crate) fn new(sqlx: AnyTransaction) -> Self {
34 Self { sqlx, hooks: None }
35 }
36
37 pub fn dialect(&self) -> DBImpl {
39 match self.sqlx {
40 #[cfg(feature = "postgres")]
41 AnyTransaction::Postgres(_) => DBImpl::Postgres,
42 #[cfg(feature = "sqlite")]
43 AnyTransaction::Sqlite(_) => DBImpl::SQLite,
44 }
45 }
46
47 pub async fn commit(mut self) -> Result<(), TransactionError> {
49 let mut hooks = self.hooks.take();
50
51 if let Some(hooks) = hooks.as_mut() {
52 hooks.pre_commit(&mut self).await?;
53
54 if let Some(invalid_hooks) = self.hooks.as_mut() {
55 debug!("Some transaction hook added additional hooks during pre-commit. This is not supported and will be ignored.");
56
57 invalid_hooks.clear();
59 }
60 }
61
62 let result = self.sqlx.commit().await;
63
64 if let Some(hooks) = hooks.as_mut() {
65 if result.is_ok() {
66 hooks.post_commit();
67
68 hooks.clear();
70 }
71 }
72
73 result
74 .map_err(Error::SqlxError)
75 .map_err(TransactionError::Database)
76 }
77
78 pub async fn rollback(self) -> Result<(), Error> {
80 self.sqlx.rollback().await.map_err(Error::SqlxError)
81 }
82}
83
84impl Drop for HookStorage {
88 fn drop(&mut self) {
89 self.on_rollback();
91 }
92}
93
94impl Transaction {
95 pub fn hooks(&mut self) -> SimpleHooksApi<'_> {
99 SimpleHooksApi(self.hooks.get_or_insert_default())
100 }
101
102 pub fn adv_hooks(&mut self) -> AdvancedHooksApi<'_> {
106 AdvancedHooksApi(self.hooks.get_or_insert_default())
107 }
108}
109
110pub struct SimpleHooksApi<'a>(&'a mut HookStorage);
114impl SimpleHooksApi<'_> {
115 pub fn pre_commit<F>(&mut self, hook: impl FnOnce() -> F + Send + 'static) -> &mut Self
119 where
120 F: Future<Output = Result<(), TransactionError>> + Send,
121 {
122 self.0
123 .get_or_insert()
124 .push(ClosureHook::new(hook, PreCommit));
125 self
126 }
127
128 pub fn post_commit(&mut self, hook: impl FnOnce() + Send + 'static) -> &mut Self {
130 self.0
131 .get_or_insert()
132 .push(ClosureHook::new(hook, PostCommit));
133 self
134 }
135
136 pub fn on_rollback(&mut self, hook: impl FnOnce() + Send + 'static) -> &mut Self {
140 self.0
141 .get_or_insert()
142 .push(ClosureHook::new(hook, OnRollback));
143 self
144 }
145}
146
147pub struct AdvancedHooksApi<'a>(&'a mut HookStorage);
161impl AdvancedHooksApi<'_> {
162 pub fn push<T: TransactionHook>(&mut self, hook: T) {
164 self.get_all().push(hook);
165 }
166
167 pub fn get_or_insert_default<T: TransactionHook + Default>(&mut self) -> &mut T {
171 self.get_or_insert_with(T::default)
172 }
173
174 pub fn get_or_insert_with<T: TransactionHook>(&mut self, init: impl FnOnce() -> T) -> &mut T {
178 let vec = self.get_all();
179 if vec.is_empty() {
180 vec.push(init());
181 }
182 &mut vec[0]
183 }
184
185 pub fn get_all<T: TransactionHook>(&mut self) -> &mut Vec<T> {
187 self.0.get_or_insert()
188 }
189}
190
191#[derive(Debug)]
193pub enum TransactionError {
194 Database(Error),
196
197 Hook(HookError),
199}
200pub type HookError = Box<dyn StdError + Send + Sync>;
202
203impl From<Error> for TransactionError {
204 fn from(value: Error) -> Self {
205 Self::Database(value)
206 }
207}
208impl From<HookError> for TransactionError {
209 fn from(value: HookError) -> Self {
210 Self::Hook(value)
211 }
212}
213impl fmt::Display for TransactionError {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 match self {
216 TransactionError::Database(x) => fmt::Display::fmt(x, f),
217 TransactionError::Hook(x) => fmt::Display::fmt(x, f),
218 }
219 }
220}
221impl StdError for TransactionError {
222 fn source(&self) -> Option<&(dyn StdError + 'static)> {
223 match self {
224 TransactionError::Database(x) => Some(x),
225 TransactionError::Hook(x) => Some(x.as_ref()),
226 }
227 }
228}
229
230#[deprecated(note = "Use `MaybeOwnedTransaction` instead")]
232pub type TransactionGuard<'a> = MaybeOwnedTransaction<'a>;
233
234#[must_use = "The owned variant needs to be committed."]
236pub enum MaybeOwnedTransaction<'a> {
237 Owned(Transaction),
239
240 Borrowed(&'a mut Transaction),
242}
243
244impl MaybeOwnedTransaction<'_> {
245 pub fn as_ref(&self) -> &Transaction {
247 &*self
248 }
249
250 pub fn as_mut(&mut self) -> &mut Transaction {
252 &mut *self
253 }
254
255 #[deprecated(note = "Use deref instead")]
257 pub fn get_transaction(&mut self) -> &mut Transaction {
258 &mut *self
259 }
260
261 pub async fn commit_if_owned(self) -> Result<(), TransactionError> {
263 if let Self::Owned(tr) = self {
264 tr.commit().await
265 } else {
266 Ok(())
267 }
268 }
269
270 pub async fn rollback_if_owned(self) -> Result<(), Error> {
272 if let Self::Owned(tr) = self {
273 tr.rollback().await
274 } else {
275 Ok(())
276 }
277 }
278
279 #[deprecated(note = "Use `commit_if_owned` instead")]
281 pub async fn commit(self) -> Result<(), TransactionError> {
282 self.commit_if_owned().await
283 }
284}
285
286impl Deref for MaybeOwnedTransaction<'_> {
287 type Target = Transaction;
288
289 fn deref(&self) -> &Self::Target {
290 match self {
291 Self::Owned(x) => x,
292 Self::Borrowed(x) => x,
293 }
294 }
295}
296
297impl DerefMut for MaybeOwnedTransaction<'_> {
298 fn deref_mut(&mut self) -> &mut Self::Target {
299 match self {
300 Self::Owned(x) => x,
301 Self::Borrowed(x) => x,
302 }
303 }
304}