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