1use crate::{
7 db::{
8 access::{AccessPlan, ExecutableAccessPlan},
9 predicate::{IndexCompileTarget, MissingRowPolicy, Predicate, PredicateProgram},
10 query::plan::{
11 AccessPlannedQuery, ContinuationPolicy, DistinctExecutionStrategy,
12 EffectiveRuntimeFilterProgram, ExecutionShapeSignature, GroupPlan,
13 GroupedAggregateExecutionSpec, GroupedDistinctExecutionStrategy, GroupedPlanStrategy,
14 LogicalPlan, PlannerRouteProfile, QueryMode, ResolvedOrder, ResolvedOrderField,
15 ResolvedOrderValueSource, ScalarPlan, StaticPlanningShape,
16 derive_logical_pushdown_eligibility,
17 expr::{
18 Expr, ProjectionSpec, ScalarProjectionExpr, compile_scalar_projection_expr,
19 compile_scalar_projection_plan,
20 },
21 grouped_aggregate_execution_specs, grouped_aggregate_specs_from_projection_spec,
22 grouped_cursor_policy_violation, grouped_plan_strategy, lower_direct_projection_slots,
23 lower_projection_identity, lower_projection_intent,
24 residual_query_predicate_after_access_path_bounds,
25 residual_query_predicate_after_filtered_access,
26 resolved_grouped_distinct_execution_strategy_for_model,
27 },
28 },
29 error::InternalError,
30 model::{entity::EntityModel, index::IndexKeyItemsRef},
31};
32
33impl QueryMode {
34 #[must_use]
36 pub const fn is_load(&self) -> bool {
37 match self {
38 Self::Load(_) => true,
39 Self::Delete(_) => false,
40 }
41 }
42
43 #[must_use]
45 pub const fn is_delete(&self) -> bool {
46 match self {
47 Self::Delete(_) => true,
48 Self::Load(_) => false,
49 }
50 }
51}
52
53impl LogicalPlan {
54 #[must_use]
56 pub(in crate::db) const fn scalar_semantics(&self) -> &ScalarPlan {
57 match self {
58 Self::Scalar(plan) => plan,
59 Self::Grouped(plan) => &plan.scalar,
60 }
61 }
62
63 #[must_use]
65 #[cfg(test)]
66 pub(in crate::db) const fn scalar_semantics_mut(&mut self) -> &mut ScalarPlan {
67 match self {
68 Self::Scalar(plan) => plan,
69 Self::Grouped(plan) => &mut plan.scalar,
70 }
71 }
72
73 #[must_use]
75 #[cfg(test)]
76 pub(in crate::db) const fn scalar(&self) -> &ScalarPlan {
77 self.scalar_semantics()
78 }
79
80 #[must_use]
82 #[cfg(test)]
83 pub(in crate::db) const fn scalar_mut(&mut self) -> &mut ScalarPlan {
84 self.scalar_semantics_mut()
85 }
86}
87
88impl AccessPlannedQuery {
89 #[must_use]
91 pub(in crate::db) const fn scalar_plan(&self) -> &ScalarPlan {
92 self.logical.scalar_semantics()
93 }
94
95 #[must_use]
98 pub(in crate::db) const fn scalar_consistency(&self) -> MissingRowPolicy {
99 self.scalar_plan().consistency
100 }
101
102 #[must_use]
104 #[cfg(test)]
105 pub(in crate::db) const fn scalar_plan_mut(&mut self) -> &mut ScalarPlan {
106 self.logical.scalar_semantics_mut()
107 }
108
109 #[must_use]
111 #[cfg(test)]
112 pub(in crate::db) const fn scalar(&self) -> &ScalarPlan {
113 self.scalar_plan()
114 }
115
116 #[must_use]
118 #[cfg(test)]
119 pub(in crate::db) const fn scalar_mut(&mut self) -> &mut ScalarPlan {
120 self.scalar_plan_mut()
121 }
122
123 #[must_use]
125 pub(in crate::db) const fn grouped_plan(&self) -> Option<&GroupPlan> {
126 match &self.logical {
127 LogicalPlan::Scalar(_) => None,
128 LogicalPlan::Grouped(plan) => Some(plan),
129 }
130 }
131
132 #[must_use]
134 pub(in crate::db) fn projection_spec(&self, model: &EntityModel) -> ProjectionSpec {
135 if let Some(static_shape) = &self.static_planning_shape {
136 return static_shape.projection_spec.clone();
137 }
138
139 lower_projection_intent(model, &self.logical, &self.projection_selection)
140 }
141
142 #[must_use]
144 pub(in crate::db::query) fn projection_spec_for_identity(&self) -> ProjectionSpec {
145 lower_projection_identity(&self.logical, &self.projection_selection)
146 }
147
148 #[must_use]
154 pub(in crate::db) fn execution_preparation_predicate(&self) -> Option<Predicate> {
155 if let Some(static_shape) = self.static_planning_shape.as_ref() {
156 return static_shape.execution_preparation_predicate.clone();
157 }
158
159 derive_execution_preparation_predicate(self)
160 }
161
162 #[must_use]
166 pub(in crate::db) fn effective_execution_predicate(&self) -> Option<Predicate> {
167 if let Some(static_shape) = self.static_planning_shape.as_ref() {
168 return static_shape.residual_filter_predicate.clone();
169 }
170
171 derive_residual_filter_predicate(self)
172 }
173
174 #[must_use]
177 pub(in crate::db) fn has_residual_filter_predicate(&self) -> bool {
178 self.effective_execution_predicate().is_some()
179 }
180
181 #[must_use]
184 pub(in crate::db) fn residual_filter_expr(&self) -> Option<&Expr> {
185 if let Some(static_shape) = self.static_planning_shape.as_ref() {
186 return static_shape.residual_filter_expr.as_ref();
187 }
188
189 if !derive_has_residual_filter(self) {
190 return None;
191 }
192
193 self.scalar_plan().filter_expr.as_ref()
194 }
195
196 #[must_use]
199 pub(in crate::db) fn has_residual_filter_expr(&self) -> bool {
200 self.residual_filter_expr().is_some()
201 }
202
203 #[must_use]
205 pub(in crate::db) const fn execution_preparation_compiled_predicate(
206 &self,
207 ) -> Option<&PredicateProgram> {
208 self.static_planning_shape()
209 .execution_preparation_compiled_predicate
210 .as_ref()
211 }
212
213 #[must_use]
215 pub(in crate::db) const fn effective_runtime_compiled_predicate(
216 &self,
217 ) -> Option<&PredicateProgram> {
218 match self
219 .static_planning_shape()
220 .effective_runtime_filter_program
221 .as_ref()
222 {
223 Some(EffectiveRuntimeFilterProgram::Predicate(program)) => Some(program),
224 Some(EffectiveRuntimeFilterProgram::Expr(_)) | None => None,
225 }
226 }
227
228 #[must_use]
230 pub(in crate::db) const fn effective_runtime_compiled_filter_expr(
231 &self,
232 ) -> Option<&ScalarProjectionExpr> {
233 match self
234 .static_planning_shape()
235 .effective_runtime_filter_program
236 .as_ref()
237 {
238 Some(EffectiveRuntimeFilterProgram::Expr(expr)) => Some(expr),
239 Some(EffectiveRuntimeFilterProgram::Predicate(_)) | None => None,
240 }
241 }
242
243 #[must_use]
245 pub(in crate::db) const fn effective_runtime_filter_program(
246 &self,
247 ) -> Option<&EffectiveRuntimeFilterProgram> {
248 self.static_planning_shape()
249 .effective_runtime_filter_program
250 .as_ref()
251 }
252
253 #[must_use]
255 pub(in crate::db) fn distinct_execution_strategy(&self) -> DistinctExecutionStrategy {
256 if !self.scalar_plan().distinct {
257 return DistinctExecutionStrategy::None;
258 }
259
260 match distinct_runtime_dedup_strategy(&self.access) {
264 Some(strategy) => strategy,
265 None => DistinctExecutionStrategy::None,
266 }
267 }
268
269 pub(in crate::db) fn finalize_planner_route_profile_for_model(&mut self, model: &EntityModel) {
271 self.set_planner_route_profile(project_planner_route_profile_for_model(model, self));
272 }
273
274 pub(in crate::db) fn finalize_static_planning_shape_for_model(
276 &mut self,
277 model: &EntityModel,
278 ) -> Result<(), InternalError> {
279 self.static_planning_shape = Some(project_static_planning_shape_for_model(model, self)?);
280
281 Ok(())
282 }
283
284 #[must_use]
286 pub(in crate::db) fn execution_shape_signature(
287 &self,
288 entity_path: &'static str,
289 ) -> ExecutionShapeSignature {
290 ExecutionShapeSignature::new(self.continuation_signature(entity_path))
291 }
292
293 #[must_use]
296 pub(in crate::db) fn predicate_fully_satisfied_by_access_contract(&self) -> bool {
297 if let Some(static_shape) = self.static_planning_shape.as_ref() {
298 return self.scalar_plan().predicate.is_some()
299 && static_shape.residual_filter_predicate.is_none()
300 && static_shape.residual_filter_expr.is_none();
301 }
302
303 derive_predicate_fully_satisfied_by_access_contract(self)
304 }
305
306 #[must_use]
308 pub(in crate::db) fn scalar_projection_plan(&self) -> Option<&[ScalarProjectionExpr]> {
309 self.static_planning_shape()
310 .scalar_projection_plan
311 .as_deref()
312 }
313
314 #[must_use]
316 pub(in crate::db) const fn primary_key_name(&self) -> &'static str {
317 self.static_planning_shape().primary_key_name
318 }
319
320 #[must_use]
322 pub(in crate::db) const fn projection_referenced_slots(&self) -> &[usize] {
323 self.static_planning_shape()
324 .projection_referenced_slots
325 .as_slice()
326 }
327
328 #[must_use]
330 #[cfg(any(test, feature = "diagnostics"))]
331 pub(in crate::db) const fn projected_slot_mask(&self) -> &[bool] {
332 self.static_planning_shape().projected_slot_mask.as_slice()
333 }
334
335 #[must_use]
337 pub(in crate::db) const fn projection_is_model_identity(&self) -> bool {
338 self.static_planning_shape().projection_is_model_identity
339 }
340
341 #[must_use]
343 pub(in crate::db) fn order_referenced_slots(&self) -> Option<&[usize]> {
344 self.static_planning_shape()
345 .order_referenced_slots
346 .as_deref()
347 }
348
349 #[must_use]
351 pub(in crate::db) const fn resolved_order(&self) -> Option<&ResolvedOrder> {
352 self.static_planning_shape().resolved_order.as_ref()
353 }
354
355 #[must_use]
357 pub(in crate::db) fn slot_map(&self) -> Option<&[usize]> {
358 self.static_planning_shape().slot_map.as_deref()
359 }
360
361 #[must_use]
363 pub(in crate::db) fn grouped_aggregate_execution_specs(
364 &self,
365 ) -> Option<&[GroupedAggregateExecutionSpec]> {
366 self.static_planning_shape()
367 .grouped_aggregate_execution_specs
368 .as_deref()
369 }
370
371 #[must_use]
373 pub(in crate::db) const fn grouped_distinct_execution_strategy(
374 &self,
375 ) -> Option<&GroupedDistinctExecutionStrategy> {
376 self.static_planning_shape()
377 .grouped_distinct_execution_strategy
378 .as_ref()
379 }
380
381 #[must_use]
383 pub(in crate::db) const fn frozen_projection_spec(&self) -> &ProjectionSpec {
384 &self.static_planning_shape().projection_spec
385 }
386
387 #[must_use]
389 pub(in crate::db) fn frozen_direct_projection_slots(&self) -> Option<&[usize]> {
390 self.static_planning_shape()
391 .projection_direct_slots
392 .as_deref()
393 }
394
395 #[must_use]
397 pub(in crate::db) fn index_compile_targets(&self) -> Option<&[IndexCompileTarget]> {
398 self.static_planning_shape()
399 .index_compile_targets
400 .as_deref()
401 }
402
403 const fn static_planning_shape(&self) -> &StaticPlanningShape {
404 self.static_planning_shape
405 .as_ref()
406 .expect("access-planned queries must freeze static planning shape before execution")
407 }
408}
409
410fn distinct_runtime_dedup_strategy<K>(access: &AccessPlan<K>) -> Option<DistinctExecutionStrategy> {
411 match access {
412 AccessPlan::Union(_) | AccessPlan::Intersection(_) => {
413 Some(DistinctExecutionStrategy::PreOrdered)
414 }
415 AccessPlan::Path(path) if path.as_ref().is_index_multi_lookup() => {
416 Some(DistinctExecutionStrategy::HashMaterialize)
417 }
418 AccessPlan::Path(_) => None,
419 }
420}
421
422fn derive_continuation_policy_validated(plan: &AccessPlannedQuery) -> ContinuationPolicy {
423 let is_grouped_safe = plan
424 .grouped_plan()
425 .is_none_or(|grouped| grouped_cursor_policy_violation(grouped, true).is_none());
426
427 ContinuationPolicy::new(
428 true, true, is_grouped_safe,
431 )
432}
433
434#[must_use]
436pub(in crate::db) fn project_planner_route_profile_for_model(
437 model: &EntityModel,
438 plan: &AccessPlannedQuery,
439) -> PlannerRouteProfile {
440 let secondary_order_contract = plan
441 .scalar_plan()
442 .order
443 .as_ref()
444 .and_then(|order| order.deterministic_secondary_order_contract(model.primary_key.name));
445
446 PlannerRouteProfile::new(
447 derive_continuation_policy_validated(plan),
448 derive_logical_pushdown_eligibility(plan, secondary_order_contract.as_ref()),
449 secondary_order_contract,
450 )
451}
452
453fn project_static_planning_shape_for_model(
454 model: &EntityModel,
455 plan: &AccessPlannedQuery,
456) -> Result<StaticPlanningShape, InternalError> {
457 let projection_spec = lower_projection_intent(model, &plan.logical, &plan.projection_selection);
458 let execution_preparation_predicate = plan.execution_preparation_predicate();
459 let residual_filter_predicate = derive_residual_filter_predicate(plan);
460 let residual_filter_expr = derive_residual_filter_expr_for_model(model, plan);
461 let execution_preparation_compiled_predicate =
462 compile_optional_predicate(model, execution_preparation_predicate.as_ref());
463 let effective_runtime_filter_program = compile_effective_runtime_filter_program(
464 model,
465 residual_filter_expr.as_ref(),
466 residual_filter_predicate.as_ref(),
467 )?;
468 let scalar_projection_plan =
469 if plan.grouped_plan().is_none() {
470 Some(compile_scalar_projection_plan(model, &projection_spec).ok_or_else(|| {
471 InternalError::query_executor_invariant(
472 "scalar projection program must compile during static planning finalization",
473 )
474 })?)
475 } else {
476 None
477 };
478 let (grouped_aggregate_execution_specs, grouped_distinct_execution_strategy) =
479 resolve_grouped_static_planning_semantics(model, plan, &projection_spec)?;
480 let projection_direct_slots =
481 lower_direct_projection_slots(model, &plan.logical, &plan.projection_selection);
482 let projection_referenced_slots = projection_spec.referenced_slots_for(model)?;
483 let projected_slot_mask =
484 projected_slot_mask_for_spec(model, projection_direct_slots.as_deref());
485 let projection_is_model_identity = projection_spec.is_model_identity_for(model);
486 let resolved_order = resolved_order_for_plan(model, plan)?;
487 let order_referenced_slots = order_referenced_slots_for_resolved_order(resolved_order.as_ref());
488 let slot_map = slot_map_for_model_plan(model, plan);
489 let index_compile_targets = index_compile_targets_for_model_plan(model, plan);
490
491 Ok(StaticPlanningShape {
492 primary_key_name: model.primary_key.name,
493 projection_spec,
494 execution_preparation_predicate,
495 residual_filter_expr,
496 residual_filter_predicate,
497 execution_preparation_compiled_predicate,
498 effective_runtime_filter_program,
499 scalar_projection_plan,
500 grouped_aggregate_execution_specs,
501 grouped_distinct_execution_strategy,
502 projection_direct_slots,
503 projection_referenced_slots,
504 projected_slot_mask,
505 projection_is_model_identity,
506 resolved_order,
507 order_referenced_slots,
508 slot_map,
509 index_compile_targets,
510 })
511}
512
513fn compile_effective_runtime_filter_program(
517 model: &EntityModel,
518 residual_filter_expr: Option<&Expr>,
519 residual_filter_predicate: Option<&Predicate>,
520) -> Result<Option<EffectiveRuntimeFilterProgram>, InternalError> {
521 if let Some(predicate) = residual_filter_predicate {
526 return Ok(Some(EffectiveRuntimeFilterProgram::Predicate(
527 PredicateProgram::compile(model, predicate),
528 )));
529 }
530
531 if let Some(filter_expr) = residual_filter_expr {
532 let compiled = compile_scalar_projection_expr(model, filter_expr).ok_or_else(|| {
533 InternalError::query_invalid_logical_plan(
534 "effective runtime scalar filter expression must compile during static planning finalization",
535 )
536 })?;
537
538 return Ok(Some(EffectiveRuntimeFilterProgram::Expr(compiled)));
539 }
540
541 Ok(None)
542}
543
544fn derive_execution_preparation_predicate(plan: &AccessPlannedQuery) -> Option<Predicate> {
548 let query_predicate = plan.scalar_plan().predicate.as_ref()?;
549
550 match plan.access.selected_index_model() {
551 Some(index) => residual_query_predicate_after_filtered_access(index, query_predicate),
552 None => Some(query_predicate.clone()),
553 }
554}
555
556fn derive_residual_filter_predicate(plan: &AccessPlannedQuery) -> Option<Predicate> {
560 let filtered_residual = derive_execution_preparation_predicate(plan);
561 let filtered_residual = filtered_residual.as_ref()?;
562
563 residual_query_predicate_after_access_path_bounds(plan.access.as_path(), filtered_residual)
564}
565
566fn derive_residual_filter_expr(plan: &AccessPlannedQuery) -> Option<Expr> {
570 let filter_expr = plan.scalar_plan().filter_expr.as_ref()?;
571 if derive_semantic_filter_fully_satisfied_by_access_contract(plan) {
572 return None;
573 }
574
575 Some(filter_expr.clone())
576}
577
578fn derive_residual_filter_expr_for_model(
582 model: &EntityModel,
583 plan: &AccessPlannedQuery,
584) -> Option<Expr> {
585 let filter_expr = plan.scalar_plan().filter_expr.as_ref()?;
586 if derive_semantic_filter_fully_satisfied_by_access_contract_for_model(model, plan) {
587 return None;
588 }
589
590 Some(filter_expr.clone())
591}
592
593fn derive_has_residual_filter(plan: &AccessPlannedQuery) -> bool {
597 match (
598 plan.scalar_plan().filter_expr.as_ref(),
599 plan.scalar_plan().predicate.as_ref(),
600 ) {
601 (None, None) => false,
602 (Some(_), None) => true,
603 (Some(_) | None, Some(_)) => !plan.predicate_fully_satisfied_by_access_contract(),
604 }
605}
606
607fn derive_predicate_fully_satisfied_by_access_contract(plan: &AccessPlannedQuery) -> bool {
610 plan.scalar_plan().predicate.is_some()
611 && derive_residual_filter_predicate(plan).is_none()
612 && derive_residual_filter_expr(plan).is_none()
613}
614
615const fn derive_semantic_filter_fully_satisfied_by_access_contract(
619 plan: &AccessPlannedQuery,
620) -> bool {
621 plan.scalar_plan().filter_expr.is_some()
622 && plan.scalar_plan().predicate.is_some()
623 && plan.scalar_plan().predicate_covers_filter_expr
624}
625
626const fn derive_semantic_filter_fully_satisfied_by_access_contract_for_model(
630 _model: &EntityModel,
631 plan: &AccessPlannedQuery,
632) -> bool {
633 derive_semantic_filter_fully_satisfied_by_access_contract(plan)
634}
635
636fn compile_optional_predicate(
639 model: &EntityModel,
640 predicate: Option<&Predicate>,
641) -> Option<PredicateProgram> {
642 predicate.map(|predicate| PredicateProgram::compile(model, predicate))
643}
644
645fn resolve_grouped_static_planning_semantics(
649 model: &EntityModel,
650 plan: &AccessPlannedQuery,
651 projection_spec: &ProjectionSpec,
652) -> Result<
653 (
654 Option<Vec<GroupedAggregateExecutionSpec>>,
655 Option<GroupedDistinctExecutionStrategy>,
656 ),
657 InternalError,
658> {
659 let Some(grouped) = plan.grouped_plan() else {
660 return Ok((None, None));
661 };
662
663 let mut aggregate_specs = grouped_aggregate_specs_from_projection_spec(
664 projection_spec,
665 grouped.group.group_fields.as_slice(),
666 grouped.group.aggregates.as_slice(),
667 )?;
668 extend_grouped_having_aggregate_specs(&mut aggregate_specs, grouped)?;
669
670 let grouped_aggregate_execution_specs = Some(grouped_aggregate_execution_specs(
671 model,
672 aggregate_specs.as_slice(),
673 )?);
674 let grouped_distinct_execution_strategy =
675 Some(resolved_grouped_distinct_execution_strategy_for_model(
676 model,
677 grouped.group.group_fields.as_slice(),
678 grouped.group.aggregates.as_slice(),
679 grouped.having_expr.as_ref(),
680 )?);
681
682 Ok((
683 grouped_aggregate_execution_specs,
684 grouped_distinct_execution_strategy,
685 ))
686}
687
688fn extend_grouped_having_aggregate_specs(
689 aggregate_specs: &mut Vec<GroupedAggregateExecutionSpec>,
690 grouped: &GroupPlan,
691) -> Result<(), InternalError> {
692 if let Some(having_expr) = grouped.having_expr.as_ref() {
693 collect_grouped_having_expr_aggregate_specs(aggregate_specs, having_expr)?;
694 }
695
696 Ok(())
697}
698
699fn collect_grouped_having_expr_aggregate_specs(
700 aggregate_specs: &mut Vec<GroupedAggregateExecutionSpec>,
701 expr: &Expr,
702) -> Result<(), InternalError> {
703 if !expr.contains_aggregate() {
704 return Ok(());
705 }
706
707 expr.try_for_each_tree_aggregate(&mut |aggregate_expr| {
708 let aggregate_spec = GroupedAggregateExecutionSpec::from_aggregate_expr(aggregate_expr);
709
710 if aggregate_specs
711 .iter()
712 .all(|current| current != &aggregate_spec)
713 {
714 aggregate_specs.push(aggregate_spec);
715 }
716
717 Ok(())
718 })
719}
720
721fn projected_slot_mask_for_spec(
722 model: &EntityModel,
723 direct_projection_slots: Option<&[usize]>,
724) -> Vec<bool> {
725 let mut projected_slots = vec![false; model.fields().len()];
726
727 let Some(direct_projection_slots) = direct_projection_slots else {
728 return projected_slots;
729 };
730
731 for slot in direct_projection_slots.iter().copied() {
732 if let Some(projected) = projected_slots.get_mut(slot) {
733 *projected = true;
734 }
735 }
736
737 projected_slots
738}
739
740fn resolved_order_for_plan(
741 model: &EntityModel,
742 plan: &AccessPlannedQuery,
743) -> Result<Option<ResolvedOrder>, InternalError> {
744 if grouped_plan_strategy(plan).is_some_and(GroupedPlanStrategy::is_top_k_group) {
745 return Ok(None);
746 }
747
748 let Some(order) = plan.scalar_plan().order.as_ref() else {
749 return Ok(None);
750 };
751
752 let mut fields = Vec::with_capacity(order.fields.len());
753 for term in &order.fields {
754 fields.push(ResolvedOrderField::new(
755 resolved_order_value_source_for_term(model, term)?,
756 term.direction(),
757 ));
758 }
759
760 Ok(Some(ResolvedOrder::new(fields)))
761}
762
763fn resolved_order_value_source_for_term(
764 model: &EntityModel,
765 term: &crate::db::query::plan::OrderTerm,
766) -> Result<ResolvedOrderValueSource, InternalError> {
767 if term.direct_field().is_none() {
768 let rendered = term.rendered_label();
769 validate_resolved_order_expr_fields(model, term.expr(), rendered.as_str())?;
770 let compiled = compile_scalar_projection_expr(model, term.expr())
771 .ok_or_else(|| order_expression_scalar_seam_error(rendered.as_str()))?;
772
773 return Ok(ResolvedOrderValueSource::expression(compiled));
774 }
775
776 let field = term
777 .direct_field()
778 .expect("direct-field order branch should only execute for field-backed terms");
779 let slot = resolve_required_field_slot(model, field, || {
780 InternalError::query_invalid_logical_plan(format!(
781 "order expression references unknown field '{field}'",
782 ))
783 })?;
784
785 Ok(ResolvedOrderValueSource::direct_field(slot))
786}
787
788fn validate_resolved_order_expr_fields(
789 model: &EntityModel,
790 expr: &Expr,
791 rendered: &str,
792) -> Result<(), InternalError> {
793 expr.try_for_each_tree_expr(&mut |node| match node {
794 Expr::Field(field_id) => resolve_required_field_slot(model, field_id.as_str(), || {
795 InternalError::query_invalid_logical_plan(format!(
796 "order expression references unknown field '{rendered}'",
797 ))
798 })
799 .map(|_| ()),
800 Expr::Aggregate(_) => Err(order_expression_scalar_seam_error(rendered)),
801 #[cfg(test)]
802 Expr::Alias { .. } => Err(order_expression_scalar_seam_error(rendered)),
803 Expr::Unary { .. } => Err(order_expression_scalar_seam_error(rendered)),
804 _ => Ok(()),
805 })
806}
807
808fn resolve_required_field_slot<F>(
811 model: &EntityModel,
812 field: &str,
813 invalid_plan_error: F,
814) -> Result<usize, InternalError>
815where
816 F: FnOnce() -> InternalError,
817{
818 model
819 .resolve_field_slot(field)
820 .ok_or_else(invalid_plan_error)
821}
822
823fn order_expression_scalar_seam_error(rendered: &str) -> InternalError {
826 InternalError::query_invalid_logical_plan(format!(
827 "order expression '{rendered}' did not stay on the scalar expression seam",
828 ))
829}
830
831fn order_referenced_slots_for_resolved_order(
836 resolved_order: Option<&ResolvedOrder>,
837) -> Option<Vec<usize>> {
838 Some(resolved_order?.referenced_slots())
839}
840
841fn slot_map_for_model_plan(model: &EntityModel, plan: &AccessPlannedQuery) -> Option<Vec<usize>> {
842 let executable = plan.access.executable_contract();
843
844 resolved_index_slots_for_access_path(model, &executable)
845}
846
847fn resolved_index_slots_for_access_path(
848 model: &EntityModel,
849 access: &ExecutableAccessPlan<'_, crate::value::Value>,
850) -> Option<Vec<usize>> {
851 let path = access.as_path()?;
852 let path_capabilities = path.capabilities();
853 let index_fields = path_capabilities.index_fields_for_slot_map()?;
854 let mut slots = Vec::with_capacity(index_fields.len());
855
856 for field_name in index_fields {
857 let slot = model.resolve_field_slot(field_name)?;
858 slots.push(slot);
859 }
860
861 Some(slots)
862}
863
864fn index_compile_targets_for_model_plan(
865 model: &EntityModel,
866 plan: &AccessPlannedQuery,
867) -> Option<Vec<IndexCompileTarget>> {
868 let index = plan.access.as_path()?.selected_index_model()?;
869 let mut targets = Vec::new();
870
871 match index.key_items() {
872 IndexKeyItemsRef::Fields(fields) => {
873 for (component_index, &field_name) in fields.iter().enumerate() {
874 let field_slot = model.resolve_field_slot(field_name)?;
875 targets.push(IndexCompileTarget {
876 component_index,
877 field_slot,
878 key_item: crate::model::index::IndexKeyItem::Field(field_name),
879 });
880 }
881 }
882 IndexKeyItemsRef::Items(items) => {
883 for (component_index, &key_item) in items.iter().enumerate() {
884 let field_slot = model.resolve_field_slot(key_item.field())?;
885 targets.push(IndexCompileTarget {
886 component_index,
887 field_slot,
888 key_item,
889 });
890 }
891 }
892 }
893
894 Some(targets)
895}