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