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