teaql_runtime/context/
transaction.rs1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Mutex;
4
5use teaql_data_service::{
6 MutationExecutor, MutationRequest, MutationResult, QueryExecutor, QueryRequest, QueryResult,
7 Transaction, TransactionExecutor,
8};
9
10use crate::{ContextError, EntityDataService, RuntimeError, UserContext};
11
12#[must_use = "transaction work must be completed through TransactionScope::execute"]
19pub struct TransactionScope<'context, E>
20where
21 E: TransactionExecutor + 'context,
22{
23 context: &'context UserContext,
24 transaction: Option<E::Tx<'context>>,
25 flushed_ledgers: Mutex<Vec<crate::EntityRuntimeState>>,
26}
27
28impl UserContext {
29 async fn start_transaction<E>(&self) -> Result<TransactionScope<'_, E>, RuntimeError>
31 where
32 E: TransactionExecutor + Send + Sync + 'static,
33 for<'transaction> E::Tx<'transaction>: Send + Sync,
34 {
35 let executor = self
36 .require_resource::<E>()
37 .map_err(|error| RuntimeError::Transaction(error.to_string()))?;
38 let transaction = TransactionExecutor::begin(executor)
39 .await
40 .map_err(|error| RuntimeError::Transaction(error.to_string()))?;
41 Ok(TransactionScope {
42 context: self,
43 transaction: Some(transaction),
44 flushed_ledgers: Mutex::new(Vec::new()),
45 })
46 }
47
48 pub async fn execute_in_transaction<'context, E, T, F>(
55 &'context self,
56 operation: F,
57 ) -> Result<T, RuntimeError>
58 where
59 E: TransactionExecutor + Send + Sync + 'static,
60 for<'transaction> E::Tx<'transaction>: Send + Sync,
61 F: for<'scope> FnOnce(
62 &'scope TransactionScope<'context, E>,
63 )
64 -> Pin<Box<dyn Future<Output = Result<T, RuntimeError>> + 'scope>>,
65 {
66 self.start_transaction::<E>()
67 .await?
68 .execute(operation)
69 .await
70 }
71}
72
73impl<'context, E> TransactionScope<'context, E>
74where
75 E: TransactionExecutor + 'context,
76{
77 fn transaction(&self) -> &E::Tx<'context> {
78 self.transaction
79 .as_ref()
80 .expect("transaction scope cannot be used after completion")
81 }
82
83 pub fn context(&self) -> &'context UserContext {
86 self.context
87 }
88
89 pub fn entity_data_service(
91 &self,
92 entity: impl Into<String>,
93 ) -> Result<EntityDataService<'_, E::Tx<'context>>, ContextError>
94 where
95 E::Tx<'context>: QueryExecutor + MutationExecutor + Send + Sync,
96 {
97 let entity = entity.into();
98 if !self.context.has_entity_data_service(&entity) {
99 return Err(ContextError::MissingEntityDataService(entity));
100 }
101 Ok(EntityDataService::for_executor(
102 self.context,
103 entity,
104 self.transaction(),
105 ))
106 }
107
108 pub async fn query(&self, request: QueryRequest) -> Result<QueryResult, RuntimeError>
110 where
111 E::Tx<'context>: QueryExecutor,
112 {
113 let result = QueryExecutor::query(self.transaction(), request)
114 .await
115 .map_err(|error| RuntimeError::Transaction(error.to_string()))?;
116 self.context.record_metadata_log(&result.metadata);
117 Ok(result)
118 }
119
120 pub async fn mutate(&self, request: MutationRequest) -> Result<MutationResult, RuntimeError>
122 where
123 E::Tx<'context>: MutationExecutor,
124 {
125 let result = MutationExecutor::mutate(self.transaction(), request)
126 .await
127 .map_err(|error| RuntimeError::Transaction(error.to_string()))?;
128 self.context.record_metadata_log(&result.metadata);
129 Ok(result)
130 }
131
132 pub async fn save_audited<T>(&self, audited: teaql_core::Audited<T>) -> Result<T, RuntimeError>
138 where
139 T: crate::LedgerEntity + Send + 'static,
140 E::Tx<'context>: QueryExecutor + MutationExecutor + Send + Sync,
141 {
142 let (entity, ledger) = crate::save_audited_ledger_entity_with_executor(
143 audited,
144 self.context,
145 self.transaction(),
146 )
147 .await?;
148 if let Some(ledger) = ledger {
149 let mut ledgers = self
150 .flushed_ledgers
151 .lock()
152 .unwrap_or_else(|error| error.into_inner());
153 if !ledgers.iter().any(|pending| pending == &ledger) {
154 ledgers.push(ledger);
155 }
156 }
157 Ok(entity)
158 }
159
160 pub async fn execute<T, F>(mut self, operation: F) -> Result<T, RuntimeError>
162 where
163 E::Tx<'context>: Send + Sync,
164 F: for<'scope> FnOnce(
165 &'scope TransactionScope<'context, E>,
166 )
167 -> Pin<Box<dyn Future<Output = Result<T, RuntimeError>> + 'scope>>,
168 {
169 match operation(&self).await {
170 Ok(value) => {
171 let transaction = self
172 .transaction
173 .take()
174 .expect("transaction scope cannot be completed twice");
175 Transaction::commit(transaction)
176 .await
177 .map_err(|error| RuntimeError::Transaction(error.to_string()))?;
178 for ledger in self
179 .flushed_ledgers
180 .lock()
181 .unwrap_or_else(|error| error.into_inner())
182 .drain(..)
183 {
184 ledger.clear_committed();
185 }
186 Ok(value)
187 }
188 Err(error) => {
189 let transaction = self
190 .transaction
191 .take()
192 .expect("transaction scope cannot be completed twice");
193 Transaction::rollback(transaction)
194 .await
195 .map_err(|rollback| {
196 RuntimeError::Transaction(format!(
197 "operation failed ({error}); rollback also failed ({rollback})"
198 ))
199 })?;
200 Err(error)
201 }
202 }
203 }
204}