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