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_direct_projection_slots,
15 lower_projection_identity, lower_projection_intent,
16 residual_query_predicate_after_access_path_bounds,
17 residual_query_predicate_after_filtered_access,
18 },
19 },
20 model::entity::EntityModel,
21};
22
23impl QueryMode {
24 #[must_use]
26 pub const fn is_load(&self) -> bool {
27 match self {
28 Self::Load(_) => true,
29 Self::Delete(_) => false,
30 }
31 }
32
33 #[must_use]
35 pub const fn is_delete(&self) -> bool {
36 match self {
37 Self::Delete(_) => true,
38 Self::Load(_) => false,
39 }
40 }
41}
42
43impl LogicalPlan {
44 #[must_use]
46 pub(in crate::db) const fn scalar_semantics(&self) -> &ScalarPlan {
47 match self {
48 Self::Scalar(plan) => plan,
49 Self::Grouped(plan) => &plan.scalar,
50 }
51 }
52
53 #[must_use]
55 #[cfg(test)]
56 pub(in crate::db) const fn scalar_semantics_mut(&mut self) -> &mut ScalarPlan {
57 match self {
58 Self::Scalar(plan) => plan,
59 Self::Grouped(plan) => &mut plan.scalar,
60 }
61 }
62
63 #[must_use]
65 #[cfg(test)]
66 pub(in crate::db) const fn scalar(&self) -> &ScalarPlan {
67 self.scalar_semantics()
68 }
69
70 #[must_use]
72 #[cfg(test)]
73 pub(in crate::db) const fn scalar_mut(&mut self) -> &mut ScalarPlan {
74 self.scalar_semantics_mut()
75 }
76}
77
78impl AccessPlannedQuery {
79 #[must_use]
81 pub(in crate::db) const fn scalar_plan(&self) -> &ScalarPlan {
82 self.logical.scalar_semantics()
83 }
84
85 #[must_use]
87 #[cfg(test)]
88 pub(in crate::db) const fn scalar_plan_mut(&mut self) -> &mut ScalarPlan {
89 self.logical.scalar_semantics_mut()
90 }
91
92 #[must_use]
94 #[cfg(test)]
95 pub(in crate::db) const fn scalar(&self) -> &ScalarPlan {
96 self.scalar_plan()
97 }
98
99 #[must_use]
101 #[cfg(test)]
102 pub(in crate::db) const fn scalar_mut(&mut self) -> &mut ScalarPlan {
103 self.scalar_plan_mut()
104 }
105
106 #[must_use]
108 pub(in crate::db) const fn grouped_plan(&self) -> Option<&GroupPlan> {
109 match &self.logical {
110 LogicalPlan::Scalar(_) => None,
111 LogicalPlan::Grouped(plan) => Some(plan),
112 }
113 }
114
115 #[must_use]
117 pub(in crate::db) fn projection_spec(&self, model: &EntityModel) -> ProjectionSpec {
118 lower_projection_intent(model, &self.logical, &self.projection_selection)
119 }
120
121 #[must_use]
124 pub(in crate::db) fn direct_projection_slots(&self, model: &EntityModel) -> Option<Vec<usize>> {
125 lower_direct_projection_slots(model, &self.logical, &self.projection_selection)
126 }
127
128 #[must_use]
130 pub(in crate::db::query) fn projection_spec_for_identity(&self) -> ProjectionSpec {
131 lower_projection_identity(&self.logical)
132 }
133
134 #[must_use]
140 pub(in crate::db) fn execution_preparation_predicate(&self) -> Option<PredicateExecutionModel> {
141 let query_predicate = self.scalar_plan().predicate.as_ref()?;
142
143 match self.access.selected_index_model() {
144 Some(index) => residual_query_predicate_after_filtered_access(index, query_predicate),
145 None => Some(query_predicate.clone()),
146 }
147 }
148
149 #[must_use]
153 pub(in crate::db) fn effective_execution_predicate(&self) -> Option<PredicateExecutionModel> {
154 let filtered_residual = self.execution_preparation_predicate();
157 let filtered_residual = filtered_residual.as_ref()?;
158
159 residual_query_predicate_after_access_path_bounds(self.access.as_path(), filtered_residual)
163 }
164
165 #[must_use]
167 pub(in crate::db) fn distinct_execution_strategy(&self) -> DistinctExecutionStrategy {
168 if !self.scalar_plan().distinct {
169 return DistinctExecutionStrategy::None;
170 }
171
172 match distinct_runtime_dedup_strategy(&self.access) {
176 Some(strategy) => strategy,
177 None => DistinctExecutionStrategy::None,
178 }
179 }
180
181 pub(in crate::db) fn finalize_planner_route_profile_for_model(&mut self, model: &EntityModel) {
183 self.set_planner_route_profile(project_planner_route_profile_for_model(model, self));
184 }
185
186 #[must_use]
188 pub(in crate::db) fn execution_shape_signature(
189 &self,
190 entity_path: &'static str,
191 ) -> ExecutionShapeSignature {
192 ExecutionShapeSignature::new(self.continuation_signature(entity_path))
193 }
194
195 #[must_use]
198 pub(in crate::db) fn predicate_fully_satisfied_by_access_contract(&self) -> bool {
199 self.scalar_plan().predicate.is_some() && self.effective_execution_predicate().is_none()
200 }
201
202 #[must_use]
206 pub(in crate::db) fn has_residual_predicate(&self) -> bool {
207 self.scalar_plan().predicate.is_some()
208 && !self.predicate_fully_satisfied_by_access_contract()
209 }
210}
211
212fn distinct_runtime_dedup_strategy<K>(access: &AccessPlan<K>) -> Option<DistinctExecutionStrategy> {
213 match access {
214 AccessPlan::Union(_) | AccessPlan::Intersection(_) => {
215 Some(DistinctExecutionStrategy::PreOrdered)
216 }
217 AccessPlan::Path(path) if path.as_ref().is_index_multi_lookup() => {
218 Some(DistinctExecutionStrategy::HashMaterialize)
219 }
220 AccessPlan::Path(_) => None,
221 }
222}
223
224fn derive_continuation_policy_validated(plan: &AccessPlannedQuery) -> ContinuationPolicy {
225 let is_grouped_safe = plan
226 .grouped_plan()
227 .is_none_or(|grouped| grouped_cursor_policy_violation(grouped, true).is_none());
228
229 ContinuationPolicy::new(
230 true, true, is_grouped_safe,
233 )
234}
235
236#[must_use]
238pub(in crate::db) fn project_planner_route_profile_for_model(
239 model: &EntityModel,
240 plan: &AccessPlannedQuery,
241) -> PlannerRouteProfile {
242 let secondary_order_contract = plan
243 .scalar_plan()
244 .order
245 .as_ref()
246 .and_then(|order| order.deterministic_secondary_order_contract(model.primary_key.name));
247
248 PlannerRouteProfile::new(
249 derive_continuation_policy_validated(plan),
250 derive_logical_pushdown_eligibility(plan, secondary_order_contract.as_ref()),
251 secondary_order_contract,
252 )
253}