1#![allow(unused_imports)]
2#![allow(async_fn_in_trait)]
3
4use crate::{DataServiceError, GraphNode, RuntimeError, UserContext};
5use std::collections::BTreeMap;
6use teaql_core::request::{
7 QueryOptions, QuerySelection, apply_runtime_metadata, merge_outer_filter_into_facet_aggregates,
8 runtime_relation_aggregates,
9};
10use teaql_core::{
11 Expr, Record, RelationAggregate as RuntimeRelationAggregate, SelectQuery, SmartList, TraceNode,
12};
13
14pub trait TeaqlRecordDataService {
15 type Error: std::error::Error + Send + Sync + 'static;
16
17 async fn fetch_all(
18 &self,
19 query: &PurposedSelectQuery,
20 ) -> Result<Vec<Record>, DataServiceError<Self::Error>>;
21
22 async fn fetch_smart_list(
23 &self,
24 query: &PurposedSelectQuery,
25 ) -> Result<SmartList<Record>, DataServiceError<Self::Error>>;
26
27 async fn fetch_smart_list_with_relation_aggregates(
28 &self,
29 query: &PurposedSelectQuery,
30 relation_aggregates: &[RuntimeRelationAggregate],
31 ) -> Result<SmartList<Record>, DataServiceError<Self::Error>>;
32
33 async fn fetch_stream(
34 &self,
35 query: &PurposedSelectQuery,
36 ) -> Result<
37 std::pin::Pin<
38 Box<
39 dyn futures_core::Stream<
40 Item = Result<
41 teaql_data_service::StreamChunk,
42 DataServiceError<Self::Error>,
43 >,
44 > + '_,
45 >,
46 >,
47 DataServiceError<Self::Error>,
48 >;
49}
50
51pub trait TeaqlEntityDataService: TeaqlRecordDataService {
52 async fn fetch_enhanced_entities<T>(
53 &self,
54 query: &PurposedSelectQuery,
55 ) -> Result<SmartList<T>, DataServiceError<Self::Error>>
56 where
57 T: teaql_core::Entity;
58
59 async fn fetch_enhanced_entities_with_relation_aggregates<T>(
60 &self,
61 query: &PurposedSelectQuery,
62 relation_aggregates: &[RuntimeRelationAggregate],
63 ) -> Result<SmartList<T>, DataServiceError<Self::Error>>
64 where
65 T: teaql_core::Entity;
66}
67
68impl<'a, E> TeaqlRecordDataService for crate::EntityDataService<'a, E>
69where
70 E: teaql_data_service::QueryExecutor
71 + teaql_data_service::MutationExecutor
72 + teaql_data_service::StreamQueryExecutor
73 + Send
74 + Sync
75 + 'static,
76{
77 type Error = E::Error;
78
79 async fn fetch_all(
80 &self,
81 query: &PurposedSelectQuery,
82 ) -> Result<Vec<Record>, DataServiceError<Self::Error>> {
83 crate::EntityDataService::fetch_all(self, query).await
84 }
85
86 async fn fetch_smart_list(
87 &self,
88 query: &PurposedSelectQuery,
89 ) -> Result<SmartList<Record>, DataServiceError<Self::Error>> {
90 crate::EntityDataService::fetch_smart_list(self, query).await
91 }
92
93 async fn fetch_smart_list_with_relation_aggregates(
94 &self,
95 query: &PurposedSelectQuery,
96 relation_aggregates: &[RuntimeRelationAggregate],
97 ) -> Result<SmartList<Record>, DataServiceError<Self::Error>> {
98 crate::EntityDataService::fetch_smart_list_with_relation_aggregates(
99 self,
100 query,
101 relation_aggregates,
102 )
103 .await
104 }
105
106 async fn fetch_stream(
107 &self,
108 query: &PurposedSelectQuery,
109 ) -> Result<
110 std::pin::Pin<
111 Box<
112 dyn futures_core::Stream<
113 Item = Result<
114 teaql_data_service::StreamChunk,
115 DataServiceError<Self::Error>,
116 >,
117 > + '_,
118 >,
119 >,
120 DataServiceError<Self::Error>,
121 > {
122 crate::EntityDataService::fetch_stream(self, query).await
123 }
124}
125
126impl<'a, E> TeaqlEntityDataService for crate::EntityDataService<'a, E>
127where
128 E: teaql_data_service::QueryExecutor
129 + teaql_data_service::MutationExecutor
130 + teaql_data_service::StreamQueryExecutor
131 + Send
132 + Sync
133 + 'static,
134{
135 async fn fetch_enhanced_entities<T>(
136 &self,
137 query: &PurposedSelectQuery,
138 ) -> Result<SmartList<T>, DataServiceError<Self::Error>>
139 where
140 T: teaql_core::Entity,
141 {
142 crate::EntityDataService::fetch_enhanced_entities(self, query).await
143 }
144
145 async fn fetch_enhanced_entities_with_relation_aggregates<T>(
146 &self,
147 query: &PurposedSelectQuery,
148 relation_aggregates: &[RuntimeRelationAggregate],
149 ) -> Result<SmartList<T>, DataServiceError<Self::Error>>
150 where
151 T: teaql_core::Entity,
152 {
153 crate::EntityDataService::fetch_enhanced_entities_with_relation_aggregates(
154 self,
155 query,
156 relation_aggregates,
157 )
158 .await
159 }
160}
161
162pub type TeaqlDataServiceError<R> = DataServiceError<<R as TeaqlRecordDataService>::Error>;
163
164pub trait TeaqlRuntime {
165 fn user_context(&self) -> &UserContext;
166
167 fn fetch_facet_smart_list(
168 &self,
169 entity: &str,
170 query: &PurposedSelectQuery,
171 relation_aggregates: &[RuntimeRelationAggregate],
172 trace_context: Vec<TraceNode>,
173 ) -> impl std::future::Future<Output = Result<SmartList<Record>, RuntimeError>> + Send;
174}
175
176#[doc(hidden)]
178pub trait AuditedSave<'a, C>
179where
180 C: TeaqlRuntime + ?Sized + 'a,
181{
182 type Error;
183 fn save(
184 self,
185 context: &'a C,
186 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<GraphNode, Self::Error>> + '_>>;
187}
188
189pub struct PurposedQuery<T> {
190 pub inner: T,
191 pub purpose: String,
192}
193
194impl<T> PurposedQuery<T> {
195 pub fn new(inner: T, purpose: impl Into<String>) -> Self {
196 Self {
197 inner,
198 purpose: purpose.into(),
199 }
200 }
201}
202
203#[derive(Debug, Clone)]
210pub struct PurposedSelectQuery {
211 query: SelectQuery,
212}
213
214impl PurposedSelectQuery {
215 pub fn new(mut query: SelectQuery, purpose: impl Into<String>) -> Self {
216 let purpose = purpose.into();
217 assert!(
218 !purpose.trim().is_empty(),
219 "query purpose must not be empty"
220 );
221 query.trace_chain.push(TraceNode {
222 entity_type: query.entity.clone(),
223 entity_id: None,
224 comment: purpose,
225 });
226 Self { query }
227 }
228
229 pub fn as_query(&self) -> &SelectQuery {
230 &self.query
231 }
232
233 pub fn into_query(self) -> SelectQuery {
234 self.query
235 }
236}
237
238pub async fn execute_facets<C>(
239 context: &C,
240 outer_query: &SelectQuery,
241 options: &QueryOptions,
242) -> Result<BTreeMap<String, SmartList<Record>>, RuntimeError>
243where
244 C: TeaqlRuntime + ?Sized,
245{
246 let mut facets = BTreeMap::new();
247 for facet in &options.facets {
248 let mut selection = facet.query.clone();
249 merge_outer_filter_into_facet_aggregates(&mut selection, outer_query);
250 if !facet.include_all_facets {
251 selection = restrict_facet_to_outer_query(
252 context,
253 selection,
254 outer_query,
255 &facet.relation_name,
256 )?;
257 }
258 let relation_aggregates = runtime_relation_aggregates(&selection.query_options);
259 let query = apply_runtime_metadata(
260 selection.query,
261 &selection.query_options,
262 &selection.child_enhancements,
263 );
264 let entity = query.entity.clone();
265 let mut chain = outer_query.trace_chain.clone();
266 chain.push(TraceNode {
267 entity_type: query.entity.clone(),
268 entity_id: None,
269 comment: facet.facet_name.clone(),
270 });
271
272 let query =
273 PurposedSelectQuery::new(query, format!("Calculate facet {}", facet.facet_name));
274 let facet_rows = context
275 .fetch_facet_smart_list(&entity, &query, &relation_aggregates, chain)
276 .await?;
277 facets.insert(facet.facet_name.clone(), facet_rows);
278 }
279 Ok(facets)
280}
281
282pub fn restrict_facet_to_outer_query<C>(
283 context: &C,
284 mut selection: QuerySelection,
285 outer_query: &SelectQuery,
286 relation_name: &str,
287) -> Result<QuerySelection, RuntimeError>
288where
289 C: TeaqlRuntime + ?Sized,
290{
291 let descriptor = context
292 .user_context()
293 .entity(&outer_query.entity)
294 .cloned()
295 .ok_or_else(|| RuntimeError::Graph(format!("missing entity: {}", outer_query.entity)))?;
296 let relation = descriptor
297 .relation_by_name(relation_name)
298 .cloned()
299 .ok_or_else(|| RuntimeError::MissingRelation {
300 entity: outer_query.entity.clone(),
301 relation: relation_name.to_owned(),
302 })?;
303 let mut subquery = outer_query.clone();
304 subquery.projection.clear();
305 subquery.expr_projection.clear();
306 subquery.order_by.clear();
307 subquery.slice = None;
308 subquery.aggregates.clear();
309 subquery.group_by.clear();
310 subquery.relations.clear();
311 selection.query = selection.query.and_filter(Expr::in_subquery(
312 relation.foreign_key,
313 descriptor,
314 subquery,
315 relation.local_key,
316 ));
317 Ok(selection)
318}