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 #[must_use]
175 pub(in crate::db) fn planner_route_profile(&self, model: &EntityModel) -> PlannerRouteProfile {
176 PlannerRouteProfile::new(
177 derive_continuation_policy_validated(self),
178 derive_logical_pushdown_eligibility(model, self),
179 )
180 }
181
182 #[must_use]
184 pub(in crate::db) fn execution_shape_signature(
185 &self,
186 entity_path: &'static str,
187 ) -> ExecutionShapeSignature {
188 ExecutionShapeSignature::new(self.continuation_signature(entity_path))
189 }
190
191 #[must_use]
194 pub(in crate::db) fn predicate_fully_satisfied_by_access_contract(&self) -> bool {
195 self.scalar_plan().predicate.is_some() && self.effective_execution_predicate().is_none()
196 }
197
198 #[must_use]
202 pub(in crate::db) fn has_residual_predicate(&self) -> bool {
203 self.scalar_plan().predicate.is_some()
204 && !self.predicate_fully_satisfied_by_access_contract()
205 }
206}
207
208fn distinct_runtime_dedup_strategy<K>(access: &AccessPlan<K>) -> Option<DistinctExecutionStrategy> {
209 match access {
210 AccessPlan::Union(_) | AccessPlan::Intersection(_) => {
211 Some(DistinctExecutionStrategy::PreOrdered)
212 }
213 AccessPlan::Path(path) if path.as_ref().is_index_multi_lookup() => {
214 Some(DistinctExecutionStrategy::HashMaterialize)
215 }
216 AccessPlan::Path(_) => None,
217 }
218}
219
220fn derive_continuation_policy_validated(plan: &AccessPlannedQuery) -> ContinuationPolicy {
221 let is_grouped_safe = plan
222 .grouped_plan()
223 .is_none_or(|grouped| grouped_cursor_policy_violation(grouped, true).is_none());
224
225 ContinuationPolicy::new(
226 true, true, is_grouped_safe,
229 )
230}