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