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