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, RelationAggregate, RelationLoad,
6    SelectQuery, Value,
7};
8
9use crate::{DataServiceError, RuntimeError};
10
11use super::{helpers::*, EntityDataService, RelationLoadPlan};
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
29impl<'a, E> EntityDataService<'a, E>
30where
31    E: teaql_data_service::QueryExecutor
32        + teaql_data_service::MutationExecutor
33        + Send
34        + Sync
35        + 'static,
36{
37    pub fn relation_loads(&self) -> Vec<String> {
38        self.behavior()
39            .map(|behavior| behavior.relation_loads(self.data_service.metadata.context))
40            .unwrap_or_default()
41    }
42
43    pub fn relation_plans(&self) -> Result<Vec<RelationLoadPlan>, RuntimeError> {
44        self.build_relation_plans(&self.entity, &self.relation_loads())
45    }
46
47    pub fn relation_query(
48        &self,
49        relation_name: &str,
50        parent_rows: &[CompactRow],
51    ) -> Result<SelectQuery, RuntimeError> {
52        let plan = self
53            .relation_plans()?
54            .into_iter()
55            .find(|plan| plan.relation_name == relation_name)
56            .ok_or_else(|| RuntimeError::MissingRelation {
57                entity: self.entity.clone(),
58                relation: relation_name.to_owned(),
59            })?;
60        Ok(self.query_for_plan(&plan, parent_rows))
61    }
62
63    pub(crate) async fn enhance_relations_internal(
64        &self,
65        parent_rows: &mut [CompactRow],
66    ) -> Result<(), DataServiceError<E::Error>> {
67        let plans = self.relation_plans().map_err(DataServiceError::Runtime)?;
68        for plan in plans {
69            self.enhance_plan(parent_rows, &plan).await?;
70        }
71        Ok(())
72    }
73
74    pub(crate) async fn enhance_query_relations_internal(
75        &self,
76        parent_rows: &mut [CompactRow],
77        query: &SelectQuery,
78    ) -> Result<(), DataServiceError<E::Error>> {
79        let plans = self
80            .build_relation_plans_from_loads(&query.entity, &query.relations)
81            .map_err(DataServiceError::Runtime)?;
82        for plan in plans {
83            self.enhance_plan(parent_rows, &plan).await?;
84        }
85        Ok(())
86    }
87
88    pub(crate) async fn hydrate_flat_plans_internal(
89        &self,
90        parent_rows: &mut [CompactRow],
91        plans: &[RelationLoadPlan],
92        root: &crate::EntityRoot,
93        graph: &mut crate::EntityGraphBuilder,
94    ) -> Result<(), DataServiceError<E::Error>> {
95        for plan in plans {
96            self.hydrate_flat_plan(parent_rows, plan, root, graph)
97                .await?;
98        }
99        Ok(())
100    }
101
102    pub(crate) async fn hydrate_compact_flat_plans_internal(
103        &self,
104        parent_rows: &[CompactRow],
105        plans: &[RelationLoadPlan],
106        root: &crate::EntityRoot,
107        graph: &mut crate::EntityGraphBuilder,
108    ) -> Result<(), DataServiceError<E::Error>> {
109        for plan in plans {
110            self.hydrate_compact_flat_plan(parent_rows, plan, root, graph)
111                .await?;
112        }
113        Ok(())
114    }
115
116    pub(crate) fn flat_relation_plans(
117        &self,
118        query: &SelectQuery,
119    ) -> Result<Option<(Vec<RelationLoadPlan>, Vec<RelationLoadPlan>)>, RuntimeError> {
120        let context = self.data_service.metadata.context;
121        let query_plans = self.build_relation_plans_from_loads(&query.entity, &query.relations)?;
122        let behavior_plans = self.relation_plans()?;
123
124        fn supported(context: &crate::UserContext, plan: &RelationLoadPlan) -> bool {
125            context.has_entity_graph_decoder(&plan.target_entity)
126                && plan.children.iter().all(|child| supported(context, child))
127        }
128
129        Ok(query_plans
130            .iter()
131            .chain(behavior_plans.iter())
132            .all(|plan| supported(context, plan))
133            .then_some((query_plans, behavior_plans)))
134    }
135
136    pub(crate) fn enhance_relation_aggregates_internal<'b>(
137        &'b self,
138        parent_rows: &'b mut [CompactRow],
139        relation_aggregates: &'b [RelationAggregate],
140        parent_cache_options: Option<teaql_core::AggregationCacheOptions>,
141        parent_trace_chain: &'b [teaql_core::TraceNode],
142    ) -> std::pin::Pin<
143        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
144    > {
145        Box::pin(async move {
146            for aggregate in relation_aggregates {
147                self.enhance_relation_aggregate(
148                    parent_rows,
149                    aggregate,
150                    parent_cache_options,
151                    parent_trace_chain,
152                )
153                .await?;
154            }
155            Ok(())
156        })
157    }
158
159    pub(crate) fn enhance_object_group_bys_internal<'b>(
160        &'b self,
161        rows: &'b mut [CompactRow],
162        object_group_bys: &'b [ObjectGroupBy],
163        parent_trace_chain: &'b [teaql_core::TraceNode],
164    ) -> std::pin::Pin<
165        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
166    > {
167        Box::pin(async move {
168            for group_by in object_group_bys {
169                let ids = rows
170                    .iter()
171                    .filter_map(|row| row.get(&group_by.storage_field).cloned())
172                    .collect::<Vec<_>>();
173                if ids.is_empty() {
174                    continue;
175                }
176                let mut query = group_by.query.clone();
177                ensure_projection(&mut query, "id");
178                query = query.and_filter(Expr::in_list("id", ids));
179                let object_rows = self
180                    .scoped_data_service_internal(query.entity.clone())
181                    .with_trace_context(parent_trace_chain.to_vec())
182                    .fetch_compact_all_internal(query)
183                    .await?
184                    .into_iter()
185                    .filter_map(|row| {
186                        row.get("id")
187                            .cloned()
188                            .map(|id| (graph_identity_key(&id), row))
189                    })
190                    .collect::<BTreeMap<_, _>>();
191                for row in rows.iter_mut() {
192                    if let Some(key) = row.get(&group_by.storage_field).map(graph_identity_key) {
193                        let value = object_rows
194                            .get(&key)
195                            .cloned()
196                            .map(|row| Value::object(row.into_map()))
197                            .unwrap_or(Value::Null);
198                        row.insert(group_by.property_name.clone(), value);
199                    }
200                }
201            }
202            Ok(())
203        })
204    }
205
206    pub(crate) fn enhance_child_queries_internal<'b>(
207        &'b self,
208        rows: &'b mut [CompactRow],
209        child_queries: &'b [SelectQuery],
210        parent_trace_chain: &'b [teaql_core::TraceNode],
211    ) -> std::pin::Pin<
212        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
213    > {
214        Box::pin(async move {
215            for child_query in child_queries {
216                let ids = rows
217                    .iter()
218                    .filter_map(|row| row.get("id").cloned())
219                    .collect::<Vec<_>>();
220                if ids.is_empty() {
221                    continue;
222                }
223                let mut query = child_query.clone();
224                ensure_projection(&mut query, "id");
225                query = query.and_filter(Expr::in_list("id", ids));
226                let child_rows = self
227                    .scoped_data_service_internal(query.entity.clone())
228                    .with_trace_context(parent_trace_chain.to_vec())
229                    .fetch_compact_all_internal(query)
230                    .await?
231                    .into_iter()
232                    .filter_map(|row| {
233                        row.get("id")
234                            .cloned()
235                            .map(|id| (graph_identity_key(&id), row))
236                    })
237                    .collect::<BTreeMap<_, _>>();
238                for row in rows.iter_mut() {
239                    if let Some(key) = row.get("id").map(graph_identity_key) {
240                        if let Some(child) = child_rows.get(&key) {
241                            row.extend(child.clone());
242                        }
243                    }
244                }
245            }
246            Ok(())
247        })
248    }
249
250    async fn enhance_relation_aggregate(
251        &self,
252        parent_rows: &mut [CompactRow],
253        aggregate: &RelationAggregate,
254        parent_cache_options: Option<teaql_core::AggregationCacheOptions>,
255        parent_trace_chain: &[teaql_core::TraceNode],
256    ) -> Result<(), DataServiceError<E::Error>> {
257        let plan = self
258            .build_relation_plans_from_loads(
259                &self.entity,
260                &[RelationLoad::with_query(
261                    aggregate.relation_name.clone(),
262                    aggregate.query.clone(),
263                )],
264            )
265            .map_err(DataServiceError::Runtime)?
266            .into_iter()
267            .next()
268            .ok_or_else(|| {
269                DataServiceError::Runtime(RuntimeError::MissingRelation {
270                    entity: self.entity.clone(),
271                    relation: aggregate.relation_name.clone(),
272                })
273            })?;
274
275        let ids = parent_rows
276            .iter()
277            .filter_map(|row| row.get(&plan.local_key).cloned())
278            .collect::<Vec<_>>();
279        if ids.is_empty() {
280            attach_empty_relation_aggregate(parent_rows, &aggregate.alias, aggregate.single_result);
281            return Ok(());
282        }
283
284        let child_repo = self.scoped_data_service_internal(plan.target_entity.clone());
285        let mut query = aggregate.query.clone();
286        query.entity = plan.target_entity.clone();
287        if query.aggregation_cache.is_none() {
288            if let Some(options) = parent_cache_options.filter(|options| options.propagate) {
289                query.aggregation_cache = Some(teaql_core::AggregationCacheOptions::enabled(
290                    options.propagate_cache_expired_millis,
291                ));
292            }
293        }
294        query.projection.clear();
295        query.expr_projection.clear();
296        query.order_by.clear();
297        query.slice = None;
298        query.relations.clear();
299        if query.aggregates.is_empty() {
300            let alias = aggregate_alias(aggregate.single_result, &aggregate.alias);
301            query = query.aggregate(Aggregate::count(alias));
302        }
303        if !query
304            .group_by
305            .iter()
306            .any(|field| field == &plan.foreign_key)
307        {
308            query = query.group_by(plan.foreign_key.clone());
309        }
310        query = query.and_filter(Expr::in_list(plan.foreign_key.clone(), ids));
311
312        let mut chain = parent_trace_chain.to_vec();
313        chain.push(teaql_core::TraceNode {
314            entity_type: query.entity.clone(),
315            entity_id: None,
316            comment: aggregate.alias.clone(),
317        });
318
319        let mut aggregate_rows = child_repo
320            .with_trace_context(chain)
321                    .fetch_compact_all_internal(query)
322            .await?;
323        let foreign_key_column = self
324            .data_service
325            .metadata
326            .context
327            .entity(&plan.target_entity)
328            .and_then(|descriptor| {
329                descriptor
330                    .properties
331                    .iter()
332                    .find(|property| property.name == plan.foreign_key)
333                    .map(|property| property.column_name.clone())
334            });
335        if let Some(foreign_key_column) =
336            foreign_key_column.filter(|column| column != &plan.foreign_key)
337        {
338            for row in &mut aggregate_rows {
339                if !row.contains_key(&plan.foreign_key) {
340                    if let Some(value) = row.remove(&foreign_key_column) {
341                        row.insert(plan.foreign_key.clone(), value);
342                    }
343                }
344            }
345        }
346        attach_relation_aggregate_rows(parent_rows, &plan, aggregate, aggregate_rows);
347        Ok(())
348    }
349
350    fn build_relation_plans(
351        &self,
352        entity: &str,
353        loads: &[String],
354    ) -> Result<Vec<RelationLoadPlan>, RuntimeError> {
355        let descriptor = self.data_service.metadata.context.require_entity(entity)?;
356        let mut grouped: BTreeMap<String, Vec<String>> = BTreeMap::new();
357        for load in loads {
358            match load.split_once('.') {
359                Some((head, tail)) => {
360                    grouped
361                        .entry(head.to_owned())
362                        .or_default()
363                        .push(tail.to_owned());
364                }
365                None => {
366                    grouped.entry(load.clone()).or_default();
367                }
368            }
369        }
370
371        grouped
372            .into_iter()
373            .map(|(name, child_loads)| {
374                let relation = descriptor.relation_by_name(&name).ok_or_else(|| {
375                    RuntimeError::MissingRelation {
376                        entity: entity.to_owned(),
377                        relation: name.clone(),
378                    }
379                })?;
380                let child_repo = self.scoped_data_service_internal(relation.target_entity.clone());
381                let children =
382                    child_repo.build_relation_plans(&relation.target_entity, &child_loads)?;
383                Ok(RelationLoadPlan {
384                    parent_entity: entity.to_owned(),
385                    relation_name: relation.name.clone(),
386                    path: relation.name.clone(),
387                    target_entity: relation.target_entity.clone(),
388                    local_key: relation.local_key.clone(),
389                    foreign_key: relation.foreign_key.clone(),
390                    many: relation.many,
391                    query: None,
392                    children,
393                })
394            })
395            .collect()
396    }
397
398    fn build_relation_plans_from_loads(
399        &self,
400        entity: &str,
401        loads: &[RelationLoad],
402    ) -> Result<Vec<RelationLoadPlan>, RuntimeError> {
403        let descriptor = self.data_service.metadata.context.require_entity(entity)?;
404        loads
405            .iter()
406            .map(|load| {
407                let relation = descriptor.relation_by_name(&load.name).ok_or_else(|| {
408                    RuntimeError::MissingRelation {
409                        entity: entity.to_owned(),
410                        relation: load.name.clone(),
411                    }
412                })?;
413                let relation_query = load.query.as_deref().cloned();
414                let child_loads = relation_query
415                    .as_ref()
416                    .map(|query| query.relations.as_slice())
417                    .unwrap_or_default();
418                let child_repo = self.scoped_data_service_internal(relation.target_entity.clone());
419                let children = child_repo
420                    .build_relation_plans_from_loads(&relation.target_entity, child_loads)?;
421                Ok(RelationLoadPlan {
422                    parent_entity: entity.to_owned(),
423                    relation_name: relation.name.clone(),
424                    path: relation.name.clone(),
425                    target_entity: relation.target_entity.clone(),
426                    local_key: relation.local_key.clone(),
427                    foreign_key: relation.foreign_key.clone(),
428                    many: relation.many,
429                    query: relation_query,
430                    children,
431                })
432            })
433            .collect()
434    }
435    fn enhance_plan<'b>(
436        &'b self,
437        parent_rows: &'b mut [CompactRow],
438        plan: &'b RelationLoadPlan,
439    ) -> std::pin::Pin<
440        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
441    > {
442        Box::pin(async move {
443            let scope = self.data_service.metadata.context.start_runtime_operation(
444                crate::RuntimeOperation::new(
445                    "relation_load",
446                    format!("{}.{}", plan.parent_entity, plan.path),
447                )
448                .attribute("teaql.entity.type", plan.parent_entity.clone())
449                .attribute("teaql.relation.name", plan.path.clone()),
450            );
451            let result = scope
452                .run(async {
453                    let child_repo = self.scoped_data_service_internal(plan.target_entity.clone());
454                    let query = self.query_for_plan(plan, parent_rows);
455            let mut child_rows = child_repo.fetch_compact_all_internal(query).await?;
456                    for child in &mut child_rows {
457                        child.remove(teaql_core::PARTITION_RANK_PROPERTY);
458                    }
459                    self.attach_relation_rows(parent_rows, plan, child_rows);
460
461                    if !plan.children.is_empty() {
462                        for parent in parent_rows.iter_mut() {
463                            match parent.get_mut(&plan.relation_name) {
464                                Some(Value::Object(child)) => {
465                                    child_repo
466                                        .enhance_child_record(child, &plan.children)
467                                        .await?;
468                                }
469                                Some(Value::List(values)) => {
470                                    for value in values.iter_mut() {
471                                        if let Value::Object(child) = value {
472                                            child_repo
473                                                .enhance_child_record(child, &plan.children)
474                                                .await?;
475                                        }
476                                    }
477                                }
478                                _ => {}
479                            }
480                        }
481                    }
482                    Ok(())
483                })
484                .await;
485            match &result {
486                Ok(_) => scope.success(BTreeMap::from([(
487                    "teaql.result.cardinality".to_owned(),
488                    crate::RuntimeAttributeValue::Integer(parent_rows.len() as i64),
489                )])),
490                Err(_) => scope.failure("relation_load_error"),
491            }
492            result
493        })
494    }
495
496    fn hydrate_flat_plan<'b>(
497        &'b self,
498        parent_rows: &'b mut [CompactRow],
499        plan: &'b RelationLoadPlan,
500        root: &'b crate::EntityRoot,
501        graph: &'b mut crate::EntityGraphBuilder,
502    ) -> std::pin::Pin<
503        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
504    > {
505        Box::pin(async move {
506            let child_repo = self.scoped_data_service_internal(plan.target_entity.clone());
507            let query = self.query_for_plan(plan, parent_rows);
508            let mut child_rows = child_repo.fetch_compact_all_internal(query).await?;
509            for child in &mut child_rows {
510                child.remove(teaql_core::PARTITION_RANK_PROPERTY);
511            }
512
513            // Hydrate descendants while the rows are still owned by this level. Nothing is
514            // embedded into a parent row: every relation is published directly into the
515            // shared, immutable identity graph.
516            for child_plan in &plan.children {
517                child_repo
518                    .hydrate_flat_plan(&mut child_rows, child_plan, root, graph)
519                    .await?;
520            }
521
522            let inverse_relation = self
523                .data_service
524                .metadata
525                .context
526                .entity(&plan.target_entity)
527                .and_then(|descriptor| {
528                    descriptor.relations.iter().find(|relation| {
529                        relation.target_entity == plan.parent_entity
530                            && relation.local_key == plan.foreign_key
531                            && relation.foreign_key == plan.local_key
532                    })
533                })
534                .map(|relation| (relation.name.clone(), relation.many));
535
536            let mut buckets: BTreeMap<FlatIdentityKey, Vec<CompactRow>> = BTreeMap::new();
537            for child in child_rows {
538                if let Some(key) = child.get(&plan.foreign_key) {
539                    buckets
540                        .entry(FlatIdentityKey::from_value(key))
541                        .or_default()
542                        .push(child);
543                }
544            }
545
546            let context = self.data_service.metadata.context;
547            for parent in parent_rows {
548                let Some(local_value) = parent.get(&plan.local_key) else {
549                    continue;
550                };
551                let related = buckets
552                    .remove(&FlatIdentityKey::from_value(local_value))
553                    .unwrap_or_default();
554
555                if let Some((inverse_name, inverse_many)) = &inverse_relation {
556                    let parent_record = parent.clone();
557                    for child in &related {
558                        let Some(child_id) = child.get("id").and_then(Value::try_u64) else {
559                            continue;
560                        };
561                        if *inverse_many {
562                            context
563                                .decode_compact_entity_list_into_graph(
564                                    &plan.parent_entity,
565                                    vec![parent_record.clone()],
566                                    root,
567                                    graph,
568                                    &plan.target_entity,
569                                    child_id,
570                                    inverse_name,
571                                )
572                                .map_err(DataServiceError::Entity)?;
573                        } else {
574                            context
575                                .decode_compact_entity_option_into_graph(
576                                    &plan.parent_entity,
577                                    vec![parent_record.clone()],
578                                    root,
579                                    graph,
580                                    &plan.target_entity,
581                                    child_id,
582                                    inverse_name,
583                                )
584                                .map_err(DataServiceError::Entity)?;
585                        }
586                    }
587                }
588
589                if plan.many || plan.local_key == "id" {
590                    let owner_id = parent.get("id").and_then(Value::try_u64).ok_or_else(|| {
591                        DataServiceError::Entity(teaql_core::EntityError::new(
592                            &plan.parent_entity,
593                            "loaded reverse relation owner is missing its u64 id",
594                        ))
595                    })?;
596                    if plan.many {
597                        context
598                            .decode_compact_entity_list_into_graph(
599                                &plan.target_entity,
600                                related,
601                                root,
602                                graph,
603                                &plan.parent_entity,
604                                owner_id,
605                                &plan.relation_name,
606                            )
607                            .map_err(DataServiceError::Entity)?;
608                    } else {
609                        context
610                            .decode_compact_entity_option_into_graph(
611                                &plan.target_entity,
612                                related,
613                                root,
614                                graph,
615                                &plan.parent_entity,
616                                owner_id,
617                                &plan.relation_name,
618                            )
619                            .map_err(DataServiceError::Entity)?;
620                    }
621                } else if related.is_empty() {
622                    // Forward optional relations use the scalar loaded marker to distinguish a
623                    // loaded null from a relation that was never requested.
624                    parent.insert(plan.relation_name.clone(), Value::Null);
625                } else {
626                    for child in related {
627                        context
628                            .decode_compact_entity_into_graph(&plan.target_entity, child, root, graph)
629                            .map_err(DataServiceError::Entity)?;
630                    }
631                }
632            }
633            Ok(())
634        })
635    }
636
637    fn hydrate_compact_flat_plan<'b>(
638        &'b self,
639        parent_rows: &'b [CompactRow],
640        plan: &'b RelationLoadPlan,
641        root: &'b crate::EntityRoot,
642        graph: &'b mut crate::EntityGraphBuilder,
643    ) -> std::pin::Pin<
644        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
645    > {
646        Box::pin(async move {
647            let child_repo = self.scoped_data_service_internal(plan.target_entity.clone());
648            let query = self.query_for_compact_plan(plan, parent_rows);
649            let child_rows = child_repo.fetch_compact_all_internal(query).await?;
650
651            for child_plan in &plan.children {
652                child_repo
653                    .hydrate_compact_flat_plan(&child_rows, child_plan, root, graph)
654                    .await?;
655            }
656
657            let mut buckets: BTreeMap<FlatIdentityKey, Vec<CompactRow>> = BTreeMap::new();
658            for child in child_rows {
659                if let Some(key) = child.get(&plan.foreign_key) {
660                    buckets
661                        .entry(FlatIdentityKey::from_value(key))
662                        .or_default()
663                        .push(child);
664                }
665            }
666
667            let context = self.data_service.metadata.context;
668            for parent in parent_rows {
669                let Some(local_value) = parent.get(&plan.local_key) else {
670                    continue;
671                };
672                let related = buckets
673                    .remove(&FlatIdentityKey::from_value(local_value))
674                    .unwrap_or_default();
675
676                if plan.many || plan.local_key == "id" {
677                    let owner_id = parent.get("id").and_then(Value::try_u64).ok_or_else(|| {
678                        DataServiceError::Entity(teaql_core::EntityError::new(
679                            &plan.parent_entity,
680                            "loaded reverse relation owner is missing its u64 id",
681                        ))
682                    })?;
683                    if plan.many {
684                        context
685                            .decode_compact_entity_list_into_graph(
686                                &plan.target_entity,
687                                related,
688                                root,
689                                graph,
690                                &plan.parent_entity,
691                                owner_id,
692                                &plan.relation_name,
693                            )
694                            .map_err(DataServiceError::Entity)?;
695                    } else {
696                        context
697                            .decode_compact_entity_option_into_graph(
698                                &plan.target_entity,
699                                related,
700                                root,
701                                graph,
702                                &plan.parent_entity,
703                                owner_id,
704                                &plan.relation_name,
705                            )
706                            .map_err(DataServiceError::Entity)?;
707                    }
708                } else {
709                    for child in related {
710                        context
711                            .decode_compact_entity_into_graph(
712                                &plan.target_entity,
713                                child,
714                                root,
715                                graph,
716                            )
717                            .map_err(DataServiceError::Entity)?;
718                    }
719                }
720            }
721            Ok(())
722        })
723    }
724
725    fn enhance_child_record<'b>(
726        &'b self,
727        child: &'b mut std::collections::BTreeMap<String, Value>,
728        plans: &'b [RelationLoadPlan],
729    ) -> std::pin::Pin<
730        Box<dyn std::future::Future<Output = Result<(), DataServiceError<E::Error>>> + Send + 'b>,
731    > {
732        Box::pin(async move {
733            for plan in plans {
734                let mut row = CompactRow::from_map(std::mem::take(child));
735                self.enhance_plan(slice::from_mut(&mut row), plan).await?;
736                *child = row.into_map();
737            }
738            Ok(())
739        })
740    }
741
742    fn query_for_plan(&self, plan: &RelationLoadPlan, parent_rows: &[CompactRow]) -> SelectQuery {
743        // Relation identities are a set. Keeping one value per normalized identity avoids
744        // compiling and binding the same foreign key once for every parent row (a common shape
745        // for pages containing many rows that share a small reference table).
746        let ids = parent_rows
747            .iter()
748            .filter_map(|row| row.get(&plan.local_key).cloned())
749            .fold(BTreeMap::new(), |mut unique, value| {
750                unique
751                    .entry(FlatIdentityKey::from_value(&value))
752                    .or_insert(value);
753                unique
754            })
755            .into_values()
756            .collect::<Vec<_>>();
757
758        let mut query = plan
759            .query
760            .clone()
761            .unwrap_or_else(|| SelectQuery::new(plan.target_entity.clone()));
762        query.entity = plan.target_entity.clone();
763        ensure_projection(&mut query, &plan.foreign_key);
764        for child in &plan.children {
765            ensure_projection(&mut query, &child.local_key);
766        }
767        if !ids.is_empty() {
768            query = query.and_filter(Expr::in_list(plan.foreign_key.clone(), ids));
769        }
770        if query.slice.is_some() {
771            query.partition_by = Some(plan.foreign_key.clone());
772        }
773        query
774    }
775
776    fn query_for_compact_plan(
777        &self,
778        plan: &RelationLoadPlan,
779        parent_rows: &[CompactRow],
780    ) -> SelectQuery {
781        let ids = parent_rows
782            .iter()
783            .filter_map(|row| row.get(&plan.local_key).cloned())
784            .fold(BTreeMap::new(), |mut unique, value| {
785                unique
786                    .entry(FlatIdentityKey::from_value(&value))
787                    .or_insert(value);
788                unique
789            })
790            .into_values()
791            .collect::<Vec<_>>();
792        let mut query = plan
793            .query
794            .clone()
795            .unwrap_or_else(|| SelectQuery::new(plan.target_entity.clone()));
796        query.entity = plan.target_entity.clone();
797        ensure_projection(&mut query, &plan.foreign_key);
798        for child in &plan.children {
799            ensure_projection(&mut query, &child.local_key);
800        }
801        if !ids.is_empty() {
802            query = query.and_filter(Expr::in_list(plan.foreign_key.clone(), ids));
803        }
804        if query.slice.is_some() {
805            query.partition_by = Some(plan.foreign_key.clone());
806        }
807        query
808    }
809
810    fn attach_relation_rows(
811        &self,
812        parent_rows: &mut [CompactRow],
813        plan: &RelationLoadPlan,
814        child_rows: Vec<CompactRow>,
815    ) {
816        let inverse_relation = self
817            .data_service
818            .metadata
819            .context
820            .entity(&plan.target_entity)
821            .and_then(|descriptor| {
822                descriptor.relations.iter().find(|relation| {
823                    relation.target_entity == plan.parent_entity
824                        && relation.local_key == plan.foreign_key
825                        && relation.foreign_key == plan.local_key
826                })
827            })
828            .map(|relation| (relation.name.clone(), relation.many));
829
830        let mut buckets: BTreeMap<String, Vec<CompactRow>> = BTreeMap::new();
831        for child in child_rows.clone() {
832            if let Some(key) = child.get(&plan.foreign_key) {
833                buckets
834                    .entry(graph_identity_key(key))
835                    .or_default()
836                    .push(child);
837            }
838        }
839
840        for parent in parent_rows.iter_mut() {
841            let Some(local_value) = parent.get(&plan.local_key) else {
842                continue;
843            };
844            let bucket_key = graph_identity_key(local_value);
845            let related = buckets.get(&bucket_key).cloned().unwrap_or_default();
846            let related = match &inverse_relation {
847                Some((inverse_relation, inverse_many)) => {
848                    let mut parent_object = parent.clone();
849                    parent_object.remove(&plan.relation_name);
850                    related
851                        .into_iter()
852                        .map(|mut child| {
853                            match *inverse_many {
854                                true => {
855                                    if !child.contains_key(inverse_relation) {
856                                        child.insert(
857                                            inverse_relation.clone(),
858                                            Value::List(Vec::new()),
859                                        );
860                                    }
861                                    let entry = child.get_mut(inverse_relation).expect(
862                                        "inverse relation was inserted immediately above",
863                                    );
864                                    if let Value::List(list) = entry {
865                                        list.push(Value::object(
866                                            parent_object.clone().into_map(),
867                                        ));
868                                    }
869                                }
870                                false => {
871                                    child.insert(
872                                        inverse_relation.clone(),
873                                        Value::object(parent_object.clone().into_map()),
874                                    );
875                                }
876                            }
877                            child
878                        })
879                        .collect::<Vec<_>>()
880                }
881                None => related,
882            };
883            match plan.many {
884                true => {
885                    parent.insert(
886                        plan.relation_name.clone(),
887                        Value::List(
888                            related
889                                .into_iter()
890                                .map(|row| Value::object(row.into_map()))
891                                .collect(),
892                        ),
893                    );
894                }
895                false => {
896                    let value = related
897                        .into_iter()
898                        .next()
899                        .map(|row| Value::object(row.into_map()))
900                        .unwrap_or(Value::Null);
901                    parent.insert(plan.relation_name.clone(), value);
902                }
903            }
904        }
905    }
906}