icydb_core/db/query/plan/semantics/
logical.rs1use crate::{
7 db::{
8 access::AccessPlan,
9 predicate::PredicateExecutionModel,
10 query::plan::{
11 AccessPlannedQuery, ContinuationPolicy, DistinctExecutionStrategy,
12 ExecutionShapeSignature, GroupPlan, LogicalPlan, PlannerRouteProfile, QueryMode,
13 ScalarPlan, derive_logical_pushdown_eligibility, expr::ProjectionSpec,
14 grouped_cursor_policy_violation, lower_projection_identity, lower_projection_intent,
15 residual_query_predicate_after_access_path_bounds,
16 residual_query_predicate_after_filtered_access,
17 },
18 },
19 model::entity::EntityModel,
20};
21
22impl QueryMode {
23 #[must_use]
25 pub const fn is_load(&self) -> bool {
26 match self {
27 Self::Load(_) => true,
28 Self::Delete(_) => false,
29 }
30 }
31
32 #[must_use]
34 pub const fn is_delete(&self) -> bool {
35 match self {
36 Self::Delete(_) => true,
37 Self::Load(_) => false,
38 }
39 }
40}
41
42impl LogicalPlan {
43 #[must_use]
45 pub(in crate::db) const fn scalar_semantics(&self) -> &ScalarPlan {
46 match self {
47 Self::Scalar(plan) => plan,
48 Self::Grouped(plan) => &plan.scalar,
49 }
50 }
51
52 #[must_use]
54 #[cfg(test)]
55 pub(in crate::db) const fn scalar_semantics_mut(&mut self) -> &mut ScalarPlan {
56 match self {
57 Self::Scalar(plan) => plan,
58 Self::Grouped(plan) => &mut plan.scalar,
59 }
60 }
61
62 #[must_use]
64 #[cfg(test)]
65 pub(in crate::db) const fn scalar(&self) -> &ScalarPlan {
66 self.scalar_semantics()
67 }
68
69 #[must_use]
71 #[cfg(test)]
72 pub(in crate::db) const fn scalar_mut(&mut self) -> &mut ScalarPlan {
73 self.scalar_semantics_mut()
74 }
75}
76
77impl AccessPlannedQuery {
78 #[must_use]
80 pub(in crate::db) const fn scalar_plan(&self) -> &ScalarPlan {
81 self.logical.scalar_semantics()
82 }
83
84 #[must_use]
86 #[cfg(test)]
87 pub(in crate::db) const fn scalar_plan_mut(&mut self) -> &mut ScalarPlan {
88 self.logical.scalar_semantics_mut()
89 }
90
91 #[must_use]
93 #[cfg(test)]
94 pub(in crate::db) const fn scalar(&self) -> &ScalarPlan {
95 self.scalar_plan()
96 }
97
98 #[must_use]
100 #[cfg(test)]
101 pub(in crate::db) const fn scalar_mut(&mut self) -> &mut ScalarPlan {
102 self.scalar_plan_mut()
103 }
104
105 #[must_use]
107 pub(in crate::db) const fn grouped_plan(&self) -> Option<&GroupPlan> {
108 match &self.logical {
109 LogicalPlan::Scalar(_) => None,
110 LogicalPlan::Grouped(plan) => Some(plan),
111 }
112 }
113
114 #[must_use]
116 pub(in crate::db) fn projection_spec(&self, model: &EntityModel) -> ProjectionSpec {
117 lower_projection_intent(model, &self.logical, &self.projection_selection)
118 }
119
120 #[must_use]
122 pub(in crate::db::query) fn projection_spec_for_identity(&self) -> ProjectionSpec {
123 lower_projection_identity(&self.logical)
124 }
125
126 #[must_use]
132 pub(in crate::db) fn execution_preparation_predicate(&self) -> Option<PredicateExecutionModel> {
133 let query_predicate = self.scalar_plan().predicate.as_ref()?;
134
135 match self.access.selected_index_model() {
136 Some(index) => residual_query_predicate_after_filtered_access(index, query_predicate),
137 None => Some(query_predicate.clone()),
138 }
139 }
140
141 #[must_use]
145 pub(in crate::db) fn effective_execution_predicate(&self) -> Option<PredicateExecutionModel> {
146 let filtered_residual = self.execution_preparation_predicate();
149 let filtered_residual = filtered_residual.as_ref()?;
150
151 residual_query_predicate_after_access_path_bounds(self.access.as_path(), filtered_residual)
155 }
156
157 #[must_use]
159 pub(in crate::db) fn distinct_execution_strategy(&self) -> DistinctExecutionStrategy {
160 if !self.scalar_plan().distinct {
161 return DistinctExecutionStrategy::None;
162 }
163
164 match distinct_runtime_dedup_strategy(&self.access) {
168 Some(strategy) => strategy,
169 None => DistinctExecutionStrategy::None,
170 }
171 }
172
173 pub(in crate::db) fn finalize_planner_route_profile_for_model(&mut self, model: &EntityModel) {
175 self.set_planner_route_profile(project_planner_route_profile_for_model(model, self));
176 }
177
178 #[must_use]
180 pub(in crate::db) fn execution_shape_signature(
181 &self,
182 entity_path: &'static str,
183 ) -> ExecutionShapeSignature {
184 ExecutionShapeSignature::new(self.continuation_signature(entity_path))
185 }
186
187 #[must_use]
190 pub(in crate::db) fn predicate_fully_satisfied_by_access_contract(&self) -> bool {
191 self.scalar_plan().predicate.is_some() && self.effective_execution_predicate().is_none()
192 }
193
194 #[must_use]
198 pub(in crate::db) fn has_residual_predicate(&self) -> bool {
199 self.scalar_plan().predicate.is_some()
200 && !self.predicate_fully_satisfied_by_access_contract()
201 }
202}
203
204fn distinct_runtime_dedup_strategy<K>(access: &AccessPlan<K>) -> Option<DistinctExecutionStrategy> {
205 match access {
206 AccessPlan::Union(_) | AccessPlan::Intersection(_) => {
207 Some(DistinctExecutionStrategy::PreOrdered)
208 }
209 AccessPlan::Path(path) if path.as_ref().is_index_multi_lookup() => {
210 Some(DistinctExecutionStrategy::HashMaterialize)
211 }
212 AccessPlan::Path(_) => None,
213 }
214}
215
216fn derive_continuation_policy_validated(plan: &AccessPlannedQuery) -> ContinuationPolicy {
217 let is_grouped_safe = plan
218 .grouped_plan()
219 .is_none_or(|grouped| grouped_cursor_policy_violation(grouped, true).is_none());
220
221 ContinuationPolicy::new(
222 true, true, is_grouped_safe,
225 )
226}
227
228#[must_use]
230pub(in crate::db) fn project_planner_route_profile_for_model(
231 model: &EntityModel,
232 plan: &AccessPlannedQuery,
233) -> PlannerRouteProfile {
234 let secondary_order_contract = plan
235 .scalar_plan()
236 .order
237 .as_ref()
238 .and_then(|order| order.deterministic_secondary_order_contract(model.primary_key.name));
239
240 PlannerRouteProfile::new(
241 derive_continuation_policy_validated(plan),
242 derive_logical_pushdown_eligibility(plan, secondary_order_contract.as_ref()),
243 secondary_order_contract,
244 )
245}