1use std::fmt::Write;
2use std::sync::Arc;
3use std::{collections::BTreeMap, future::Future};
4
5use teaql_core::{
6 DeleteCommand, Entity, InsertCommand, Record, RecoverCommand, SelectQuery, SmartList,
7 UpdateCommand,
8};
9
10use crate::{
11 ContextError, DataServiceError, GraphMutationPlan, GraphNode, RuntimeError, UserContext,
12};
13
14use super::{
15 AggregationCacheBackend, ContextDataService, EntityDataService, InMemoryAggregationCache,
16 RuntimeDataService, UserContextMetadata, helpers::invalidate_aggregation_cache_namespace,
17};
18
19impl UserContext {
20 pub(crate) fn data_service_internal<E>(&self) -> Result<ContextDataService<'_, E>, ContextError>
21 where
22 E: teaql_data_service::QueryExecutor
23 + teaql_data_service::MutationExecutor
24 + Send
25 + Sync
26 + 'static,
27 {
28 if self.metadata.is_none() {
29 return Err(ContextError::MissingResource("metadata".to_owned()));
30 }
31
32 let executor = self.require_resource::<E>()?;
33 Ok(ContextDataService {
34 metadata: UserContextMetadata { context: self },
35 executor,
36 })
37 }
38
39 pub fn entity_data_service<E>(
40 &self,
41 entity: impl Into<String>,
42 ) -> Result<EntityDataService<'_, E>, ContextError>
43 where
44 E: teaql_data_service::QueryExecutor
45 + teaql_data_service::MutationExecutor
46 + Send
47 + Sync
48 + 'static,
49 {
50 let entity = entity.into();
51 if !self.has_entity_data_service(&entity) {
52 return Err(ContextError::MissingEntityDataService(entity));
53 }
54 Ok(EntityDataService {
55 entity,
56 data_service: self.data_service_internal::<E>()?,
57 trace_context: Vec::new(),
58 })
59 }
60
61 pub fn register_executor<E>(&mut self, executor: E)
65 where
66 E: teaql_data_service::QueryExecutor
67 + teaql_data_service::MutationExecutor
68 + Send
69 + Sync
70 + 'static,
71 {
72 use std::sync::Arc;
73 self.insert_resource::<Arc<dyn crate::entity_save::DynGraphSaver>>(Arc::new(
74 crate::entity_save::GraphSaverFor::<E>::new(),
75 ));
76 self.insert_resource(executor);
77 }
78}
79
80impl<'a, E> ContextDataService<'a, E>
81where
82 E: teaql_data_service::QueryExecutor
83 + teaql_data_service::MutationExecutor
84 + Send
85 + Sync
86 + 'static,
87{
88 async fn observe<T, F>(
89 &self,
90 family: &str,
91 name: String,
92 entity: &str,
93 work: F,
94 ) -> Result<T, DataServiceError<E::Error>>
95 where
96 F: Future<Output = Result<T, DataServiceError<E::Error>>>,
97 {
98 let operation = crate::RuntimeOperation::new(family, name)
99 .attribute("teaql.entity.type", entity.to_owned());
100 let scope = self.metadata.context.start_runtime_operation(operation);
101 let provider_kind = std::any::type_name::<E>().to_owned();
102 let provider_operation = family.to_owned();
103 let result = scope
104 .run(async {
105 let provider_scope = self.metadata.context.start_runtime_operation(
106 crate::RuntimeOperation::new(
107 "provider",
108 format!("{provider_kind}.{provider_operation}"),
109 )
110 .attribute("teaql.provider.kind", provider_kind)
111 .attribute("teaql.provider.operation", provider_operation),
112 );
113 let result = provider_scope.run(work).await;
114 match &result {
115 Ok(_) => provider_scope.success(BTreeMap::new()),
116 Err(_) => provider_scope.failure("data_service_error"),
117 }
118 result
119 })
120 .await;
121 match result {
122 Ok(value) => {
123 scope.success(BTreeMap::new());
124 Ok(value)
125 }
126 Err(error) => {
127 scope.failure("data_service_error");
128 Err(error)
129 }
130 }
131 }
132
133 fn data_service(&self) -> RuntimeDataService<'_, UserContextMetadata<'_>, E> {
134 RuntimeDataService::new(&self.metadata, self.executor)
135 }
136
137 pub(crate) async fn fetch_all(
138 &self,
139 mut query: SelectQuery,
140 ) -> Result<Vec<Record>, DataServiceError<E::Error>> {
141 let final_comment = self.resolve_final_comment(&query.trace_chain, query.comment.clone());
142 query.comment = final_comment;
143 self.observe(
144 "query",
145 format!("{}.list", query.entity),
146 &query.entity,
147 self.data_service().fetch_all(&query),
148 )
149 .await
150 }
151
152 pub(crate) async fn fetch_smart_list(
153 &self,
154 query: &SelectQuery,
155 ) -> Result<SmartList<Record>, DataServiceError<E::Error>> {
156 self.observe(
157 "query",
158 format!("{}.list", query.entity),
159 &query.entity,
160 self.data_service().fetch_smart_list(query),
161 )
162 .await
163 }
164
165 pub(crate) async fn fetch_entities<T>(
166 &self,
167 query: &SelectQuery,
168 ) -> Result<SmartList<T>, DataServiceError<E::Error>>
169 where
170 T: Entity,
171 {
172 self.observe(
173 "query",
174 format!("{}.list", query.entity),
175 &query.entity,
176 self.data_service().fetch_entities(query),
177 )
178 .await
179 }
180
181 pub(crate) async fn fetch_enhanced_entities<T>(
182 &self,
183 query: &SelectQuery,
184 ) -> Result<SmartList<T>, DataServiceError<E::Error>>
185 where
186 T: Entity,
187 {
188 self.observe(
189 "query",
190 format!("{}.list", query.entity),
191 &query.entity,
192 self.data_service().fetch_enhanced_entities(query),
193 )
194 .await
195 }
196
197 pub(crate) async fn insert(
198 &self,
199 command: &InsertCommand,
200 ) -> Result<u64, DataServiceError<E::Error>> {
201 let affected = self
202 .observe(
203 "mutation",
204 format!("{}.insert", command.entity),
205 &command.entity,
206 self.data_service().insert(command),
207 )
208 .await?;
209 self.invalidate_aggregation_cache_for(&command.entity);
210 Ok(affected)
211 }
212
213 pub(crate) async fn update(
214 &self,
215 command: &UpdateCommand,
216 ) -> Result<u64, DataServiceError<E::Error>> {
217 let affected = self
218 .observe(
219 "mutation",
220 format!("{}.update", command.entity),
221 &command.entity,
222 self.data_service().update(command),
223 )
224 .await?;
225 self.invalidate_aggregation_cache_for(&command.entity);
226 Ok(affected)
227 }
228
229 pub(crate) async fn batch_insert(
230 &self,
231 command: &teaql_core::BatchInsertCommand,
232 ) -> Result<u64, DataServiceError<E::Error>> {
233 let affected = self
234 .observe(
235 "mutation",
236 format!("{}.batch_insert", command.entity),
237 &command.entity,
238 self.data_service().batch_insert(command),
239 )
240 .await?;
241 self.invalidate_aggregation_cache_for(&command.entity);
242 Ok(affected)
243 }
244
245 pub(crate) async fn batch_update(
246 &self,
247 command: &teaql_core::BatchUpdateCommand,
248 ) -> Result<u64, DataServiceError<E::Error>> {
249 let affected = self
250 .observe(
251 "mutation",
252 format!("{}.batch_update", command.entity),
253 &command.entity,
254 self.data_service().batch_update(command),
255 )
256 .await?;
257 self.invalidate_aggregation_cache_for(&command.entity);
258 Ok(affected)
259 }
260
261 pub(crate) async fn delete(
262 &self,
263 command: &DeleteCommand,
264 ) -> Result<u64, DataServiceError<E::Error>> {
265 let affected = self
266 .observe(
267 "mutation",
268 format!("{}.delete", command.entity),
269 &command.entity,
270 self.data_service().delete(command),
271 )
272 .await?;
273 self.invalidate_aggregation_cache_for(&command.entity);
274 Ok(affected)
275 }
276
277 pub(crate) async fn recover(
278 &self,
279 command: &RecoverCommand,
280 ) -> Result<u64, DataServiceError<E::Error>> {
281 let affected = self
282 .observe(
283 "mutation",
284 format!("{}.recover", command.entity),
285 &command.entity,
286 self.data_service().recover(command),
287 )
288 .await?;
289 self.invalidate_aggregation_cache_for(&command.entity);
290 Ok(affected)
291 }
292
293 pub(super) fn invalidate_aggregation_cache_for(&self, entity: &str) {
294 if let Some(cache) = self
295 .metadata
296 .context
297 .get_resource::<Arc<dyn AggregationCacheBackend>>()
298 {
299 invalidate_aggregation_cache_namespace(cache.as_ref(), entity);
300 }
301 if let Some(cache) = self
302 .metadata
303 .context
304 .get_resource::<InMemoryAggregationCache>()
305 {
306 invalidate_aggregation_cache_namespace(cache, entity);
307 }
308 }
309
310 pub(crate) fn resolve_final_comment(
311 &self,
312 trace_chain: &[teaql_core::TraceNode],
313 comment: Option<String>,
314 ) -> Option<String> {
315 let chain_str = (!trace_chain.is_empty()).then(|| {
316 let mut chain = String::with_capacity(trace_chain.len().saturating_mul(64));
317 for (index, node) in trace_chain.iter().enumerate() {
318 if index > 0 {
319 chain.push_str(" -> ");
320 }
321 match node.entity_id {
322 Some(id) => {
323 let _ = write!(chain, "{}({id}): {}", node.entity_type, node.comment);
324 }
325 None => {
326 let _ = write!(
327 chain,
328 "{}(pending): {}",
329 node.entity_type, node.comment
330 );
331 }
332 }
333 }
334 chain
335 });
336
337 let business_comment = chain_str.or(comment);
338 let user_id = self
339 .metadata
340 .context
341 .user_identifier()
342 .map(|s| s.to_owned());
343
344 match (user_id, business_comment) {
345 (Some(user), Some(bus)) if !user.is_empty() && !bus.is_empty() => {
346 Some(format!("[{user}] {bus}"))
347 }
348 (Some(user), _) if !user.is_empty() => Some(format!("[{user}]")),
349 (_, Some(bus)) if !bus.is_empty() => Some(bus),
350 _ => None,
351 }
352 }
353}