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