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::{
7    access::{AccessPath, AccessPlan},
8    direction::Direction,
9    query::plan::OrderDirection,
10};
11
12///
13/// ExecutionAccessPathVariant
14///
15/// Coarse access path shape used by the load execution trace surface.
16///
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum ExecutionAccessPathVariant {
20    ByKey,
21    ByKeys,
22    KeyRange,
23    IndexPrefix,
24    IndexRange,
25    FullScan,
26    Union,
27    Intersection,
28}
29
30///
31/// ExecutionOptimization
32///
33/// Canonical load optimization selected by execution, if any.
34///
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub enum ExecutionOptimization {
38    PrimaryKey,
39    SecondaryOrderPushdown,
40    IndexRangeLimitPushdown,
41}
42
43///
44/// ExecutionTrace
45///
46/// Structured, opt-in load execution introspection snapshot.
47/// Captures plan-shape and execution decisions without changing semantics.
48///
49
50#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51pub struct ExecutionTrace {
52    pub access_path_variant: ExecutionAccessPathVariant,
53    pub direction: OrderDirection,
54    pub optimization: Option<ExecutionOptimization>,
55    pub keys_scanned: u64,
56    pub rows_returned: u64,
57    pub continuation_applied: bool,
58    pub index_predicate_applied: bool,
59    pub index_predicate_keys_rejected: u64,
60    pub distinct_keys_deduped: u64,
61}
62
63impl ExecutionTrace {
64    /// Build one trace payload from canonical access shape and runtime direction.
65    #[must_use]
66    pub(in crate::db) fn new<K>(
67        access: &AccessPlan<K>,
68        direction: Direction,
69        continuation_applied: bool,
70    ) -> Self {
71        Self {
72            access_path_variant: access_path_variant(access),
73            direction: execution_order_direction(direction),
74            optimization: None,
75            keys_scanned: 0,
76            rows_returned: 0,
77            continuation_applied,
78            index_predicate_applied: false,
79            index_predicate_keys_rejected: 0,
80            distinct_keys_deduped: 0,
81        }
82    }
83
84    /// Apply one finalized path outcome to this trace snapshot.
85    pub(in crate::db) fn set_path_outcome(
86        &mut self,
87        optimization: Option<ExecutionOptimization>,
88        keys_scanned: usize,
89        rows_returned: usize,
90        index_predicate_applied: bool,
91        index_predicate_keys_rejected: u64,
92        distinct_keys_deduped: u64,
93    ) {
94        self.optimization = optimization;
95        self.keys_scanned = u64::try_from(keys_scanned).unwrap_or(u64::MAX);
96        self.rows_returned = u64::try_from(rows_returned).unwrap_or(u64::MAX);
97        self.index_predicate_applied = index_predicate_applied;
98        self.index_predicate_keys_rejected = index_predicate_keys_rejected;
99        self.distinct_keys_deduped = distinct_keys_deduped;
100        debug_assert_eq!(
101            self.keys_scanned,
102            u64::try_from(keys_scanned).unwrap_or(u64::MAX),
103            "execution trace keys_scanned must match rows_scanned metrics input",
104        );
105    }
106}
107
108fn access_path_variant<K>(access: &AccessPlan<K>) -> ExecutionAccessPathVariant {
109    match access {
110        AccessPlan::Path(path) => match path.as_ref() {
111            AccessPath::ByKey(_) => ExecutionAccessPathVariant::ByKey,
112            AccessPath::ByKeys(_) => ExecutionAccessPathVariant::ByKeys,
113            AccessPath::KeyRange { .. } => ExecutionAccessPathVariant::KeyRange,
114            AccessPath::IndexPrefix { .. } => ExecutionAccessPathVariant::IndexPrefix,
115            AccessPath::IndexRange { .. } => ExecutionAccessPathVariant::IndexRange,
116            AccessPath::FullScan => ExecutionAccessPathVariant::FullScan,
117        },
118        AccessPlan::Union(_) => ExecutionAccessPathVariant::Union,
119        AccessPlan::Intersection(_) => ExecutionAccessPathVariant::Intersection,
120    }
121}
122
123const fn execution_order_direction(direction: Direction) -> OrderDirection {
124    match direction {
125        Direction::Asc => OrderDirection::Asc,
126        Direction::Desc => OrderDirection::Desc,
127    }
128}