use crate::db::{
access::{AccessPathKind, AccessPlan, AccessPlanDispatch, dispatch_access_plan},
direction::Direction,
query::plan::OrderDirection,
};
#[cfg_attr(
doc,
doc = "ExecutionAccessPathVariant\n\nCoarse access path shape recorded in execution traces."
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ExecutionAccessPathVariant {
ByKey,
ByKeys,
KeyRange,
IndexPrefix,
IndexMultiLookup,
IndexRange,
FullScan,
Union,
Intersection,
}
#[cfg_attr(
doc,
doc = "ExecutionOptimization\n\nLoad optimization selected at execution time, if any."
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ExecutionOptimization {
PrimaryKey,
PrimaryKeyTopNSeek,
SecondaryOrderPushdown,
SecondaryOrderTopNSeek,
IndexRangeLimitPushdown,
}
#[cfg_attr(
doc,
doc = "ExecutionTrace\n\nStructured execution trace snapshot for one load path.\nCaptures plan shape and counters without affecting behavior."
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ExecutionTrace {
pub(crate) access_path_variant: ExecutionAccessPathVariant,
pub(crate) direction: OrderDirection,
pub(crate) optimization: Option<ExecutionOptimization>,
pub(crate) keys_scanned: u64,
pub(crate) rows_materialized: u64,
pub(crate) rows_returned: u64,
pub(crate) execution_time_micros: u64,
pub(crate) index_only: bool,
pub(crate) continuation_applied: bool,
pub(crate) index_predicate_applied: bool,
pub(crate) index_predicate_keys_rejected: u64,
pub(crate) distinct_keys_deduped: u64,
}
#[cfg_attr(
doc,
doc = "ExecutionMetrics\n\nCompact metrics view derived from one `ExecutionTrace`.\nKept small for lightweight observability surfaces."
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ExecutionMetrics {
pub(crate) rows_scanned: u64,
pub(crate) rows_materialized: u64,
pub(crate) execution_time_micros: u64,
pub(crate) index_only: bool,
}
impl ExecutionTrace {
#[must_use]
pub(in crate::db) fn new<K>(
access: &AccessPlan<K>,
direction: Direction,
continuation_applied: bool,
) -> Self {
Self {
access_path_variant: access_path_variant(access),
direction: execution_order_direction(direction),
optimization: None,
keys_scanned: 0,
rows_materialized: 0,
rows_returned: 0,
execution_time_micros: 0,
index_only: false,
continuation_applied,
index_predicate_applied: false,
index_predicate_keys_rejected: 0,
distinct_keys_deduped: 0,
}
}
#[expect(clippy::too_many_arguments)]
pub(in crate::db) fn set_path_outcome(
&mut self,
optimization: Option<ExecutionOptimization>,
keys_scanned: usize,
rows_materialized: usize,
rows_returned: usize,
execution_time_micros: u64,
index_only: bool,
index_predicate_applied: bool,
index_predicate_keys_rejected: u64,
distinct_keys_deduped: u64,
) {
self.optimization = optimization;
self.keys_scanned = u64::try_from(keys_scanned).unwrap_or(u64::MAX);
self.rows_materialized = u64::try_from(rows_materialized).unwrap_or(u64::MAX);
self.rows_returned = u64::try_from(rows_returned).unwrap_or(u64::MAX);
self.execution_time_micros = execution_time_micros;
self.index_only = index_only;
self.index_predicate_applied = index_predicate_applied;
self.index_predicate_keys_rejected = index_predicate_keys_rejected;
self.distinct_keys_deduped = distinct_keys_deduped;
debug_assert_eq!(
self.keys_scanned,
u64::try_from(keys_scanned).unwrap_or(u64::MAX),
"execution trace keys_scanned must match rows_scanned metrics input",
);
}
#[must_use]
pub const fn metrics(&self) -> ExecutionMetrics {
ExecutionMetrics {
rows_scanned: self.keys_scanned,
rows_materialized: self.rows_materialized,
execution_time_micros: self.execution_time_micros,
index_only: self.index_only,
}
}
#[must_use]
pub const fn access_path_variant(&self) -> ExecutionAccessPathVariant {
self.access_path_variant
}
#[must_use]
pub const fn direction(&self) -> OrderDirection {
self.direction
}
#[must_use]
pub const fn optimization(&self) -> Option<ExecutionOptimization> {
self.optimization
}
#[must_use]
pub const fn keys_scanned(&self) -> u64 {
self.keys_scanned
}
#[must_use]
pub const fn rows_materialized(&self) -> u64 {
self.rows_materialized
}
#[must_use]
pub const fn rows_returned(&self) -> u64 {
self.rows_returned
}
#[must_use]
pub const fn execution_time_micros(&self) -> u64 {
self.execution_time_micros
}
#[must_use]
pub const fn index_only(&self) -> bool {
self.index_only
}
#[must_use]
pub const fn continuation_applied(&self) -> bool {
self.continuation_applied
}
#[must_use]
pub const fn index_predicate_applied(&self) -> bool {
self.index_predicate_applied
}
#[must_use]
pub const fn index_predicate_keys_rejected(&self) -> u64 {
self.index_predicate_keys_rejected
}
#[must_use]
pub const fn distinct_keys_deduped(&self) -> u64 {
self.distinct_keys_deduped
}
}
impl ExecutionMetrics {
#[must_use]
pub const fn rows_scanned(&self) -> u64 {
self.rows_scanned
}
#[must_use]
pub const fn rows_materialized(&self) -> u64 {
self.rows_materialized
}
#[must_use]
pub const fn execution_time_micros(&self) -> u64 {
self.execution_time_micros
}
#[must_use]
pub const fn index_only(&self) -> bool {
self.index_only
}
}
fn access_path_variant<K>(access: &AccessPlan<K>) -> ExecutionAccessPathVariant {
match dispatch_access_plan(access) {
AccessPlanDispatch::Path(path) => match path.kind() {
AccessPathKind::ByKey => ExecutionAccessPathVariant::ByKey,
AccessPathKind::ByKeys => ExecutionAccessPathVariant::ByKeys,
AccessPathKind::KeyRange => ExecutionAccessPathVariant::KeyRange,
AccessPathKind::IndexPrefix => ExecutionAccessPathVariant::IndexPrefix,
AccessPathKind::IndexMultiLookup => ExecutionAccessPathVariant::IndexMultiLookup,
AccessPathKind::IndexRange => ExecutionAccessPathVariant::IndexRange,
AccessPathKind::FullScan => ExecutionAccessPathVariant::FullScan,
},
AccessPlanDispatch::Union(_) => ExecutionAccessPathVariant::Union,
AccessPlanDispatch::Intersection(_) => ExecutionAccessPathVariant::Intersection,
}
}
const fn execution_order_direction(direction: Direction) -> OrderDirection {
match direction {
Direction::Asc => OrderDirection::Asc,
Direction::Desc => OrderDirection::Desc,
}
}