1use super::{Db, Target};
4use crate::backend::{Backend, Executor, Transaction};
5use crate::capabilities::{BackendKind, Feature};
6use crate::capabilities::{IsolationLevel, TransactionSupport};
7use crate::error::{BackendCapabilityError, OrmError, QueryError};
8use std::future::Future;
9use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
10
11fn lock<T>(cell: &Mutex<T>) -> MutexGuard<'_, T> {
12 cell.lock().unwrap_or_else(PoisonError::into_inner)
13}
14
15#[cfg(test)]
16mod tests;
17
18type Hook = Box<dyn FnOnce() + Send>;
19
20#[derive(Default)]
21pub(super) struct Hooks(Mutex<Vec<Hook>>);
22
23impl Hooks {
24 fn push(&self, hook: Hook) {
25 lock(&self.0).push(hook);
26 }
27
28 fn take(&self) -> Vec<Hook> {
29 std::mem::take(&mut *lock(&self.0))
30 }
31}
32
33struct Lifecycle {
34 handle: Option<Arc<dyn Transaction>>,
35 active: u64,
36 next: u64,
37 running: bool,
38 aborted: bool,
39}
40
41pub(super) struct Control(Mutex<Lifecycle>);
42
43impl Control {
44 fn new(handle: Arc<dyn Transaction>) -> Arc<Self> {
45 Arc::new(Self(Mutex::new(Lifecycle {
46 handle: Some(handle),
47 active: 0,
48 next: 0,
49 running: false,
50 aborted: false,
51 })))
52 }
53
54 fn check(state: &Lifecycle, scope: u64) -> Result<(), OrmError> {
55 if state.aborted {
56 return Err(QueryError::TransactionAborted.into());
57 }
58 if state.handle.is_none() {
59 return Err(QueryError::TransactionClosed.into());
60 }
61 if state.active != scope || state.running {
62 return Err(QueryError::TransactionBusy.into());
63 }
64 Ok(())
65 }
66
67 fn ready(&self, scope: u64) -> Result<(), OrmError> {
68 Self::check(&lock(&self.0), scope)
69 }
70
71 fn child(self: &Arc<Self>, parent: u64) -> Result<Scope, OrmError> {
72 let mut state = lock(&self.0);
73 Self::check(&state, parent)?;
74 let id = state
75 .next
76 .checked_add(1)
77 .ok_or_else(|| QueryError::InvalidPlan("scope overflow".into()))?;
78 state.next = id;
79 state.active = id;
80 Ok(Scope {
81 control: Arc::clone(self),
82 id,
83 parent,
84 armed: true,
85 hooks: Arc::default(),
86 })
87 }
88
89 pub(super) fn lease(self: &Arc<Self>, id: u64) -> Result<Lease, OrmError> {
90 let mut state = lock(&self.0);
91 Self::check(&state, id)?;
92 let handle = state
93 .handle
94 .as_ref()
95 .ok_or(QueryError::TransactionClosed)?
96 .clone();
97 state.running = true;
98 Ok(Lease {
99 control: Arc::clone(self),
100 handle,
101 settled: false,
102 })
103 }
104
105 fn close(&self, aborted: bool) {
106 let handle = {
108 let mut state = lock(&self.0);
109 state.aborted |= aborted;
110 state.handle.take()
111 };
112 drop(handle);
113 }
114
115 fn end_root(&self) -> Result<(), OrmError> {
116 let (ready, handle) = {
117 let mut state = lock(&self.0);
118 let ready = Self::check(&state, 0);
119 state.aborted |= ready.is_err();
120 (ready, state.handle.take())
121 };
122 drop(handle);
123 ready
124 }
125
126 fn register(&self, scope: u64, hooks: &Hooks, hook: Hook) {
127 let state = lock(&self.0);
128 if !state.aborted && state.handle.is_some() && state.active == scope {
129 hooks.push(hook);
130 }
131 }
132}
133
134pub(super) struct Lease {
136 control: Arc<Control>,
137 handle: Arc<dyn Transaction>,
138 settled: bool,
139}
140
141impl Lease {
142 async fn execute_script(&mut self, sql: &str) -> Result<(), OrmError> {
143 let result = self.handle.execute_script(sql).await;
144 self.settled = true;
145 result
146 }
147}
148
149impl Drop for Lease {
150 fn drop(&mut self) {
151 let handle = {
152 let mut state = self
153 .control
154 .0
155 .lock()
156 .unwrap_or_else(PoisonError::into_inner);
157 state.running = false;
158 if self.settled {
159 None
160 } else {
161 state.aborted = true;
162 state.handle.take()
163 }
164 };
165 drop(handle);
166 }
167}
168
169pub(super) enum ExecutorRef<'a> {
170 Pool(&'a dyn Executor),
171 Leased(Lease),
172}
173
174impl ExecutorRef<'_> {
175 pub(super) fn finish(&mut self) {
176 if let Self::Leased(lease) = self {
177 lease.settled = true;
178 }
179 }
180
181 pub(super) fn as_ref(&self) -> &dyn Executor {
182 match self {
183 Self::Pool(pool) => *pool,
184 Self::Leased(lease) => lease.handle.as_ref(),
185 }
186 }
187}
188
189pub(super) struct TxState {
190 pub(super) control: Arc<Control>,
191 pub(super) pool: Arc<dyn Backend>,
192 pub(super) hooks: Arc<Hooks>,
193 pub(super) scope: u64,
194}
195
196pub(super) struct ConnState {
197 pub(super) control: Arc<Control>,
198 pub(super) pool: Arc<dyn Backend>,
199}
200
201struct Scope {
203 control: Arc<Control>,
204 id: u64,
205 parent: u64,
206 armed: bool,
207 hooks: Arc<Hooks>,
208}
209
210impl Scope {
211 fn root(control: &Arc<Control>) -> Self {
212 Self {
213 control: Arc::clone(control),
214 id: 0,
215 parent: 0,
216 armed: true,
217 hooks: Arc::default(),
218 }
219 }
220
221 fn release(
222 &mut self,
223 hooks: &Hooks,
224 parent: Option<&Hooks>,
225 keep: bool,
226 ) -> Result<(), OrmError> {
227 let mut state = self
228 .control
229 .0
230 .lock()
231 .unwrap_or_else(PoisonError::into_inner);
232 Control::check(&state, self.id)?;
233 if keep && let Some(parent) = parent {
234 for hook in hooks.take() {
235 parent.push(hook);
236 }
237 }
238 state.active = self.parent;
239 self.armed = false;
240 Ok(())
241 }
242}
243
244impl Drop for Scope {
245 fn drop(&mut self) {
246 if self.armed {
247 self.control.close(true);
248 }
249 drop(self.hooks.take());
251 }
252}
253
254async fn rollback(handle: &dyn Transaction) {
255 if let Err(error) = handle.rollback().await {
256 tracing::warn!(%error, "transaction rollback failed");
257 }
258}
259
260impl Db {
261 pub async fn transaction<F, Fut, T, E>(&self, f: F) -> Result<T, E>
280 where
281 F: FnOnce(Db) -> Fut,
282 Fut: Future<Output = Result<T, E>>,
283 E: From<OrmError>,
284 {
285 self.run_transaction(None, f).await
286 }
287
288 pub async fn transaction_with<F, Fut, T, E>(
300 &self,
301 isolation: IsolationLevel,
302 f: F,
303 ) -> Result<T, E>
304 where
305 F: FnOnce(Db) -> Fut,
306 Fut: Future<Output = Result<T, E>>,
307 E: From<OrmError>,
308 {
309 self.run_transaction(Some(isolation), f).await
310 }
311
312 pub async fn schema_change<F, Fut, T, E>(
328 &self,
329 transactional: bool,
331 f: F,
332 ) -> Result<T, E>
333 where
334 F: FnOnce(Db) -> Fut,
335 Fut: Future<Output = Result<T, E>>,
336 E: From<OrmError>,
337 {
338 match &self.target {
339 Target::Pool(pool) => {
340 let handle: Arc<dyn Transaction> =
341 Arc::from(pool.begin_schema(transactional).await?);
342 self.root_scope(pool, handle, transactional, f).await
343 }
344 _ if self.capabilities().kind == BackendKind::Sqlite => {
345 let reason = "SQLite schema requires a pool handle";
346 let error = QueryError::InvalidPlan(reason.into());
347 Err(OrmError::from(error).into())
348 }
349 _ => self.run_transaction(None, f).await,
350 }
351 }
352
353 async fn root_scope<F, Fut, T, E>(
354 &self,
355 pool: &Arc<dyn Backend>,
356 handle: Arc<dyn Transaction>,
357 transactional: bool,
358 f: F,
359 ) -> Result<T, E>
360 where
361 F: FnOnce(Db) -> Fut,
362 Fut: Future<Output = Result<T, E>>,
363 E: From<OrmError>,
364 {
365 let control = Control::new(Arc::clone(&handle));
366 let mut guard = Scope::root(&control);
367 let hooks = Arc::clone(&guard.hooks);
368 let target = if transactional {
369 Target::Tx(Arc::new(TxState {
370 control: Arc::clone(&control),
371 pool: Arc::clone(pool),
372 hooks: Arc::clone(&hooks),
373 scope: 0,
374 }))
375 } else {
376 Target::Conn(Arc::new(ConnState {
377 control: Arc::clone(&control),
378 pool: Arc::clone(pool),
379 }))
380 };
381 let result = f(Db {
382 target,
383 signals: self.signals.clone(),
384 })
385 .await;
386 let ready = control.end_root();
387 match result {
388 Ok(value) => {
389 if let Err(error) = ready {
390 rollback(handle.as_ref()).await;
391 return Err(error.into());
392 }
393 handle.commit().await?;
394 guard.armed = false;
395 hooks.take().into_iter().for_each(|hook| hook());
396 Ok(value)
397 }
398 Err(error) => {
399 rollback(handle.as_ref()).await;
400 guard.armed = false;
401 Err(error)
402 }
403 }
404 }
405
406 async fn run_transaction<F, Fut, T, E>(
407 &self,
408 isolation: Option<IsolationLevel>,
409 f: F,
410 ) -> Result<T, E>
411 where
412 F: FnOnce(Db) -> Fut,
413 Fut: Future<Output = Result<T, E>>,
414 E: From<OrmError>,
415 {
416 if let Target::Pool(pool) = &self.target {
417 if let Some(level) = isolation {
418 pool.capabilities()
419 .require(Feature::Isolation(level))
420 .map_err(OrmError::from)?;
421 }
422 let handle = Arc::from(pool.begin(isolation).await?);
423 return self.root_scope(pool, handle, true, f).await;
424 }
425 if isolation.is_some() {
426 return Err(OrmError::from(QueryError::InvalidPlan(
427 "isolation level cannot be set on a nested transaction".into(),
428 ))
429 .into());
430 }
431 let (control, pool, parent_scope, parent_hooks) = match &self.target {
432 Target::Tx(parent) => {
433 let caps = parent.pool.capabilities();
434 if caps.transactions < TransactionSupport::Savepoints {
435 let error = BackendCapabilityError::Unsupported {
436 backend: caps.kind,
437 feature: Feature::Savepoints,
438 };
439 return Err(OrmError::from(error).into());
440 }
441 (
442 &parent.control,
443 &parent.pool,
444 parent.scope,
445 Some(&parent.hooks),
446 )
447 }
448 Target::Conn(parent) => (&parent.control, &parent.pool, 0, None),
449 Target::Pool(_) => unreachable!("pool handled above"),
450 };
451 let mut guard = control.child(parent_scope)?;
452 let name = format!("siderite_sp_{}", guard.id);
453 let begin = if parent_hooks.is_some() {
454 format!("SAVEPOINT {name}")
455 } else {
456 "BEGIN".into()
457 };
458 control.lease(guard.id)?.execute_script(&begin).await?;
459 let hooks = Arc::clone(&guard.hooks);
460 let child = Db {
461 target: Target::Tx(Arc::new(TxState {
462 control: Arc::clone(control),
463 pool: Arc::clone(pool),
464 hooks: Arc::clone(&hooks),
465 scope: guard.id,
466 })),
467 signals: self.signals.clone(),
468 };
469 let result = f(child).await;
470 if let Err(error) = control.ready(guard.id) {
471 tracing::warn!(%error, "transaction scope became unusable");
472 return match result {
473 Err(original) => Err(original),
474 Ok(_) => Err(error.into()),
475 };
476 }
477 let end = match (&result, parent_hooks) {
478 (Ok(_), Some(_)) => format!("RELEASE SAVEPOINT {name}"),
479 (Err(_), Some(_)) => format!("ROLLBACK TO SAVEPOINT {name}"),
480 (Ok(_), None) => "COMMIT".into(),
481 (Err(_), None) => "ROLLBACK".into(),
482 };
483 let cleanup = async {
484 control.lease(guard.id)?.execute_script(&end).await?;
485 if result.is_err() && parent_hooks.is_some() {
486 control
487 .lease(guard.id)?
488 .execute_script(&format!("RELEASE SAVEPOINT {name}"))
489 .await?;
490 }
491 Ok::<_, OrmError>(())
492 }
493 .await;
494 if let Err(error) = cleanup {
495 tracing::warn!(%error, "transaction scope cleanup failed");
496 return match result {
497 Err(original) => Err(original),
498 Ok(_) => Err(error.into()),
499 };
500 }
501 guard.release(&hooks, parent_hooks.map(Arc::as_ref), result.is_ok())?;
502 if result.is_ok() && parent_hooks.is_none() {
503 hooks.take().into_iter().for_each(|hook| hook());
504 }
505 result
506 }
507
508 pub fn on_commit(&self, hook: impl FnOnce() + Send + 'static) {
520 match &self.target {
521 Target::Pool(_) => hook(),
522 Target::Conn(state) => {
523 if state.control.ready(0).is_ok() {
524 hook();
525 }
526 }
527 Target::Tx(state) => {
528 state
529 .control
530 .register(state.scope, &state.hooks, Box::new(hook));
531 }
532 }
533 }
534}