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