Skip to main content

icydb_core/db/query/plan/semantics/
logical.rs

1//! Module: query::plan::semantics::logical
2//! Responsibility: logical-plan semantic lowering from planner contracts to access-planned queries.
3//! Does not own: access-path index selection internals or runtime execution behavior.
4//! Boundary: derives planner-owned execution semantics, shape signatures, and continuation policy.
5
6use 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            grouped_cursor_policy_violation_for_continuation, lower_projection_identity,
14            lower_projection_intent,
15        },
16    },
17    model::entity::EntityModel,
18    traits::FieldValue,
19};
20
21impl QueryMode {
22    /// True if this mode represents a load intent.
23    #[must_use]
24    pub const fn is_load(&self) -> bool {
25        match self {
26            Self::Load(_) => true,
27            Self::Delete(_) => false,
28        }
29    }
30
31    /// True if this mode represents a delete intent.
32    #[must_use]
33    pub const fn is_delete(&self) -> bool {
34        match self {
35            Self::Delete(_) => true,
36            Self::Load(_) => false,
37        }
38    }
39}
40
41impl LogicalPlan {
42    /// Borrow scalar semantic fields shared by scalar/grouped logical variants.
43    #[must_use]
44    pub(in crate::db) const fn scalar_semantics(&self) -> &ScalarPlan {
45        match self {
46            Self::Scalar(plan) => plan,
47            Self::Grouped(plan) => &plan.scalar,
48        }
49    }
50
51    /// Borrow scalar semantic fields mutably across logical variants.
52    #[must_use]
53    #[cfg(test)]
54    pub(in crate::db) const fn scalar_semantics_mut(&mut self) -> &mut ScalarPlan {
55        match self {
56            Self::Scalar(plan) => plan,
57            Self::Grouped(plan) => &mut plan.scalar,
58        }
59    }
60
61    /// Test-only shorthand for explicit scalar semantic borrowing.
62    #[must_use]
63    #[cfg(test)]
64    pub(in crate::db) const fn scalar(&self) -> &ScalarPlan {
65        self.scalar_semantics()
66    }
67
68    /// Test-only shorthand for explicit mutable scalar semantic borrowing.
69    #[must_use]
70    #[cfg(test)]
71    pub(in crate::db) const fn scalar_mut(&mut self) -> &mut ScalarPlan {
72        self.scalar_semantics_mut()
73    }
74}
75
76impl<K> AccessPlannedQuery<K> {
77    /// Borrow scalar semantic fields shared by scalar/grouped logical variants.
78    #[must_use]
79    pub(in crate::db) const fn scalar_plan(&self) -> &ScalarPlan {
80        self.logical.scalar_semantics()
81    }
82
83    /// Borrow grouped semantic fields when this plan is grouped.
84    #[must_use]
85    pub(in crate::db) const fn grouped_plan(&self) -> Option<&GroupPlan> {
86        match &self.logical {
87            LogicalPlan::Scalar(_) => None,
88            LogicalPlan::Grouped(plan) => Some(plan),
89        }
90    }
91
92    /// Lower this plan into one canonical planner-owned projection semantic spec.
93    #[must_use]
94    pub(in crate::db) fn projection_spec(&self, model: &EntityModel) -> ProjectionSpec {
95        lower_projection_intent(model, &self.logical)
96    }
97
98    /// Lower this plan into one projection semantic shape for identity hashing.
99    #[must_use]
100    pub(in crate::db::query) fn projection_spec_for_identity(&self) -> ProjectionSpec {
101        lower_projection_identity(&self.logical)
102    }
103
104    /// Lower scalar DISTINCT semantics into one executor-facing execution strategy.
105    #[must_use]
106    pub(in crate::db) fn distinct_execution_strategy(&self) -> DistinctExecutionStrategy {
107        if !self.scalar_plan().distinct {
108            return DistinctExecutionStrategy::None;
109        }
110
111        // DISTINCT on duplicate-safe single-path access shapes is a planner
112        // no-op for runtime dedup mechanics. Composite shapes can surface
113        // duplicate keys and therefore retain explicit dedup execution.
114        match distinct_runtime_dedup_strategy(&self.access) {
115            Some(strategy) => strategy,
116            None => DistinctExecutionStrategy::None,
117        }
118    }
119
120    /// Project one planner-owned route profile for executor route planning.
121    #[must_use]
122    pub(in crate::db) fn planner_route_profile(&self, model: &EntityModel) -> PlannerRouteProfile {
123        PlannerRouteProfile::new(
124            derive_continuation_policy_validated(self),
125            derive_logical_pushdown_eligibility(model, self),
126        )
127    }
128
129    /// Build one immutable execution-shape signature contract for runtime layers.
130    #[must_use]
131    pub(in crate::db) fn execution_shape_signature(
132        &self,
133        entity_path: &'static str,
134    ) -> ExecutionShapeSignature
135    where
136        K: FieldValue,
137    {
138        ExecutionShapeSignature::new(self.continuation_signature(entity_path))
139    }
140
141    /// Borrow scalar semantic fields mutably across logical variants.
142    #[must_use]
143    #[cfg(test)]
144    pub(in crate::db) const fn scalar_plan_mut(&mut self) -> &mut ScalarPlan {
145        self.logical.scalar_semantics_mut()
146    }
147
148    /// Test-only shorthand for explicit scalar plan borrowing.
149    #[must_use]
150    #[cfg(test)]
151    pub(in crate::db) const fn scalar(&self) -> &ScalarPlan {
152        self.scalar_plan()
153    }
154
155    /// Test-only shorthand for explicit mutable scalar plan borrowing.
156    #[must_use]
157    #[cfg(test)]
158    pub(in crate::db) const fn scalar_mut(&mut self) -> &mut ScalarPlan {
159        self.scalar_plan_mut()
160    }
161}
162
163fn distinct_runtime_dedup_strategy<K>(access: &AccessPlan<K>) -> Option<DistinctExecutionStrategy> {
164    match access {
165        AccessPlan::Union(_) | AccessPlan::Intersection(_) => {
166            Some(DistinctExecutionStrategy::PreOrdered)
167        }
168        AccessPlan::Path(path) if path.as_ref().is_index_multi_lookup() => {
169            Some(DistinctExecutionStrategy::HashMaterialize)
170        }
171        AccessPlan::Path(_) => None,
172    }
173}
174
175fn derive_continuation_policy_validated<K>(plan: &AccessPlannedQuery<K>) -> ContinuationPolicy {
176    let is_grouped_safe = plan.grouped_plan().is_none_or(|grouped| {
177        grouped_cursor_policy_violation_for_continuation(grouped, true).is_none()
178    });
179
180    ContinuationPolicy::new(
181        true, // Continuation resume windows require anchor semantics for pushdown-safe replay.
182        true, // Continuation resumes must advance strictly to prevent replay/regression loops.
183        is_grouped_safe,
184    )
185}