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