Skip to main content

icydb_core/db/query/
diagnostics.rs

1//! Public, read-only diagnostics for query planning and execution.
2//!
3//! Diagnostics contract:
4//! - `ExplainPlan` is deterministic for equivalent queries and plans.
5//! - `PlanFingerprint` is stable within a major version (inputs are normalized).
6//! - Execution trace events are best-effort diagnostics and may evolve.
7//! - Diagnostics never execute queries unless explicitly requested.
8//! - Diagnostics are observational only; they are not correctness proofs.
9
10use crate::db::query::plan::{AccessPath, AccessPlan, ExplainPlan, PlanFingerprint};
11
12///
13/// QueryDiagnostics
14///
15/// Read-only planning diagnostics derived from a `Query`.
16///
17
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct QueryDiagnostics {
20    pub explain: ExplainPlan,
21    pub fingerprint: PlanFingerprint,
22}
23
24impl From<ExplainPlan> for QueryDiagnostics {
25    fn from(explain: ExplainPlan) -> Self {
26        let fingerprint = explain.fingerprint();
27        Self {
28            explain,
29            fingerprint,
30        }
31    }
32}
33
34///
35/// QueryExecutionDiagnostics
36///
37/// Read-only execution diagnostics emitted for a single query execution.
38///
39
40#[derive(Clone, Debug, Eq, PartialEq)]
41pub struct QueryExecutionDiagnostics {
42    pub fingerprint: PlanFingerprint,
43    pub events: Vec<QueryTraceEvent>,
44}
45
46pub use crate::db::executor::trace::{QueryTraceEvent, TraceAccess, TraceExecutorKind};
47
48/// Public alias for trace access kinds in query diagnostics.
49pub type QueryTraceAccess = TraceAccess;
50
51/// Public alias for trace executor kinds in query diagnostics.
52pub type QueryTraceExecutorKind = TraceExecutorKind;
53
54pub(crate) fn trace_access_from_plan(plan: &AccessPlan) -> Option<TraceAccess> {
55    match plan {
56        AccessPlan::Path(path) => Some(trace_access_from_path(path)),
57        AccessPlan::Union(_) | AccessPlan::Intersection(_) => None,
58    }
59}
60
61fn trace_access_from_path(path: &AccessPath) -> TraceAccess {
62    match path {
63        AccessPath::ByKey(_) => TraceAccess::ByKey,
64        AccessPath::ByKeys(keys) => TraceAccess::ByKeys {
65            count: u32::try_from(keys.len()).unwrap_or(u32::MAX),
66        },
67        AccessPath::KeyRange { .. } => TraceAccess::KeyRange,
68        AccessPath::IndexPrefix { index, values } => TraceAccess::IndexPrefix {
69            name: index.name,
70            prefix_len: u32::try_from(values.len()).unwrap_or(u32::MAX),
71        },
72        AccessPath::FullScan => TraceAccess::FullScan,
73    }
74}
75
76#[must_use]
77pub const fn start_event(
78    fingerprint: PlanFingerprint,
79    access: Option<TraceAccess>,
80    executor: TraceExecutorKind,
81) -> QueryTraceEvent {
82    QueryTraceEvent::Start {
83        fingerprint,
84        executor,
85        access,
86    }
87}
88
89#[must_use]
90pub const fn finish_event(
91    fingerprint: PlanFingerprint,
92    access: Option<TraceAccess>,
93    executor: TraceExecutorKind,
94    rows: u64,
95) -> QueryTraceEvent {
96    QueryTraceEvent::Finish {
97        fingerprint,
98        executor,
99        access,
100        rows,
101    }
102}