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            entity_type: query.entity.clone(),
224            entity_id: None,
225            comment: purpose,
226        });
227        Self { query }
228    }
229
230    pub fn as_query(&self) -> &SelectQuery {
231        &self.query
232    }
233
234    pub fn into_query(self) -> SelectQuery {
235        self.query
236    }
237}
238
239pub async fn execute_facets<C>(
240    context: &C,
241    outer_query: &SelectQuery,
242    options: &QueryOptions,
243) -> Result<BTreeMap<String, SmartList<CompactRow>>, RuntimeError>
244where
245    C: TeaqlRuntime + ?Sized,
246{
247    let mut facets = BTreeMap::new();
248    for facet in &options.facets {
249        let mut selection = facet.query.clone();
250        merge_outer_filter_into_facet_aggregates(&mut selection, outer_query);
251        if !facet.include_all_facets {
252            selection = restrict_facet_to_outer_query(
253                context,
254                selection,
255                outer_query,
256                &facet.relation_name,
257            )?;
258        }
259        let relation_aggregates = runtime_relation_aggregates(&selection.query_options);
260        let query = apply_runtime_metadata(
261            selection.query,
262            &selection.query_options,
263            &selection.child_enhancements,
264        );
265        let entity = query.entity.clone();
266        let mut chain = outer_query.trace_chain.clone();
267        chain.push(TraceNode {
268            entity_type: query.entity.clone(),
269            entity_id: None,
270            comment: facet.facet_name.clone(),
271        });
272
273        let query =
274            PurposedSelectQuery::new(query, format!("Calculate facet {}", facet.facet_name));
275        let facet_rows = context
276            .fetch_facet_smart_list(&entity, &query, &relation_aggregates, chain)
277            .await?;
278        facets.insert(facet.facet_name.clone(), facet_rows);
279    }
280    Ok(facets)
281}
282
283pub fn restrict_facet_to_outer_query<C>(
284    context: &C,
285    mut selection: QuerySelection,
286    outer_query: &SelectQuery,
287    relation_name: &str,
288) -> Result<QuerySelection, RuntimeError>
289where
290    C: TeaqlRuntime + ?Sized,
291{
292    let descriptor = context
293        .user_context()
294        .entity(&outer_query.entity)
295        .cloned()
296        .ok_or_else(|| RuntimeError::Graph(format!("missing entity: {}", outer_query.entity)))?;
297    let relation = descriptor
298        .relation_by_name(relation_name)
299        .cloned()
300        .ok_or_else(|| RuntimeError::MissingRelation {
301            entity: outer_query.entity.clone(),
302            relation: relation_name.to_owned(),
303        })?;
304    let mut subquery = outer_query.clone();
305    subquery.projection.clear();
306    subquery.expr_projection.clear();
307    subquery.order_by.clear();
308    subquery.slice = None;
309    subquery.aggregates.clear();
310    subquery.group_by.clear();
311    subquery.relations.clear();
312    selection.query = selection.query.and_filter(Expr::in_subquery(
313        relation.foreign_key,
314        descriptor,
315        subquery,
316        relation.local_key,
317    ));
318    Ok(selection)
319}