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
50impl AccessPlannedQuery {
51    /// Borrow scalar semantic fields shared by scalar/grouped logical variants.
52    #[must_use]
53    pub(in crate::db) const fn scalar_plan(&self) -> &ScalarPlan {
54        self.logical.scalar_semantics()
55    }
56
57    /// Borrow grouped semantic fields when this plan is grouped.
58    #[must_use]
59    pub(in crate::db) const fn grouped_plan(&self) -> Option<&GroupPlan> {
60        match &self.logical {
61            LogicalPlan::Scalar(_) => None,
62            LogicalPlan::Grouped(plan) => Some(plan),
63        }
64    }
65
66    /// Lower this plan into one canonical planner-owned projection semantic spec.
67    #[must_use]
68    pub(in crate::db) fn projection_spec(&self, model: &EntityModel) -> ProjectionSpec {
69        lower_projection_intent(model, &self.logical, &self.projection_selection)
70    }
71
72    /// Lower this plan into one projection semantic shape for identity hashing.
73    #[must_use]
74    pub(in crate::db::query) fn projection_spec_for_identity(&self) -> ProjectionSpec {
75        lower_projection_identity(&self.logical)
76    }
77
78    /// Lower scalar DISTINCT semantics into one executor-facing execution strategy.
79    #[must_use]
80    pub(in crate::db) fn distinct_execution_strategy(&self) -> DistinctExecutionStrategy {
81        if !self.scalar_plan().distinct {
82            return DistinctExecutionStrategy::None;
83        }
84
85        // DISTINCT on duplicate-safe single-path access shapes is a planner
86        // no-op for runtime dedup mechanics. Composite shapes can surface
87        // duplicate keys and therefore retain explicit dedup execution.
88        match distinct_runtime_dedup_strategy(&self.access) {
89            Some(strategy) => strategy,
90            None => DistinctExecutionStrategy::None,
91        }
92    }
93
94    /// Project one planner-owned route profile for executor route planning.
95    #[must_use]
96    pub(in crate::db) fn planner_route_profile(&self, model: &EntityModel) -> PlannerRouteProfile {
97        PlannerRouteProfile::new(
98            derive_continuation_policy_validated(self),
99            derive_logical_pushdown_eligibility(model, self),
100        )
101    }
102
103    /// Build one immutable execution-shape signature contract for runtime layers.
104    #[must_use]
105    pub(in crate::db) fn execution_shape_signature(
106        &self,
107        entity_path: &'static str,
108    ) -> ExecutionShapeSignature {
109        ExecutionShapeSignature::new(self.continuation_signature(entity_path))
110    }
111}
112
113fn distinct_runtime_dedup_strategy<K>(access: &AccessPlan<K>) -> Option<DistinctExecutionStrategy> {
114    match access {
115        AccessPlan::Union(_) | AccessPlan::Intersection(_) => {
116            Some(DistinctExecutionStrategy::PreOrdered)
117        }
118        AccessPlan::Path(path) if path.as_ref().is_index_multi_lookup() => {
119            Some(DistinctExecutionStrategy::HashMaterialize)
120        }
121        AccessPlan::Path(_) => None,
122    }
123}
124
125fn derive_continuation_policy_validated(plan: &AccessPlannedQuery) -> ContinuationPolicy {
126    let is_grouped_safe = plan
127        .grouped_plan()
128        .is_none_or(|grouped| grouped_cursor_policy_violation(grouped, true).is_none());
129
130    ContinuationPolicy::new(
131        true, // Continuation resume windows require anchor semantics for pushdown-safe replay.
132        true, // Continuation resumes must advance strictly to prevent replay/regression loops.
133        is_grouped_safe,
134    )
135}