icydb_core/db/query/plan/semantics/
logical.rs1use crate::{
7 db::{
8 access::AccessPlan,
9 query::plan::{
10 AccessPlannedQuery, ContinuationPolicy, DistinctExecutionStrategy,
11 ExecutionShapeSignature, GroupPlan, LogicalPlan, PlannerRouteProfile, QueryMode,
12 ScalarPlan, derive_logical_pushdown_eligibility, expr::ProjectionSpec,
13 filtered_index_predicate_satisfies_query, grouped_cursor_policy_violation,
14 lower_projection_identity, lower_projection_intent,
15 },
16 },
17 model::entity::EntityModel,
18};
19
20impl QueryMode {
21 #[must_use]
23 pub const fn is_load(&self) -> bool {
24 match self {
25 Self::Load(_) => true,
26 Self::Delete(_) => false,
27 }
28 }
29
30 #[must_use]
32 pub const fn is_delete(&self) -> bool {
33 match self {
34 Self::Delete(_) => true,
35 Self::Load(_) => false,
36 }
37 }
38}
39
40impl LogicalPlan {
41 #[must_use]
43 pub(in crate::db) const fn scalar_semantics(&self) -> &ScalarPlan {
44 match self {
45 Self::Scalar(plan) => plan,
46 Self::Grouped(plan) => &plan.scalar,
47 }
48 }
49
50 #[must_use]
52 #[cfg(test)]
53 pub(in crate::db) const fn scalar_semantics_mut(&mut self) -> &mut ScalarPlan {
54 match self {
55 Self::Scalar(plan) => plan,
56 Self::Grouped(plan) => &mut plan.scalar,
57 }
58 }
59
60 #[must_use]
62 #[cfg(test)]
63 pub(in crate::db) const fn scalar(&self) -> &ScalarPlan {
64 self.scalar_semantics()
65 }
66
67 #[must_use]
69 #[cfg(test)]
70 pub(in crate::db) const fn scalar_mut(&mut self) -> &mut ScalarPlan {
71 self.scalar_semantics_mut()
72 }
73}
74
75impl AccessPlannedQuery {
76 #[must_use]
78 pub(in crate::db) const fn scalar_plan(&self) -> &ScalarPlan {
79 self.logical.scalar_semantics()
80 }
81
82 #[must_use]
84 #[cfg(test)]
85 pub(in crate::db) const fn scalar_plan_mut(&mut self) -> &mut ScalarPlan {
86 self.logical.scalar_semantics_mut()
87 }
88
89 #[must_use]
91 #[cfg(test)]
92 pub(in crate::db) const fn scalar(&self) -> &ScalarPlan {
93 self.scalar_plan()
94 }
95
96 #[must_use]
98 #[cfg(test)]
99 pub(in crate::db) const fn scalar_mut(&mut self) -> &mut ScalarPlan {
100 self.scalar_plan_mut()
101 }
102
103 #[must_use]
105 pub(in crate::db) const fn grouped_plan(&self) -> Option<&GroupPlan> {
106 match &self.logical {
107 LogicalPlan::Scalar(_) => None,
108 LogicalPlan::Grouped(plan) => Some(plan),
109 }
110 }
111
112 #[must_use]
114 pub(in crate::db) fn projection_spec(&self, model: &EntityModel) -> ProjectionSpec {
115 lower_projection_intent(model, &self.logical, &self.projection_selection)
116 }
117
118 #[must_use]
120 pub(in crate::db::query) fn projection_spec_for_identity(&self) -> ProjectionSpec {
121 lower_projection_identity(&self.logical)
122 }
123
124 #[must_use]
126 pub(in crate::db) fn distinct_execution_strategy(&self) -> DistinctExecutionStrategy {
127 if !self.scalar_plan().distinct {
128 return DistinctExecutionStrategy::None;
129 }
130
131 match distinct_runtime_dedup_strategy(&self.access) {
135 Some(strategy) => strategy,
136 None => DistinctExecutionStrategy::None,
137 }
138 }
139
140 #[must_use]
142 pub(in crate::db) fn planner_route_profile(&self, model: &EntityModel) -> PlannerRouteProfile {
143 PlannerRouteProfile::new(
144 derive_continuation_policy_validated(self),
145 derive_logical_pushdown_eligibility(model, self),
146 )
147 }
148
149 #[must_use]
151 pub(in crate::db) fn execution_shape_signature(
152 &self,
153 entity_path: &'static str,
154 ) -> ExecutionShapeSignature {
155 ExecutionShapeSignature::new(self.continuation_signature(entity_path))
156 }
157
158 #[must_use]
161 pub(in crate::db) fn predicate_fully_satisfied_by_filtered_access(&self) -> bool {
162 let Some(query_predicate) = self.scalar_plan().predicate.as_ref() else {
163 return false;
164 };
165 let Some(index) = self.access.selected_index_model() else {
166 return false;
167 };
168
169 filtered_index_predicate_satisfies_query(index, query_predicate)
170 }
171
172 #[must_use]
175 pub(in crate::db) fn has_residual_predicate(&self) -> bool {
176 self.scalar_plan().predicate.is_some()
177 && !self.predicate_fully_satisfied_by_filtered_access()
178 }
179}
180
181fn distinct_runtime_dedup_strategy<K>(access: &AccessPlan<K>) -> Option<DistinctExecutionStrategy> {
182 match access {
183 AccessPlan::Union(_) | AccessPlan::Intersection(_) => {
184 Some(DistinctExecutionStrategy::PreOrdered)
185 }
186 AccessPlan::Path(path) if path.as_ref().is_index_multi_lookup() => {
187 Some(DistinctExecutionStrategy::HashMaterialize)
188 }
189 AccessPlan::Path(_) => None,
190 }
191}
192
193fn derive_continuation_policy_validated(plan: &AccessPlannedQuery) -> ContinuationPolicy {
194 let is_grouped_safe = plan
195 .grouped_plan()
196 .is_none_or(|grouped| grouped_cursor_policy_violation(grouped, true).is_none());
197
198 ContinuationPolicy::new(
199 true, true, is_grouped_safe,
202 )
203}