Skip to main content

icydb_core/db/diagnostics/
execution_trace.rs

1//! Module: diagnostics::execution_trace
2//! Responsibility: execution trace contracts and mutation boundaries.
3//! Does not own: execution routing policy or stream/materialization behavior.
4//! Boundary: shared trace surface used by executor and response APIs.
5
6use crate::db::{executor::ExecutionOptimization, query::plan::OrderDirection};
7
8#[cfg_attr(
9    doc,
10    doc = "ExecutionAccessPathVariant\n\nCoarse access path shape recorded in execution traces."
11)]
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub enum ExecutionAccessPathVariant {
14    ByKey,
15    ByKeys,
16    KeyRange,
17    IndexPrefix,
18    IndexMultiLookup,
19    IndexRange,
20    FullScan,
21    Union,
22    Intersection,
23}
24
25#[cfg_attr(
26    doc,
27    doc = "ExecutionTrace\n\nStructured execution trace snapshot for one load path.\nCaptures plan shape and counters without affecting behavior."
28)]
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub struct ExecutionTrace {
31    pub(crate) access_path_variant: ExecutionAccessPathVariant,
32    pub(crate) direction: OrderDirection,
33    pub(crate) optimization: Option<ExecutionOptimization>,
34    pub(crate) keys_scanned: u64,
35    pub(crate) rows_materialized: u64,
36    pub(crate) rows_returned: u64,
37    pub(crate) execution_time_micros: u64,
38    pub(crate) index_only: bool,
39    pub(crate) continuation_applied: bool,
40    pub(crate) index_predicate_applied: bool,
41    pub(crate) index_predicate_keys_rejected: u64,
42    pub(crate) distinct_keys_deduped: u64,
43}
44
45#[cfg_attr(
46    doc,
47    doc = "ExecutionMetrics\n\nCompact metrics view derived from one `ExecutionTrace`.\nKept small for lightweight observability surfaces."
48)]
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub struct ExecutionMetrics {
51    pub(crate) rows_scanned: u64,
52    pub(crate) rows_materialized: u64,
53    pub(crate) execution_time_micros: u64,
54    pub(crate) index_only: bool,
55}
56
57impl ExecutionTrace {
58    /// Build one trace payload from an executor-projected access shape.
59    #[must_use]
60    pub(crate) const fn new_from_variant(
61        access_path_variant: ExecutionAccessPathVariant,
62        direction: OrderDirection,
63        continuation_applied: bool,
64    ) -> Self {
65        Self {
66            access_path_variant,
67            direction,
68            optimization: None,
69            keys_scanned: 0,
70            rows_materialized: 0,
71            rows_returned: 0,
72            execution_time_micros: 0,
73            index_only: false,
74            continuation_applied,
75            index_predicate_applied: false,
76            index_predicate_keys_rejected: 0,
77            distinct_keys_deduped: 0,
78        }
79    }
80
81    /// Apply one finalized path outcome to this trace snapshot.
82    #[expect(clippy::too_many_arguments)]
83    pub(in crate::db) fn set_path_outcome(
84        &mut self,
85        optimization: Option<ExecutionOptimization>,
86        keys_scanned: usize,
87        rows_materialized: usize,
88        rows_returned: usize,
89        execution_time_micros: u64,
90        index_only: bool,
91        index_predicate_applied: bool,
92        index_predicate_keys_rejected: u64,
93        distinct_keys_deduped: u64,
94    ) {
95        self.optimization = optimization;
96        self.keys_scanned = u64::try_from(keys_scanned).unwrap_or(u64::MAX);
97        self.rows_materialized = u64::try_from(rows_materialized).unwrap_or(u64::MAX);
98        self.rows_returned = u64::try_from(rows_returned).unwrap_or(u64::MAX);
99        self.execution_time_micros = execution_time_micros;
100        self.index_only = index_only;
101        self.index_predicate_applied = index_predicate_applied;
102        self.index_predicate_keys_rejected = index_predicate_keys_rejected;
103        self.distinct_keys_deduped = distinct_keys_deduped;
104        debug_assert_eq!(
105            self.keys_scanned,
106            u64::try_from(keys_scanned).unwrap_or(u64::MAX),
107            "execution trace keys_scanned must match rows_scanned metrics input",
108        );
109    }
110
111    /// Return compact execution metrics for pre-EXPLAIN observability surfaces.
112    #[must_use]
113    pub const fn metrics(&self) -> ExecutionMetrics {
114        ExecutionMetrics {
115            rows_scanned: self.keys_scanned,
116            rows_materialized: self.rows_materialized,
117            execution_time_micros: self.execution_time_micros,
118            index_only: self.index_only,
119        }
120    }
121
122    /// Return the coarse executed access-path variant.
123    #[must_use]
124    pub const fn access_path_variant(&self) -> ExecutionAccessPathVariant {
125        self.access_path_variant
126    }
127
128    /// Return executed order direction.
129    #[must_use]
130    pub const fn direction(&self) -> OrderDirection {
131        self.direction
132    }
133
134    /// Return selected optimization, if any.
135    #[must_use]
136    pub const fn optimization(&self) -> Option<ExecutionOptimization> {
137        self.optimization
138    }
139
140    /// Return number of keys scanned.
141    #[must_use]
142    pub const fn keys_scanned(&self) -> u64 {
143        self.keys_scanned
144    }
145
146    /// Return number of rows materialized.
147    #[must_use]
148    pub const fn rows_materialized(&self) -> u64 {
149        self.rows_materialized
150    }
151
152    /// Return number of rows returned.
153    #[must_use]
154    pub const fn rows_returned(&self) -> u64 {
155        self.rows_returned
156    }
157
158    /// Return execution time in microseconds.
159    #[must_use]
160    pub const fn execution_time_micros(&self) -> u64 {
161        self.execution_time_micros
162    }
163
164    /// Return whether execution remained index-only.
165    #[must_use]
166    pub const fn index_only(&self) -> bool {
167        self.index_only
168    }
169
170    /// Return whether continuation was applied.
171    #[must_use]
172    pub const fn continuation_applied(&self) -> bool {
173        self.continuation_applied
174    }
175
176    /// Return whether index predicate pushdown was applied.
177    #[must_use]
178    pub const fn index_predicate_applied(&self) -> bool {
179        self.index_predicate_applied
180    }
181
182    /// Return number of keys rejected by index predicate pushdown.
183    #[must_use]
184    pub const fn index_predicate_keys_rejected(&self) -> u64 {
185        self.index_predicate_keys_rejected
186    }
187
188    /// Return number of deduplicated keys under DISTINCT processing.
189    #[must_use]
190    pub const fn distinct_keys_deduped(&self) -> u64 {
191        self.distinct_keys_deduped
192    }
193}
194
195impl ExecutionMetrics {
196    /// Return number of rows scanned.
197    #[must_use]
198    pub const fn rows_scanned(&self) -> u64 {
199        self.rows_scanned
200    }
201
202    /// Return number of rows materialized.
203    #[must_use]
204    pub const fn rows_materialized(&self) -> u64 {
205        self.rows_materialized
206    }
207
208    /// Return execution time in microseconds.
209    #[must_use]
210    pub const fn execution_time_micros(&self) -> u64 {
211        self.execution_time_micros
212    }
213
214    /// Return whether execution remained index-only.
215    #[must_use]
216    pub const fn index_only(&self) -> bool {
217        self.index_only
218    }
219}