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