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 => 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
354impl ExplainExecutionNodeDescriptor {
355    /// Return node type.
356    #[must_use]
357    pub const fn node_type(&self) -> ExplainExecutionNodeType {
358        self.node_type
359    }
360
361    /// Return execution mode.
362    #[must_use]
363    pub const fn execution_mode(&self) -> ExplainExecutionMode {
364        self.execution_mode
365    }
366
367    /// Borrow optional access strategy annotation.
368    #[must_use]
369    pub const fn access_strategy(&self) -> Option<&ExplainAccessPath> {
370        self.access_strategy.as_ref()
371    }
372
373    /// Borrow optional predicate pushdown annotation.
374    #[must_use]
375    pub fn predicate_pushdown(&self) -> Option<&str> {
376        self.predicate_pushdown.as_deref()
377    }
378
379    /// Borrow optional residual predicate annotation.
380    #[must_use]
381    pub const fn residual_predicate(&self) -> Option<&ExplainPredicate> {
382        self.residual_predicate.as_ref()
383    }
384
385    /// Borrow optional projection annotation.
386    #[must_use]
387    pub fn projection(&self) -> Option<&str> {
388        self.projection.as_deref()
389    }
390
391    /// Return optional ordering source annotation.
392    #[must_use]
393    pub const fn ordering_source(&self) -> Option<ExplainExecutionOrderingSource> {
394        self.ordering_source
395    }
396
397    /// Return optional limit annotation.
398    #[must_use]
399    pub const fn limit(&self) -> Option<u32> {
400        self.limit
401    }
402
403    /// Return optional continuation annotation.
404    #[must_use]
405    pub const fn cursor(&self) -> Option<bool> {
406        self.cursor
407    }
408
409    /// Return optional covering-scan annotation.
410    #[must_use]
411    pub const fn covering_scan(&self) -> Option<bool> {
412        self.covering_scan
413    }
414
415    /// Return optional row-count expectation annotation.
416    #[must_use]
417    pub const fn rows_expected(&self) -> Option<u64> {
418        self.rows_expected
419    }
420
421    /// Borrow child execution nodes.
422    #[must_use]
423    pub const fn children(&self) -> &[Self] {
424        self.children.as_slice()
425    }
426
427    /// Borrow node properties.
428    #[must_use]
429    pub const fn node_properties(&self) -> &BTreeMap<String, Value> {
430        &self.node_properties
431    }
432}
433
434pub(in crate::db::query::explain) const fn execution_mode_label(
435    mode: ExplainExecutionMode,
436) -> &'static str {
437    match mode {
438        ExplainExecutionMode::Streaming => "Streaming",
439        ExplainExecutionMode::Materialized => "Materialized",
440    }
441}
442
443pub(in crate::db::query::explain) const fn ordering_source_label(
444    ordering_source: ExplainExecutionOrderingSource,
445) -> &'static str {
446    match ordering_source {
447        ExplainExecutionOrderingSource::AccessOrder => "AccessOrder",
448        ExplainExecutionOrderingSource::Materialized => "Materialized",
449        ExplainExecutionOrderingSource::IndexSeekFirst { .. } => "IndexSeekFirst",
450        ExplainExecutionOrderingSource::IndexSeekLast { .. } => "IndexSeekLast",
451    }
452}