Skip to main content

teaql_runtime/
generated_support.rs

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/// Internal trait for audited save access. Application code should not use this trait directly.
177#[doc(hidden)]
178pub trait AuditedSave<'a, C>
179where
180    C: TeaqlRuntime + ?Sized + 'a,
181{
182    type Error;
183    fn save(
184        self,
185        ctx: &'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/// A low-level select query carrying an explicit, non-empty execution purpose.
204///
205/// Generated request builders construct this type after `.purpose(...)` unlocks
206/// their terminal methods. Runtime execution APIs accept this wrapper rather
207/// than a bare [`SelectQuery`], so infrastructure callers must also declare
208/// intent explicitly.
209#[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    ctx: &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 =
252                restrict_facet_to_outer_query(ctx, selection, outer_query, &facet.relation_name)?;
253        }
254        let relation_aggregates = runtime_relation_aggregates(&selection.query_options);
255        let query = apply_runtime_metadata(
256            selection.query,
257            &selection.query_options,
258            &selection.child_enhancements,
259        );
260        let entity = query.entity.clone();
261        let mut chain = outer_query.trace_chain.clone();
262        chain.push(TraceNode {
263            entity_type: query.entity.clone(),
264            entity_id: None,
265            comment: facet.facet_name.clone(),
266        });
267
268        let query =
269            PurposedSelectQuery::new(query, format!("Calculate facet {}", facet.facet_name));
270        let facet_rows = ctx
271            .fetch_facet_smart_list(&entity, &query, &relation_aggregates, chain)
272            .await?;
273        facets.insert(facet.facet_name.clone(), facet_rows);
274    }
275    Ok(facets)
276}
277
278pub fn restrict_facet_to_outer_query<C>(
279    ctx: &C,
280    mut selection: QuerySelection,
281    outer_query: &SelectQuery,
282    relation_name: &str,
283) -> Result<QuerySelection, RuntimeError>
284where
285    C: TeaqlRuntime + ?Sized,
286{
287    let descriptor = ctx
288        .user_context()
289        .entity(&outer_query.entity)
290        .cloned()
291        .ok_or_else(|| RuntimeError::Graph(format!("missing entity: {}", outer_query.entity)))?;
292    let relation = descriptor
293        .relation_by_name(relation_name)
294        .cloned()
295        .ok_or_else(|| RuntimeError::MissingRelation {
296            entity: outer_query.entity.clone(),
297            relation: relation_name.to_owned(),
298        })?;
299    let mut subquery = outer_query.clone();
300    subquery.projection.clear();
301    subquery.expr_projection.clear();
302    subquery.order_by.clear();
303    subquery.slice = None;
304    subquery.aggregates.clear();
305    subquery.group_by.clear();
306    subquery.relations.clear();
307    selection.query = selection.query.and_filter(Expr::in_subquery(
308        relation.foreign_key,
309        descriptor,
310        subquery,
311        relation.local_key,
312    ));
313    Ok(selection)
314}