Skip to main content

teaql_runtime/data_service/
relation.rs

1use std::collections::BTreeMap;
2use std::slice;
3
4use teaql_core::{
5    Aggregate, CompactRow, Expr, ObjectGroupBy, OrderBy, RelationAggregate, RelationLoad,
6    SelectQuery, Value,
7};
8
9use crate::{DataServiceError, MetadataStore, RuntimeError};
10
11use super::{EntityDataService, RelationLoadPlan, helpers::*};
12
13#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
14enum FlatIdentityKey {
15    U64(u64),
16    Other(String),
17}
18
19impl FlatIdentityKey {
20    fn from_value(value: &Value) -> Self {
21        match value {
22            Value::U64(value) => Self::U64(*value),
23            Value::I64(value) if *value >= 0 => Self::U64(*value as u64),
24            _ => Self::Other(graph_identity_key(value)),
25        }
26    }
27}
28
29fn unique_relation_values(rows: &[CompactRow], field: &str) -> Vec<Value> {
30    let mut values = rows
31        .iter()
32        .filter_map(|row| row.get(field).cloned())
33        .map(|value| (FlatIdentityKey::from_value(&value), value))
34        .collect::<Vec<_>>();
35    values.sort_unstable_by(|left, right| left.0.cmp(&right.0));
36    values.dedup_by(|left, right| left.0 == right.0);
37    values.into_iter().map(|(_, value)| value).collect()
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41enum RelationTopNExecutionPlan {
42    Window,
43    BoundedProbes,
44}
45
46fn relation_top_n_operation(
47    plan: &RelationLoadPlan,
48    selected_plan: RelationTopNExecutionPlan,
49    parent_count: usize,
50) -> crate::RuntimeOperation {
51    let configured_threshold = plan
52        .query
53        .as_ref()
54        .and_then(|query| query.top_n_probe_parent_threshold)
55        .unwrap_or(0);
56    let per_parent_limit = plan
57        .query
58        .as_ref()
59        .and_then(|query| query.slice)
60        .and_then(|slice| slice.limit)
61        .unwrap_or(0);
62    crate::RuntimeOperation::new(
63        "relation_load",
64        format!("{}.{}", plan.parent_entity, plan.path),
65    )
66    .attribute("teaql.entity.type", plan.parent_entity.clone())
67    .attribute("teaql.relation.name", plan.path.clone())
68    .attribute("teaql.relation.parent_count", parent_count)
69    .attribute("teaql.relation.per_parent_limit", per_parent_limit as usize)
70    .attribute(
71        "teaql.relation.configured_probe_threshold",
72        configured_threshold,
73    )
74    .attribute(
75        "teaql.relation.selected_plan",
76        match selected_plan {
77            RelationTopNExecutionPlan::Window => "WINDOW",
78            RelationTopNExecutionPlan::BoundedProbes => "BOUNDED_PROBES",
79        },
80    )
81    .attribute(
82        "teaql.relation.probe_count",
83        if selected_plan == RelationTopNExecutionPlan::BoundedProbes {
84            parent_count
85        } else {
86            0
87        },
88    )
89}
90
91fn relation_top_n_execution_plan(
92    capabilities: &teaql_data_service::DataServiceCapabilities,
93    plan: &RelationLoadPlan,
94    parent_count: usize,
95) -> RelationTopNExecutionPlan {
96    let Some(query) = plan.query.as_ref().filter(|_| plan.many) else {
97        return RelationTopNExecutionPlan::Window;
98    };
99    if query.slice.is_none() {
100        return RelationTopNExecutionPlan::Window;
101    }
102    let use_probes = match query.top_n_probe_parent_threshold {
103        Some(0) => false,
104        Some(threshold) => parent_count <= threshold,
105        None => capabilities.small_parent_relation_probes,
106    };
107    if use_probes {
108        RelationTopNExecutionPlan::BoundedProbes
109    } else {
110        RelationTopNExecutionPlan::Window
111    }
112}
113
114impl<'a, E> EntityDataService<'a, E>
115where
116    E: teaql_data_service::QueryExecutor + teaql_data_service::MutationExecutor + Send + Sync,
117{
118    pub fn relation_loads(&self) -> Vec<String> {
119        self.behavior()
120            .map(|behavior| behavior.relation_loads(self.data_service.metadata.context))
121            .unwrap_or_default()
122    }
123
124    pub fn relation_plans(&self) -> Result<Vec<RelationLoadPlan>, RuntimeError> {
125        self.build_relation_plans(&self.entity, &self.relation_loads())
126    }
127
128    pub fn relation_query(
129        &self,
130        relation_name: &str,
131        parent_rows: &[CompactRow],
132    ) -> Result<SelectQuery, RuntimeError> {
133        let plan = self
134            .relation_plans()?
135            .into_iter()
136            .find(|plan| plan.relation_name == relation_name)
137            .ok_or_else(|| RuntimeError::MissingRelation {
138                entity: self.entity.clone(),
139                relation: relation_name.to_owned(),
140            })?;
141        Ok(self.query_for_plan(&plan, parent_rows))
142    }
143
144    pub(crate) async fn enhance_relations_internal(
145        &self,
146        parent_rows: &mut [CompactRow],
147    ) -> Result<(), DataServiceError<E::Error>> {
148        let plans = self.relation_plans().map_err(DataServiceError::Runtime)?;
149        for plan in plans {
150            self.enhance_plan(parent_rows, &plan).await?;
151        }
152        Ok(())
153    }
154
155    pub(crate) async fn enhance_query_relations_internal(
156        &self,
157        parent_rows: &mut [CompactRow],
158        query: &SelectQuery,
159    ) -> Result<(), DataServiceError<E::Error>> {
160        let plans = self
161            .build_relation_plans_from_loads(&query.entity, &query.relations)
162            .map_err(DataServiceError::Runtime)?;
163        let mut parent_trace = self.trace_context.clone();
164        parent_trace.extend(query.trace_chain.clone());
165        let traced = self
166            .scoped_data_service_internal(query.entity.clone())
167            .with_trace_context(parent_trace);
168        for plan in plans {
169            traced.enhance_plan(parent_rows, &plan).await?;
170        }
171        Ok(())
172    }
173
174    pub(crate) async fn hydrate_flat_plans_internal(
175        &self,
176        parent_rows: &mut [CompactRow],
177        plans: &[RelationLoadPlan],
178        root: &crate::EntityRuntimeState,
179        graph: &mut crate::EntityGraphBuilder,
180    ) -> Result<(), DataServiceError<E::Error>> {
181        for plan in plans {
182            self.hydrate_flat_plan(parent_rows, plan, root, graph)
183                .await?;
184        }
185        Ok(())
186    }
187
188    pub(crate) async fn hydrate_compact_flat_plans_internal(
189        &self,
190        parent_rows: &[CompactRow],
191        plans: &[RelationLoadPlan],
192        root: &crate::EntityRuntimeState,
193        graph: &mut crate::EntityGraphBuilder,
194    ) -> Result<(), DataServiceError<E::Error>> {
195        for plan in plans {
196            if plan.children.is_empty() {
197                self.hydrate_compact_flat_leaf(parent_rows, plan, root, graph)
198                    .await?;
199            } else {
200                self.hydrate_compact_flat_plan(parent_rows, plan, root, graph)
201                    .await?;
202            }
203        }
204        Ok(())
205    }
206
207    pub(crate) fn flat_relation_plans(
208        &self,
209        query: &SelectQuery,
210    ) -> Result<Option<(Vec<RelationLoadPlan>, Vec<RelationLoadPlan>)>, RuntimeError> {
211        let context = self.data_service.metadata.context;
212        let query_plans = self.build_relation_plans_from_loads(&query.entity, &query.relations)?;
213        let behavior_plans = self.relation_plans()?;
214
215        fn supported(context: &crate::UserContext, plan: &RelationLoadPlan) -> bool {
216            context.has_entity_graph_decoder(&plan.target_entity)
217                && plan.children.iter().all(|child| supported(context, child))
218        }
219
220        let all_supported = query_plans
221            .iter()
222            .chain(behavior_plans.iter())
223            .all(|plan| supported(context, plan));
224        Ok(all_supported.then_some((query_plans, behavior_plans)))
225    }
226
227    pub(crate) fn enhance_relation_aggregates_internal<'b>(
228        &'b self,
229        parent_rows: &'b mut [CompactRow],
230        relation_aggregates: &'b [RelationAggregate],
231        parent_cache_options: Option<teaql_core::AggregationCacheOptions>,
232        parent_trace_chain: &'b [teaql_core::TraceNode],
233    ) -> std::pin::Pin<
234        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
235    > {
236        Box::pin(async move {
237            for aggregate in relation_aggregates {
238                self.enhance_relation_aggregate(
239                    parent_rows,
240                    aggregate,
241                    parent_cache_options,
242                    parent_trace_chain,
243                )
244                .await?;
245            }
246            Ok(())
247        })
248    }
249
250    pub(crate) fn enhance_object_group_bys_internal<'b>(
251        &'b self,
252        rows: &'b mut [CompactRow],
253        object_group_bys: &'b [ObjectGroupBy],
254        parent_trace_chain: &'b [teaql_core::TraceNode],
255    ) -> std::pin::Pin<
256        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
257    > {
258        Box::pin(async move {
259            for group_by in object_group_bys {
260                let ids = rows
261                    .iter()
262                    .filter_map(|row| row.get(&group_by.storage_field).cloned())
263                    .collect::<Vec<_>>();
264                if ids.is_empty() {
265                    continue;
266                }
267                let mut query = group_by.query.clone();
268                ensure_projection(&mut query, "id");
269                query = query.and_filter(Expr::in_list("id", ids));
270                let object_rows = self
271                    .scoped_data_service_internal(query.entity.clone())
272                    .with_trace_context(parent_trace_chain.to_vec())
273                    .fetch_compact_all_internal(query)
274                    .await?
275                    .into_iter()
276                    .filter_map(|row| {
277                        row.get("id")
278                            .cloned()
279                            .map(|id| (graph_identity_key(&id), row))
280                    })
281                    .collect::<BTreeMap<_, _>>();
282                for row in rows.iter_mut() {
283                    if let Some(key) = row.get(&group_by.storage_field).map(graph_identity_key) {
284                        let value = object_rows
285                            .get(&key)
286                            .cloned()
287                            .map(|row| Value::object(row.into_map()))
288                            .unwrap_or(Value::Null);
289                        row.insert(group_by.property_name.clone(), value);
290                    }
291                }
292            }
293            Ok(())
294        })
295    }
296
297    pub(crate) fn enhance_child_queries_internal<'b>(
298        &'b self,
299        rows: &'b mut [CompactRow],
300        child_queries: &'b [SelectQuery],
301        parent_trace_chain: &'b [teaql_core::TraceNode],
302    ) -> std::pin::Pin<
303        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
304    > {
305        Box::pin(async move {
306            for child_query in child_queries {
307                let ids = rows
308                    .iter()
309                    .filter_map(|row| row.get("id").cloned())
310                    .collect::<Vec<_>>();
311                if ids.is_empty() {
312                    continue;
313                }
314                let mut query = child_query.clone();
315                ensure_projection(&mut query, "id");
316                query = query.and_filter(Expr::in_list("id", ids));
317                let child_rows = self
318                    .scoped_data_service_internal(query.entity.clone())
319                    .with_trace_context(parent_trace_chain.to_vec())
320                    .fetch_compact_all_internal(query)
321                    .await?
322                    .into_iter()
323                    .filter_map(|row| {
324                        row.get("id")
325                            .cloned()
326                            .map(|id| (graph_identity_key(&id), row))
327                    })
328                    .collect::<BTreeMap<_, _>>();
329                for row in rows.iter_mut() {
330                    if let Some(key) = row.get("id").map(graph_identity_key) {
331                        if let Some(child) = child_rows.get(&key) {
332                            row.extend(child.clone());
333                        }
334                    }
335                }
336            }
337            Ok(())
338        })
339    }
340
341    async fn enhance_relation_aggregate(
342        &self,
343        parent_rows: &mut [CompactRow],
344        aggregate: &RelationAggregate,
345        parent_cache_options: Option<teaql_core::AggregationCacheOptions>,
346        parent_trace_chain: &[teaql_core::TraceNode],
347    ) -> Result<(), DataServiceError<E::Error>> {
348        let plan = self
349            .build_relation_plans_from_loads(
350                &self.entity,
351                &[RelationLoad::with_query(
352                    aggregate.relation_name.clone(),
353                    aggregate.query.clone(),
354                )],
355            )
356            .map_err(DataServiceError::Runtime)?
357            .into_iter()
358            .next()
359            .ok_or_else(|| {
360                DataServiceError::Runtime(RuntimeError::MissingRelation {
361                    entity: self.entity.clone(),
362                    relation: aggregate.relation_name.clone(),
363                })
364            })?;
365
366        let ids = parent_rows
367            .iter()
368            .filter_map(|row| row.get(&plan.local_key).cloned())
369            .collect::<Vec<_>>();
370        if ids.is_empty() {
371            attach_empty_relation_aggregate(parent_rows, &aggregate.alias, aggregate.single_result);
372            return Ok(());
373        }
374
375        let child_repo = self.relation_child_repo(&plan);
376        let mut query = aggregate.query.clone();
377        query.entity = plan.target_entity.clone();
378        if query.aggregation_cache.is_none() {
379            if let Some(options) = parent_cache_options.filter(|options| options.propagate) {
380                query.aggregation_cache = Some(teaql_core::AggregationCacheOptions::enabled(
381                    options.propagate_cache_expired_millis,
382                ));
383            }
384        }
385        query.projection.clear();
386        query.expr_projection.clear();
387        query.order_by.clear();
388        query.slice = None;
389        query.relations.clear();
390        if query.aggregates.is_empty() {
391            let alias = aggregate_alias(aggregate.single_result, &aggregate.alias);
392            query = query.aggregate(Aggregate::count(alias));
393        }
394        if !query
395            .group_by
396            .iter()
397            .any(|field| field == &plan.foreign_key)
398        {
399            query = query.group_by(plan.foreign_key.clone());
400        }
401        query = query.and_filter(Expr::in_list(plan.foreign_key.clone(), ids));
402
403        let mut chain = parent_trace_chain.to_vec();
404        chain.push(teaql_core::TraceNode {
405            kind: teaql_core::TraceKind::Relation,
406            entity_type: query.entity.clone(),
407            entity_id: None,
408            comment: aggregate.alias.clone(),
409        });
410
411        let mut aggregate_rows = child_repo
412            .with_trace_context(chain)
413            .fetch_compact_all_internal(query)
414            .await?;
415        let foreign_key_column = self
416            .data_service
417            .metadata
418            .context
419            .entity(&plan.target_entity)
420            .and_then(|descriptor| {
421                descriptor
422                    .properties
423                    .iter()
424                    .find(|property| property.name == plan.foreign_key)
425                    .map(|property| property.column_name.clone())
426            });
427        if let Some(foreign_key_column) =
428            foreign_key_column.filter(|column| column != &plan.foreign_key)
429        {
430            for row in &mut aggregate_rows {
431                if !row.contains_key(&plan.foreign_key) {
432                    if let Some(value) = row.remove(&foreign_key_column) {
433                        row.insert(plan.foreign_key.clone(), value);
434                    }
435                }
436            }
437        }
438        attach_relation_aggregate_rows(parent_rows, &plan, aggregate, aggregate_rows);
439        Ok(())
440    }
441
442    fn build_relation_plans(
443        &self,
444        entity: &str,
445        loads: &[String],
446    ) -> Result<Vec<RelationLoadPlan>, RuntimeError> {
447        let descriptor = self.data_service.metadata.context.require_entity(entity)?;
448        let mut grouped: BTreeMap<String, Vec<String>> = BTreeMap::new();
449        for load in loads {
450            match load.split_once('.') {
451                Some((head, tail)) => {
452                    grouped
453                        .entry(head.to_owned())
454                        .or_default()
455                        .push(tail.to_owned());
456                }
457                None => {
458                    grouped.entry(load.clone()).or_default();
459                }
460            }
461        }
462
463        grouped
464            .into_iter()
465            .map(|(name, child_loads)| {
466                let relation = descriptor.relation_by_name(&name).ok_or_else(|| {
467                    RuntimeError::MissingRelation {
468                        entity: entity.to_owned(),
469                        relation: name.clone(),
470                    }
471                })?;
472                let child_repo = self.scoped_data_service_internal(relation.target_entity.clone());
473                let children =
474                    child_repo.build_relation_plans(&relation.target_entity, &child_loads)?;
475                Ok(RelationLoadPlan {
476                    parent_entity: entity.to_owned(),
477                    relation_name: relation.name.clone(),
478                    path: relation.name.clone(),
479                    target_entity: relation.target_entity.clone(),
480                    local_key: relation.local_key.clone(),
481                    foreign_key: relation.foreign_key.clone(),
482                    many: relation.many,
483                    query: None,
484                    children,
485                })
486            })
487            .collect()
488    }
489
490    fn build_relation_plans_from_loads(
491        &self,
492        entity: &str,
493        loads: &[RelationLoad],
494    ) -> Result<Vec<RelationLoadPlan>, RuntimeError> {
495        let descriptor = self.data_service.metadata.context.require_entity(entity)?;
496        loads
497            .iter()
498            .map(|load| {
499                let relation = descriptor.relation_by_name(&load.name).ok_or_else(|| {
500                    RuntimeError::MissingRelation {
501                        entity: entity.to_owned(),
502                        relation: load.name.clone(),
503                    }
504                })?;
505                let relation_query = load.query.as_deref().cloned();
506                let child_loads = relation_query
507                    .as_ref()
508                    .map(|query| query.relations.as_slice())
509                    .unwrap_or_default();
510                let child_repo = self.scoped_data_service_internal(relation.target_entity.clone());
511                let children = child_repo
512                    .build_relation_plans_from_loads(&relation.target_entity, child_loads)?;
513                Ok(RelationLoadPlan {
514                    parent_entity: entity.to_owned(),
515                    relation_name: relation.name.clone(),
516                    path: relation.name.clone(),
517                    target_entity: relation.target_entity.clone(),
518                    local_key: relation.local_key.clone(),
519                    foreign_key: relation.foreign_key.clone(),
520                    many: relation.many,
521                    query: relation_query,
522                    children,
523                })
524            })
525            .collect()
526    }
527    fn enhance_plan<'b>(
528        &'b self,
529        parent_rows: &'b mut [CompactRow],
530        plan: &'b RelationLoadPlan,
531    ) -> std::pin::Pin<
532        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
533    > {
534        Box::pin(async move {
535            let capabilities =
536                teaql_data_service::DataServiceExecutor::capabilities(self.data_service.executor);
537            let parent_count = unique_relation_values(parent_rows, &plan.local_key).len();
538            let selected_plan = relation_top_n_execution_plan(&capabilities, plan, parent_count);
539            let scope = self.data_service.metadata.context.start_runtime_operation(
540                relation_top_n_operation(plan, selected_plan, parent_count),
541            );
542            let result = scope
543                .run(async {
544                    let child_repo = self.relation_child_repo(plan);
545                    let mut child_rows = self
546                        .fetch_relation_rows(&child_repo, plan, parent_rows, false)
547                        .await?;
548                    for child in &mut child_rows {
549                        child.remove(teaql_core::PARTITION_RANK_PROPERTY);
550                    }
551                    self.attach_relation_rows(parent_rows, plan, child_rows);
552
553                    if !plan.children.is_empty() {
554                        for parent in parent_rows.iter_mut() {
555                            match parent.get_mut(&plan.relation_name) {
556                                Some(Value::Object(child)) => {
557                                    child_repo
558                                        .enhance_child_record(child, &plan.children)
559                                        .await?;
560                                }
561                                Some(Value::List(values)) => {
562                                    for value in values.iter_mut() {
563                                        if let Value::Object(child) = value {
564                                            child_repo
565                                                .enhance_child_record(child, &plan.children)
566                                                .await?;
567                                        }
568                                    }
569                                }
570                                _ => {}
571                            }
572                        }
573                    }
574                    Ok(())
575                })
576                .await;
577            match &result {
578                Ok(_) => scope.success(BTreeMap::from([(
579                    "teaql.result.cardinality".to_owned(),
580                    crate::RuntimeAttributeValue::Integer(parent_rows.len() as i64),
581                )])),
582                Err(_) => scope.failure("relation_load_error"),
583            }
584            result
585        })
586    }
587
588    fn hydrate_flat_plan<'b>(
589        &'b self,
590        parent_rows: &'b mut [CompactRow],
591        plan: &'b RelationLoadPlan,
592        root: &'b crate::EntityRuntimeState,
593        graph: &'b mut crate::EntityGraphBuilder,
594    ) -> std::pin::Pin<
595        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
596    > {
597        Box::pin(async move {
598            let child_repo = self.relation_child_repo(plan);
599            let mut child_rows = self
600                .fetch_relation_rows(&child_repo, plan, parent_rows, false)
601                .await?;
602            for child in &mut child_rows {
603                child.remove(teaql_core::PARTITION_RANK_PROPERTY);
604            }
605
606            // Hydrate descendants while the rows are still owned by this level. Nothing is
607            // embedded into a parent row: every relation is published directly into the
608            // shared, immutable identity graph.
609            for child_plan in &plan.children {
610                child_repo
611                    .hydrate_flat_plan(&mut child_rows, child_plan, root, graph)
612                    .await?;
613            }
614
615            let inverse_relation = self
616                .data_service
617                .metadata
618                .context
619                .entity(&plan.target_entity)
620                .and_then(|descriptor| {
621                    descriptor.relations.iter().find(|relation| {
622                        relation.target_entity == plan.parent_entity
623                            && relation.local_key == plan.foreign_key
624                            && relation.foreign_key == plan.local_key
625                    })
626                })
627                .map(|relation| (relation.name.clone(), relation.many));
628
629            let mut buckets: BTreeMap<FlatIdentityKey, Vec<CompactRow>> = BTreeMap::new();
630            for child in child_rows {
631                if let Some(key) = child.get(&plan.foreign_key) {
632                    buckets
633                        .entry(FlatIdentityKey::from_value(key))
634                        .or_default()
635                        .push(child);
636                }
637            }
638
639            let context = self.data_service.metadata.context;
640            for parent in parent_rows {
641                let Some(local_value) = parent.get(&plan.local_key) else {
642                    continue;
643                };
644                let related = buckets
645                    .remove(&FlatIdentityKey::from_value(local_value))
646                    .unwrap_or_default();
647
648                if let Some((inverse_name, inverse_many)) = &inverse_relation {
649                    let parent_record = parent.clone();
650                    for child in &related {
651                        let Some(child_id) = child.get("id").and_then(Value::try_u64) else {
652                            continue;
653                        };
654                        if *inverse_many {
655                            context
656                                .decode_compact_entity_list_into_graph(
657                                    &plan.parent_entity,
658                                    vec![parent_record.clone()],
659                                    root,
660                                    graph,
661                                    &plan.target_entity,
662                                    child_id,
663                                    inverse_name,
664                                )
665                                .map_err(DataServiceError::Entity)?;
666                        } else {
667                            context
668                                .decode_compact_entity_option_into_graph(
669                                    &plan.parent_entity,
670                                    vec![parent_record.clone()],
671                                    root,
672                                    graph,
673                                    &plan.target_entity,
674                                    child_id,
675                                    inverse_name,
676                                )
677                                .map_err(DataServiceError::Entity)?;
678                        }
679                    }
680                }
681
682                if plan.many || plan.local_key == "id" {
683                    let owner_id = parent.get("id").and_then(Value::try_u64).ok_or_else(|| {
684                        DataServiceError::Entity(teaql_core::EntityError::new(
685                            &plan.parent_entity,
686                            "loaded reverse relation owner is missing its u64 id",
687                        ))
688                    })?;
689                    if plan.many {
690                        context
691                            .decode_compact_entity_list_into_graph(
692                                &plan.target_entity,
693                                related,
694                                root,
695                                graph,
696                                &plan.parent_entity,
697                                owner_id,
698                                &plan.relation_name,
699                            )
700                            .map_err(DataServiceError::Entity)?;
701                    } else {
702                        context
703                            .decode_compact_entity_option_into_graph(
704                                &plan.target_entity,
705                                related,
706                                root,
707                                graph,
708                                &plan.parent_entity,
709                                owner_id,
710                                &plan.relation_name,
711                            )
712                            .map_err(DataServiceError::Entity)?;
713                    }
714                } else if related.is_empty() {
715                    // Forward optional relations use the scalar loaded marker to distinguish a
716                    // loaded null from a relation that was never requested.
717                    parent.insert(plan.relation_name.clone(), Value::Null);
718                } else {
719                    for child in related {
720                        context
721                            .decode_compact_entity_into_graph(
722                                &plan.target_entity,
723                                child,
724                                root,
725                                graph,
726                            )
727                            .map_err(DataServiceError::Entity)?;
728                    }
729                }
730            }
731            Ok(())
732        })
733    }
734
735    fn hydrate_compact_flat_plan<'b>(
736        &'b self,
737        parent_rows: &'b [CompactRow],
738        plan: &'b RelationLoadPlan,
739        root: &'b crate::EntityRuntimeState,
740        graph: &'b mut crate::EntityGraphBuilder,
741    ) -> std::pin::Pin<
742        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
743    > {
744        Box::pin(async move {
745            let child_repo = self.relation_child_repo(plan);
746            let child_rows = self
747                .fetch_relation_rows(&child_repo, plan, parent_rows, true)
748                .await?;
749
750            for child_plan in &plan.children {
751                if child_plan.children.is_empty() {
752                    child_repo
753                        .hydrate_compact_flat_leaf(&child_rows, child_plan, root, graph)
754                        .await?;
755                } else {
756                    child_repo
757                        .hydrate_compact_flat_plan(&child_rows, child_plan, root, graph)
758                        .await?;
759                }
760            }
761
762            self.install_compact_flat_relation(parent_rows, plan, child_rows, root, graph)
763        })
764    }
765
766    fn hydrate_compact_flat_leaf<'b>(
767        &'b self,
768        parent_rows: &'b [CompactRow],
769        plan: &'b RelationLoadPlan,
770        root: &'b crate::EntityRuntimeState,
771        graph: &'b mut crate::EntityGraphBuilder,
772    ) -> std::pin::Pin<
773        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
774    > {
775        Box::pin(async move {
776            let child_repo = self.relation_child_repo(plan);
777            let child_rows = self
778                .fetch_relation_rows(&child_repo, plan, parent_rows, true)
779                .await?;
780            self.install_compact_flat_relation(parent_rows, plan, child_rows, root, graph)
781        })
782    }
783
784    fn install_compact_flat_relation(
785        &self,
786        parent_rows: &[CompactRow],
787        plan: &RelationLoadPlan,
788        child_rows: Vec<CompactRow>,
789        root: &crate::EntityRuntimeState,
790        graph: &mut crate::EntityGraphBuilder,
791    ) -> Result<(), DataServiceError<E::Error>> {
792        // A forward to-one relation only needs its fetched targets installed in the shared
793        // identity table. Building owner buckets and then removing them one parent at a time
794        // creates a map and one Vec per distinct target without adding information.
795        if !plan.many && plan.local_key != "id" {
796            return self
797                .data_service
798                .metadata
799                .context
800                .decode_compact_entity_batch_into_graph(
801                    &plan.target_entity,
802                    child_rows,
803                    root,
804                    graph,
805                )
806                .map_err(DataServiceError::Entity);
807        }
808
809        let mut buckets: BTreeMap<FlatIdentityKey, Vec<CompactRow>> = BTreeMap::new();
810        for child in child_rows {
811            if let Some(key) = child.get(&plan.foreign_key) {
812                buckets
813                    .entry(FlatIdentityKey::from_value(key))
814                    .or_default()
815                    .push(child);
816            }
817        }
818
819        let context = self.data_service.metadata.context;
820        for parent in parent_rows {
821            let Some(local_value) = parent.get(&plan.local_key) else {
822                continue;
823            };
824            let related = buckets
825                .remove(&FlatIdentityKey::from_value(local_value))
826                .unwrap_or_default();
827
828            if plan.many || plan.local_key == "id" {
829                let owner_id = parent.get("id").and_then(Value::try_u64).ok_or_else(|| {
830                    DataServiceError::Entity(teaql_core::EntityError::new(
831                        &plan.parent_entity,
832                        "loaded reverse relation owner is missing its u64 id",
833                    ))
834                })?;
835                if plan.many {
836                    context
837                        .decode_compact_entity_list_into_graph(
838                            &plan.target_entity,
839                            related,
840                            root,
841                            graph,
842                            &plan.parent_entity,
843                            owner_id,
844                            &plan.relation_name,
845                        )
846                        .map_err(DataServiceError::Entity)?;
847                } else {
848                    context
849                        .decode_compact_entity_option_into_graph(
850                            &plan.target_entity,
851                            related,
852                            root,
853                            graph,
854                            &plan.parent_entity,
855                            owner_id,
856                            &plan.relation_name,
857                        )
858                        .map_err(DataServiceError::Entity)?;
859                }
860            } else {
861                for child in related {
862                    context
863                        .decode_compact_entity_into_graph(&plan.target_entity, child, root, graph)
864                        .map_err(DataServiceError::Entity)?;
865                }
866            }
867        }
868        Ok(())
869    }
870
871    fn enhance_child_record<'b>(
872        &'b self,
873        child: &'b mut std::collections::BTreeMap<String, Value>,
874        plans: &'b [RelationLoadPlan],
875    ) -> std::pin::Pin<
876        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
877    > {
878        Box::pin(async move {
879            for plan in plans {
880                let mut row = CompactRow::from_map(std::mem::take(child));
881                self.enhance_plan(slice::from_mut(&mut row), plan).await?;
882                *child = row.into_map();
883            }
884            Ok(())
885        })
886    }
887
888    fn relation_child_repo(&self, plan: &RelationLoadPlan) -> EntityDataService<'a, E> {
889        let mut trace = self.trace_context.clone();
890        trace.push(teaql_core::TraceNode::typed(
891            teaql_core::TraceKind::Relation,
892            plan.relation_name.clone(),
893            None,
894            format!("{}.{}", plan.parent_entity, plan.relation_name),
895        ));
896        self.scoped_data_service_internal(plan.target_entity.clone())
897            .with_trace_context(trace)
898    }
899
900    fn query_for_plan(&self, plan: &RelationLoadPlan, parent_rows: &[CompactRow]) -> SelectQuery {
901        // Relation identities are a set. Keeping one value per normalized identity avoids
902        // compiling and binding the same foreign key once for every parent row (a common shape
903        // for pages containing many rows that share a small reference table).
904        let ids = unique_relation_values(parent_rows, &plan.local_key);
905
906        let mut query = plan
907            .query
908            .clone()
909            .unwrap_or_else(|| SelectQuery::new(plan.target_entity.clone()));
910        query.entity = plan.target_entity.clone();
911        ensure_projection(&mut query, &plan.foreign_key);
912        for child in &plan.children {
913            ensure_projection(&mut query, &child.local_key);
914        }
915        self.ensure_stable_top_n_order(plan, &mut query);
916        if !ids.is_empty() {
917            query = query.and_filter(Expr::in_list(plan.foreign_key.clone(), ids));
918        }
919        if query.slice.is_some() {
920            query.partition_by = Some(plan.foreign_key.clone());
921        }
922        query
923    }
924
925    async fn fetch_relation_rows(
926        &self,
927        child_repo: &EntityDataService<'a, E>,
928        plan: &RelationLoadPlan,
929        parent_rows: &[CompactRow],
930        compact: bool,
931    ) -> Result<Vec<CompactRow>, DataServiceError<E::Error>> {
932        let ids = unique_relation_values(parent_rows, &plan.local_key);
933        if ids.is_empty() {
934            return Ok(Vec::new());
935        }
936
937        let capabilities =
938            teaql_data_service::DataServiceExecutor::capabilities(self.data_service.executor);
939        let probe = relation_top_n_execution_plan(&capabilities, plan, ids.len())
940            == RelationTopNExecutionPlan::BoundedProbes;
941
942        if !probe {
943            let query = if compact {
944                self.query_for_compact_plan(plan, parent_rows)
945            } else {
946                self.query_for_plan(plan, parent_rows)
947            };
948            return child_repo.fetch_compact_all_internal(query).await;
949        }
950
951        // With execution metadata disabled, retain one semantic partition query and let an
952        // embedded provider execute its indexed parent probes behind one executor boundary.
953        // When metadata is enabled we intentionally keep one observable result per SQL probe.
954        let provider_owns_probe_batch = capabilities.small_parent_relation_probes
955            && plan
956                .query
957                .as_ref()
958                .is_none_or(|query| query.top_n_probe_parent_threshold.is_none());
959        if provider_owns_probe_batch && !self.data_service.metadata.capture_execution_metadata() {
960            let query = if compact {
961                self.query_for_compact_plan(plan, parent_rows)
962            } else {
963                self.query_for_plan(plan, parent_rows)
964            };
965            return child_repo.fetch_compact_all_internal(query).await;
966        }
967
968        let mut rows = Vec::new();
969        for id in ids {
970            let mut query = self.base_relation_query(plan);
971            query = query.and_filter(Expr::eq(plan.foreign_key.clone(), id));
972            // The slice now belongs to one parent, so no window partition is needed.
973            query.partition_by = None;
974            rows.extend(child_repo.fetch_compact_all_internal(query).await?);
975        }
976        Ok(rows)
977    }
978
979    fn base_relation_query(&self, plan: &RelationLoadPlan) -> SelectQuery {
980        let mut query = plan
981            .query
982            .clone()
983            .unwrap_or_else(|| SelectQuery::new(plan.target_entity.clone()));
984        query.entity = plan.target_entity.clone();
985        ensure_projection(&mut query, &plan.foreign_key);
986        for child in &plan.children {
987            ensure_projection(&mut query, &child.local_key);
988        }
989        self.ensure_stable_top_n_order(plan, &mut query);
990        query
991    }
992
993    fn ensure_stable_top_n_order(&self, plan: &RelationLoadPlan, query: &mut SelectQuery) {
994        if query.slice.is_none() {
995            return;
996        }
997        let Some(id_property) = self
998            .data_service
999            .metadata
1000            .entity(&plan.target_entity)
1001            .and_then(|entity| entity.id_property())
1002        else {
1003            return;
1004        };
1005        if !query
1006            .order_by
1007            .iter()
1008            .any(|order| order.expr.is_none() && order.field == id_property.name)
1009        {
1010            query.order_by.push(OrderBy::asc(id_property.name.clone()));
1011        }
1012    }
1013
1014    fn query_for_compact_plan(
1015        &self,
1016        plan: &RelationLoadPlan,
1017        parent_rows: &[CompactRow],
1018    ) -> SelectQuery {
1019        let ids = unique_relation_values(parent_rows, &plan.local_key);
1020        let mut query = plan
1021            .query
1022            .clone()
1023            .unwrap_or_else(|| SelectQuery::new(plan.target_entity.clone()));
1024        query.entity = plan.target_entity.clone();
1025        // The flat hydrator owns the relation tree and recursively loads each child plan into
1026        // one shared identity graph. Leaving nested relations on this single-layer query makes
1027        // fetch_compact_all_internal hydrate the same subtree first; the flat hydrator then
1028        // hydrates it again, causing an exponential number of duplicate relation queries.
1029        query.relations.clear();
1030        ensure_projection(&mut query, &plan.foreign_key);
1031        for child in &plan.children {
1032            ensure_projection(&mut query, &child.local_key);
1033        }
1034        self.ensure_stable_top_n_order(plan, &mut query);
1035        if !ids.is_empty() {
1036            query = query.and_filter(Expr::in_list(plan.foreign_key.clone(), ids));
1037        }
1038        if query.slice.is_some() {
1039            query.partition_by = Some(plan.foreign_key.clone());
1040        }
1041        query
1042    }
1043
1044    fn attach_relation_rows(
1045        &self,
1046        parent_rows: &mut [CompactRow],
1047        plan: &RelationLoadPlan,
1048        child_rows: Vec<CompactRow>,
1049    ) {
1050        let inverse_relation = self
1051            .data_service
1052            .metadata
1053            .context
1054            .entity(&plan.target_entity)
1055            .and_then(|descriptor| {
1056                descriptor.relations.iter().find(|relation| {
1057                    relation.target_entity == plan.parent_entity
1058                        && relation.local_key == plan.foreign_key
1059                        && relation.foreign_key == plan.local_key
1060                })
1061            })
1062            .map(|relation| (relation.name.clone(), relation.many));
1063
1064        let mut buckets: BTreeMap<String, Vec<CompactRow>> = BTreeMap::new();
1065        for child in child_rows.clone() {
1066            if let Some(key) = child.get(&plan.foreign_key) {
1067                buckets
1068                    .entry(graph_identity_key(key))
1069                    .or_default()
1070                    .push(child);
1071            }
1072        }
1073
1074        for parent in parent_rows.iter_mut() {
1075            let Some(local_value) = parent.get(&plan.local_key) else {
1076                continue;
1077            };
1078            let bucket_key = graph_identity_key(local_value);
1079            let related = buckets.get(&bucket_key).cloned().unwrap_or_default();
1080            let related = match &inverse_relation {
1081                Some((inverse_relation, inverse_many)) => {
1082                    let mut parent_object = parent.clone();
1083                    parent_object.remove(&plan.relation_name);
1084                    related
1085                        .into_iter()
1086                        .map(|mut child| {
1087                            match *inverse_many {
1088                                true => {
1089                                    if !child.contains_key(inverse_relation) {
1090                                        child.insert(
1091                                            inverse_relation.clone(),
1092                                            Value::List(Vec::new()),
1093                                        );
1094                                    }
1095                                    let entry = child
1096                                        .get_mut(inverse_relation)
1097                                        .expect("inverse relation was inserted immediately above");
1098                                    if let Value::List(list) = entry {
1099                                        list.push(Value::object(parent_object.clone().into_map()));
1100                                    }
1101                                }
1102                                false => {
1103                                    child.insert(
1104                                        inverse_relation.clone(),
1105                                        Value::object(parent_object.clone().into_map()),
1106                                    );
1107                                }
1108                            }
1109                            child
1110                        })
1111                        .collect::<Vec<_>>()
1112                }
1113                None => related,
1114            };
1115            match plan.many {
1116                true => {
1117                    parent.insert(
1118                        plan.relation_name.clone(),
1119                        Value::List(
1120                            related
1121                                .into_iter()
1122                                .map(|row| Value::object(row.into_map()))
1123                                .collect(),
1124                        ),
1125                    );
1126                }
1127                false => {
1128                    let value = related
1129                        .into_iter()
1130                        .next()
1131                        .map(|row| Value::object(row.into_map()))
1132                        .unwrap_or(Value::Null);
1133                    parent.insert(plan.relation_name.clone(), value);
1134                }
1135            }
1136        }
1137    }
1138}
1139
1140#[cfg(test)]
1141mod planner_tests {
1142    use super::*;
1143
1144    fn limited_many_plan() -> RelationLoadPlan {
1145        RelationLoadPlan {
1146            parent_entity: "Vendor".to_owned(),
1147            relation_name: "trips".to_owned(),
1148            path: "trips".to_owned(),
1149            target_entity: "Trip".to_owned(),
1150            local_key: "id".to_owned(),
1151            foreign_key: "vendor_id".to_owned(),
1152            many: true,
1153            query: Some(SelectQuery::new("Trip").order_desc("id").limit(10)),
1154            children: Vec::new(),
1155        }
1156    }
1157
1158    #[test]
1159    fn topn_001_002_003_004_plan_respects_default_threshold_and_sqlite_policy() {
1160        let plan = limited_many_plan();
1161        let mut capabilities = teaql_data_service::DataServiceCapabilities::default();
1162        assert_eq!(
1163            relation_top_n_execution_plan(&capabilities, &plan, 6),
1164            RelationTopNExecutionPlan::Window
1165        );
1166
1167        capabilities.small_parent_relation_probes = true;
1168        assert_eq!(
1169            relation_top_n_execution_plan(&capabilities, &plan, 10_000),
1170            RelationTopNExecutionPlan::BoundedProbes
1171        );
1172
1173        let mut threshold_plan = plan.clone();
1174        threshold_plan
1175            .query
1176            .as_mut()
1177            .unwrap()
1178            .top_n_probe_parent_threshold = Some(4);
1179        capabilities.small_parent_relation_probes = false;
1180        assert_eq!(
1181            relation_top_n_execution_plan(&capabilities, &threshold_plan, 4),
1182            RelationTopNExecutionPlan::BoundedProbes
1183        );
1184        assert_eq!(
1185            relation_top_n_execution_plan(&capabilities, &threshold_plan, 5),
1186            RelationTopNExecutionPlan::Window
1187        );
1188
1189        threshold_plan
1190            .query
1191            .as_mut()
1192            .unwrap()
1193            .top_n_probe_parent_threshold = Some(0);
1194        capabilities.small_parent_relation_probes = true;
1195        assert_eq!(
1196            relation_top_n_execution_plan(&capabilities, &threshold_plan, 1),
1197            RelationTopNExecutionPlan::Window
1198        );
1199    }
1200
1201    #[test]
1202    fn topn_010_plan_telemetry_is_safe_and_complete() {
1203        let mut plan = limited_many_plan();
1204        plan.query.as_mut().unwrap().top_n_probe_parent_threshold = Some(32);
1205        let operation =
1206            relation_top_n_operation(&plan, RelationTopNExecutionPlan::BoundedProbes, 6);
1207
1208        assert_eq!(operation.family, "relation_load");
1209        assert_eq!(
1210            operation.attributes.get("teaql.relation.selected_plan"),
1211            Some(&crate::RuntimeAttributeValue::String(
1212                "BOUNDED_PROBES".to_owned()
1213            ))
1214        );
1215        assert_eq!(
1216            operation.attributes.get("teaql.relation.parent_count"),
1217            Some(&crate::RuntimeAttributeValue::Integer(6))
1218        );
1219        assert_eq!(
1220            operation.attributes.get("teaql.relation.per_parent_limit"),
1221            Some(&crate::RuntimeAttributeValue::Integer(10))
1222        );
1223        assert_eq!(
1224            operation
1225                .attributes
1226                .get("teaql.relation.configured_probe_threshold"),
1227            Some(&crate::RuntimeAttributeValue::Integer(32))
1228        );
1229        assert_eq!(
1230            operation.attributes.get("teaql.relation.probe_count"),
1231            Some(&crate::RuntimeAttributeValue::Integer(6))
1232        );
1233        assert!(!operation.attributes.contains_key("teaql.entity.id"));
1234    }
1235}