Skip to main content

icydb_core/db/query/explain/
execution.rs

1//! Module: query::explain::execution
2//! Responsibility: stable execution-descriptor vocabulary for EXPLAIN.
3//! Does not own: logical plan projection or rendering logic.
4//! Boundary: execution descriptor types consumed by explain renderers.
5
6use crate::{
7    db::query::{
8        explain::{ExplainAccessPath, ExplainPlan, ExplainPredicate},
9        plan::AggregateKind,
10    },
11    value::Value,
12};
13use std::collections::BTreeMap;
14
15///
16/// ExplainAggregateTerminalRoute
17///
18/// Executor-projected scalar aggregate terminal route label for explain output.
19/// Keeps seek-edge fast-path labels explicit without exposing route internals.
20///
21
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23pub enum ExplainAggregateTerminalRoute {
24    Standard,
25    IndexSeekFirst { fetch: usize },
26    IndexSeekLast { fetch: usize },
27}
28
29///
30/// ExplainAggregateTerminalPlan
31///
32/// Combined explain payload for one scalar aggregate terminal request.
33/// Includes logical explain projection plus executor route label.
34///
35
36#[derive(Clone, Debug, Eq, PartialEq)]
37pub struct ExplainAggregateTerminalPlan {
38    pub(crate) query: ExplainPlan,
39    pub(crate) terminal: AggregateKind,
40    pub(crate) route: ExplainAggregateTerminalRoute,
41    pub(crate) execution: ExplainExecutionDescriptor,
42}
43
44///
45/// ExplainExecutionOrderingSource
46///
47/// Stable ordering-origin projection used by terminal execution explain output.
48/// This keeps index-seek labels and materialized fallback labels explicit.
49///
50
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
52pub enum ExplainExecutionOrderingSource {
53    AccessOrder,
54    Materialized,
55    IndexSeekFirst { fetch: usize },
56    IndexSeekLast { fetch: usize },
57}
58
59///
60/// ExplainExecutionMode
61///
62/// Stable execution-mode projection used by execution explain descriptors.
63///
64
65#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66pub enum ExplainExecutionMode {
67    Streaming,
68    Materialized,
69}
70
71///
72/// ExplainExecutionDescriptor
73///
74/// Stable scalar execution descriptor consumed by terminal EXPLAIN surfaces.
75/// This keeps execution authority projection centralized and avoids ad-hoc
76/// terminal-specific explain branching at call sites.
77///
78
79#[derive(Clone, Debug, Eq, PartialEq)]
80pub struct ExplainExecutionDescriptor {
81    pub(crate) access_strategy: ExplainAccessPath,
82    pub(crate) covering_projection: bool,
83    pub(crate) aggregation: AggregateKind,
84    pub(crate) execution_mode: ExplainExecutionMode,
85    pub(crate) ordering_source: ExplainExecutionOrderingSource,
86    pub(crate) limit: Option<u32>,
87    pub(crate) cursor: bool,
88    pub(crate) node_properties: BTreeMap<String, Value>,
89}
90
91///
92/// ExplainExecutionNodeType
93///
94/// Stable execution-node vocabulary for EXPLAIN descriptor projection.
95///
96
97#[derive(Clone, Copy, Debug, Eq, PartialEq)]
98pub enum ExplainExecutionNodeType {
99    ByKeyLookup,
100    ByKeysLookup,
101    PrimaryKeyRangeScan,
102    IndexPrefixScan,
103    IndexRangeScan,
104    IndexMultiLookup,
105    FullScan,
106    Union,
107    Intersection,
108    IndexPredicatePrefilter,
109    ResidualPredicateFilter,
110    OrderByAccessSatisfied,
111    OrderByMaterializedSort,
112    DistinctPreOrdered,
113    DistinctMaterialized,
114    ProjectionMaterialized,
115    ProjectionIndexOnly,
116    LimitOffset,
117    CursorResume,
118    IndexRangeLimitPushdown,
119    TopNSeek,
120    AggregateCount,
121    AggregateExists,
122    AggregateMin,
123    AggregateMax,
124    AggregateFirst,
125    AggregateLast,
126    AggregateSum,
127    AggregateSeekFirst,
128    AggregateSeekLast,
129    GroupedAggregateHashMaterialized,
130    GroupedAggregateOrderedMaterialized,
131    SecondaryOrderPushdown,
132}
133
134///
135/// ExplainExecutionNodeDescriptor
136///
137/// Canonical execution-node descriptor used by EXPLAIN text/verbose/json
138/// renderers. Optional fields are node-family specific and are additive.
139///
140
141#[derive(Clone, Debug, Eq, PartialEq)]
142pub struct ExplainExecutionNodeDescriptor {
143    pub(crate) node_type: ExplainExecutionNodeType,
144    pub(crate) execution_mode: ExplainExecutionMode,
145    pub(crate) access_strategy: Option<ExplainAccessPath>,
146    pub(crate) predicate_pushdown: Option<String>,
147    pub(crate) residual_predicate: Option<ExplainPredicate>,
148    pub(crate) projection: Option<String>,
149    pub(crate) ordering_source: Option<ExplainExecutionOrderingSource>,
150    pub(crate) limit: Option<u32>,
151    pub(crate) cursor: Option<bool>,
152    pub(crate) covering_scan: Option<bool>,
153    pub(crate) rows_expected: Option<u64>,
154    pub(crate) children: Vec<Self>,
155    pub(crate) node_properties: BTreeMap<String, Value>,
156}
157
158impl ExplainAggregateTerminalPlan {
159    /// Borrow the underlying query explain payload.
160    #[must_use]
161    pub const fn query(&self) -> &ExplainPlan {
162        &self.query
163    }
164
165    /// Return terminal aggregate kind.
166    #[must_use]
167    pub const fn terminal(&self) -> AggregateKind {
168        self.terminal
169    }
170
171    /// Return projected aggregate terminal route.
172    #[must_use]
173    pub const fn route(&self) -> ExplainAggregateTerminalRoute {
174        self.route
175    }
176
177    /// Borrow projected execution descriptor.
178    #[must_use]
179    pub const fn execution(&self) -> &ExplainExecutionDescriptor {
180        &self.execution
181    }
182
183    #[must_use]
184    pub(in crate::db) const fn new(
185        query: ExplainPlan,
186        terminal: AggregateKind,
187        execution: ExplainExecutionDescriptor,
188    ) -> Self {
189        let route = execution.route();
190
191        Self {
192            query,
193            terminal,
194            route,
195            execution,
196        }
197    }
198}
199
200impl ExplainExecutionDescriptor {
201    /// Borrow projected access strategy.
202    #[must_use]
203    pub const fn access_strategy(&self) -> &ExplainAccessPath {
204        &self.access_strategy
205    }
206
207    /// Return whether projection can be served from index payload only.
208    #[must_use]
209    pub const fn covering_projection(&self) -> bool {
210        self.covering_projection
211    }
212
213    /// Return projected aggregate kind.
214    #[must_use]
215    pub const fn aggregation(&self) -> AggregateKind {
216        self.aggregation
217    }
218
219    /// Return projected execution mode.
220    #[must_use]
221    pub const fn execution_mode(&self) -> ExplainExecutionMode {
222        self.execution_mode
223    }
224
225    /// Return projected ordering source.
226    #[must_use]
227    pub const fn ordering_source(&self) -> ExplainExecutionOrderingSource {
228        self.ordering_source
229    }
230
231    /// Return projected execution limit.
232    #[must_use]
233    pub const fn limit(&self) -> Option<u32> {
234        self.limit
235    }
236
237    /// Return whether continuation was applied.
238    #[must_use]
239    pub const fn cursor(&self) -> bool {
240        self.cursor
241    }
242
243    /// Borrow projected execution node properties.
244    #[must_use]
245    pub const fn node_properties(&self) -> &BTreeMap<String, Value> {
246        &self.node_properties
247    }
248
249    #[must_use]
250    pub(in crate::db) const fn route(&self) -> ExplainAggregateTerminalRoute {
251        match self.ordering_source {
252            ExplainExecutionOrderingSource::IndexSeekFirst { fetch } => {
253                ExplainAggregateTerminalRoute::IndexSeekFirst { fetch }
254            }
255            ExplainExecutionOrderingSource::IndexSeekLast { fetch } => {
256                ExplainAggregateTerminalRoute::IndexSeekLast { fetch }
257            }
258            ExplainExecutionOrderingSource::AccessOrder
259            | ExplainExecutionOrderingSource::Materialized => {
260                ExplainAggregateTerminalRoute::Standard
261            }
262        }
263    }
264}
265
266impl ExplainAggregateTerminalPlan {
267    /// Build an execution-node descriptor for aggregate terminal plans.
268    #[must_use]
269    pub fn execution_node_descriptor(&self) -> ExplainExecutionNodeDescriptor {
270        ExplainExecutionNodeDescriptor {
271            node_type: aggregate_execution_node_type(self.terminal, self.execution.ordering_source),
272            execution_mode: self.execution.execution_mode,
273            access_strategy: Some(self.execution.access_strategy.clone()),
274            predicate_pushdown: None,
275            residual_predicate: None,
276            projection: None,
277            ordering_source: Some(self.execution.ordering_source),
278            limit: self.execution.limit,
279            cursor: Some(self.execution.cursor),
280            covering_scan: Some(self.execution.covering_projection),
281            rows_expected: None,
282            children: Vec::new(),
283            node_properties: self.execution.node_properties.clone(),
284        }
285    }
286}
287
288const fn aggregate_execution_node_type(
289    terminal: AggregateKind,
290    ordering_source: ExplainExecutionOrderingSource,
291) -> ExplainExecutionNodeType {
292    match ordering_source {
293        ExplainExecutionOrderingSource::IndexSeekFirst { .. } => {
294            ExplainExecutionNodeType::AggregateSeekFirst
295        }
296        ExplainExecutionOrderingSource::IndexSeekLast { .. } => {
297            ExplainExecutionNodeType::AggregateSeekLast
298        }
299        ExplainExecutionOrderingSource::AccessOrder
300        | ExplainExecutionOrderingSource::Materialized => match terminal {
301            AggregateKind::Count => ExplainExecutionNodeType::AggregateCount,
302            AggregateKind::Exists => ExplainExecutionNodeType::AggregateExists,
303            AggregateKind::Min => ExplainExecutionNodeType::AggregateMin,
304            AggregateKind::Max => ExplainExecutionNodeType::AggregateMax,
305            AggregateKind::First => ExplainExecutionNodeType::AggregateFirst,
306            AggregateKind::Last => ExplainExecutionNodeType::AggregateLast,
307            AggregateKind::Sum | AggregateKind::Avg => ExplainExecutionNodeType::AggregateSum,
308        },
309    }
310}
311
312impl ExplainExecutionNodeType {
313    /// Return the stable string label used by explain renderers.
314    #[must_use]
315    pub const fn as_str(self) -> &'static str {
316        match self {
317            Self::ByKeyLookup => "ByKeyLookup",
318            Self::ByKeysLookup => "ByKeysLookup",
319            Self::PrimaryKeyRangeScan => "PrimaryKeyRangeScan",
320            Self::IndexPrefixScan => "IndexPrefixScan",
321            Self::IndexRangeScan => "IndexRangeScan",
322            Self::IndexMultiLookup => "IndexMultiLookup",
323            Self::FullScan => "FullScan",
324            Self::Union => "Union",
325            Self::Intersection => "Intersection",
326            Self::IndexPredicatePrefilter => "IndexPredicatePrefilter",
327            Self::ResidualPredicateFilter => "ResidualPredicateFilter",
328            Self::OrderByAccessSatisfied => "OrderByAccessSatisfied",
329            Self::OrderByMaterializedSort => "OrderByMaterializedSort",
330            Self::DistinctPreOrdered => "DistinctPreOrdered",
331            Self::DistinctMaterialized => "DistinctMaterialized",
332            Self::ProjectionMaterialized => "ProjectionMaterialized",
333            Self::ProjectionIndexOnly => "ProjectionIndexOnly",
334            Self::LimitOffset => "LimitOffset",
335            Self::CursorResume => "CursorResume",
336            Self::IndexRangeLimitPushdown => "IndexRangeLimitPushdown",
337            Self::TopNSeek => "TopNSeek",
338            Self::AggregateCount => "AggregateCount",
339            Self::AggregateExists => "AggregateExists",
340            Self::AggregateMin => "AggregateMin",
341            Self::AggregateMax => "AggregateMax",
342            Self::AggregateFirst => "AggregateFirst",
343            Self::AggregateLast => "AggregateLast",
344            Self::AggregateSum => "AggregateSum",
345            Self::AggregateSeekFirst => "AggregateSeekFirst",
346            Self::AggregateSeekLast => "AggregateSeekLast",
347            Self::GroupedAggregateHashMaterialized => "GroupedAggregateHashMaterialized",
348            Self::GroupedAggregateOrderedMaterialized => "GroupedAggregateOrderedMaterialized",
349            Self::SecondaryOrderPushdown => "SecondaryOrderPushdown",
350        }
351    }
352
353    /// Return the owning execution layer label for this node type.
354    #[must_use]
355    pub const fn layer_label(self) -> &'static str {
356        crate::db::query::explain::nodes::layer_label(self)
357    }
358}
359
360impl ExplainExecutionNodeDescriptor {
361    /// Return node type.
362    #[must_use]
363    pub const fn node_type(&self) -> ExplainExecutionNodeType {
364        self.node_type
365    }
366
367    /// Return execution mode.
368    #[must_use]
369    pub const fn execution_mode(&self) -> ExplainExecutionMode {
370        self.execution_mode
371    }
372
373    /// Borrow optional access strategy annotation.
374    #[must_use]
375    pub const fn access_strategy(&self) -> Option<&ExplainAccessPath> {
376        self.access_strategy.as_ref()
377    }
378
379    /// Borrow optional predicate pushdown annotation.
380    #[must_use]
381    pub fn predicate_pushdown(&self) -> Option<&str> {
382        self.predicate_pushdown.as_deref()
383    }
384
385    /// Borrow optional residual predicate annotation.
386    #[must_use]
387    pub const fn residual_predicate(&self) -> Option<&ExplainPredicate> {
388        self.residual_predicate.as_ref()
389    }
390
391    /// Borrow optional projection annotation.
392    #[must_use]
393    pub fn projection(&self) -> Option<&str> {
394        self.projection.as_deref()
395    }
396
397    /// Return optional ordering source annotation.
398    #[must_use]
399    pub const fn ordering_source(&self) -> Option<ExplainExecutionOrderingSource> {
400        self.ordering_source
401    }
402
403    /// Return optional limit annotation.
404    #[must_use]
405    pub const fn limit(&self) -> Option<u32> {
406        self.limit
407    }
408
409    /// Return optional continuation annotation.
410    #[must_use]
411    pub const fn cursor(&self) -> Option<bool> {
412        self.cursor
413    }
414
415    /// Return optional covering-scan annotation.
416    #[must_use]
417    pub const fn covering_scan(&self) -> Option<bool> {
418        self.covering_scan
419    }
420
421    /// Return optional row-count expectation annotation.
422    #[must_use]
423    pub const fn rows_expected(&self) -> Option<u64> {
424        self.rows_expected
425    }
426
427    /// Borrow child execution nodes.
428    #[must_use]
429    pub const fn children(&self) -> &[Self] {
430        self.children.as_slice()
431    }
432
433    /// Borrow node properties.
434    #[must_use]
435    pub const fn node_properties(&self) -> &BTreeMap<String, Value> {
436        &self.node_properties
437    }
438}
439
440pub(in crate::db::query::explain) const fn execution_mode_label(
441    mode: ExplainExecutionMode,
442) -> &'static str {
443    match mode {
444        ExplainExecutionMode::Streaming => "Streaming",
445        ExplainExecutionMode::Materialized => "Materialized",
446    }
447}
448
449pub(in crate::db::query::explain) const fn ordering_source_label(
450    ordering_source: ExplainExecutionOrderingSource,
451) -> &'static str {
452    match ordering_source {
453        ExplainExecutionOrderingSource::AccessOrder => "AccessOrder",
454        ExplainExecutionOrderingSource::Materialized => "Materialized",
455        ExplainExecutionOrderingSource::IndexSeekFirst { .. } => "IndexSeekFirst",
456        ExplainExecutionOrderingSource::IndexSeekLast { .. } => "IndexSeekLast",
457    }
458}