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.
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 grouped semantic fields when this plan is grouped.
82    #[must_use]
83    pub(in crate::db) const fn grouped_plan(&self) -> Option<&GroupPlan> {
84        match &self.logical {
85            LogicalPlan::Scalar(_) => None,
86            LogicalPlan::Grouped(plan) => Some(plan),
87        }
88    }
89
90    /// Lower this plan into one canonical planner-owned projection semantic spec.
91    #[must_use]
92    pub(in crate::db) fn projection_spec(&self, model: &EntityModel) -> ProjectionSpec {
93        lower_projection_intent(model, &self.logical, &self.projection_selection)
94    }
95
96    /// Lower this plan into one projection semantic shape for identity hashing.
97    #[must_use]
98    pub(in crate::db::query) fn projection_spec_for_identity(&self) -> ProjectionSpec {
99        lower_projection_identity(&self.logical)
100    }
101
102    /// Lower scalar DISTINCT semantics into one executor-facing execution strategy.
103    #[must_use]
104    pub(in crate::db) fn distinct_execution_strategy(&self) -> DistinctExecutionStrategy {
105        if !self.scalar_plan().distinct {
106            return DistinctExecutionStrategy::None;
107        }
108
109        // DISTINCT on duplicate-safe single-path access shapes is a planner
110        // no-op for runtime dedup mechanics. Composite shapes can surface
111        // duplicate keys and therefore retain explicit dedup execution.
112        match distinct_runtime_dedup_strategy(&self.access) {
113            Some(strategy) => strategy,
114            None => DistinctExecutionStrategy::None,
115        }
116    }
117
118    /// Project one planner-owned route profile for executor route planning.
119    #[must_use]
120    pub(in crate::db) fn planner_route_profile(&self, model: &EntityModel) -> PlannerRouteProfile {
121        PlannerRouteProfile::new(
122            derive_continuation_policy_validated(self),
123            derive_logical_pushdown_eligibility(model, self),
124        )
125    }
126
127    /// Build one immutable execution-shape signature contract for runtime layers.
128    #[must_use]
129    pub(in crate::db) fn execution_shape_signature(
130        &self,
131        entity_path: &'static str,
132    ) -> ExecutionShapeSignature {
133        ExecutionShapeSignature::new(self.continuation_signature(entity_path))
134    }
135
136    /// Borrow scalar semantic fields mutably across logical variants.
137    #[must_use]
138    #[cfg(test)]
139    pub(in crate::db) const fn scalar_plan_mut(&mut self) -> &mut ScalarPlan {
140        self.logical.scalar_semantics_mut()
141    }
142
143    /// Test-only shorthand for explicit scalar plan borrowing.
144    #[must_use]
145    #[cfg(test)]
146    pub(in crate::db) const fn scalar(&self) -> &ScalarPlan {
147        self.scalar_plan()
148    }
149
150    /// Test-only shorthand for explicit mutable scalar plan borrowing.
151    #[must_use]
152    #[cfg(test)]
153    pub(in crate::db) const fn scalar_mut(&mut self) -> &mut ScalarPlan {
154        self.scalar_plan_mut()
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}