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
10pub use crate::db::executor::trace::{QueryTraceEvent, TraceAccess, TraceExecutorKind, TracePhase};
11use crate::db::query::plan::{AccessPath, AccessPlan, ExplainPlan, PlanFingerprint};
12
13///
14/// QueryDiagnostics
15///
16/// Read-only planning diagnostics derived from a `Query`.
17///
18
19#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct QueryDiagnostics {
21    pub explain: ExplainPlan,
22    pub fingerprint: PlanFingerprint,
23}
24
25impl From<ExplainPlan> for QueryDiagnostics {
26    fn from(explain: ExplainPlan) -> Self {
27        let fingerprint = explain.fingerprint();
28        Self {
29            explain,
30            fingerprint,
31        }
32    }
33}
34
35///
36/// QueryExecutionDiagnostics
37///
38/// Read-only execution diagnostics emitted for a single query execution.
39///
40
41#[derive(Clone, Debug, Eq, PartialEq)]
42pub struct QueryExecutionDiagnostics {
43    pub fingerprint: PlanFingerprint,
44    pub events: Vec<QueryTraceEvent>,
45}
46
47/// Public alias for trace access kinds in query diagnostics.
48pub type QueryTraceAccess = TraceAccess;
49
50/// Public alias for trace executor kinds in query diagnostics.
51pub type QueryTraceExecutorKind = TraceExecutorKind;
52
53/// Public alias for trace phase kinds in query diagnostics.
54pub type QueryTracePhase = TracePhase;
55
56pub(crate) fn trace_access_from_plan(plan: &AccessPlan) -> TraceAccess {
57    match plan {
58        AccessPlan::Path(path) => trace_access_from_path(path),
59        AccessPlan::Union(children) => TraceAccess::Union {
60            branches: u32::try_from(children.len()).unwrap_or(u32::MAX),
61        },
62        AccessPlan::Intersection(children) => TraceAccess::Intersection {
63            branches: u32::try_from(children.len()).unwrap_or(u32::MAX),
64        },
65    }
66}
67
68fn trace_access_from_path(path: &AccessPath) -> TraceAccess {
69    match path {
70        AccessPath::ByKey(_) => TraceAccess::ByKey,
71        AccessPath::ByKeys(keys) => TraceAccess::ByKeys {
72            count: u32::try_from(keys.len()).unwrap_or(u32::MAX),
73        },
74        AccessPath::KeyRange { .. } => TraceAccess::KeyRange,
75        AccessPath::IndexPrefix { index, values } => TraceAccess::IndexPrefix {
76            name: index.name,
77            prefix_len: u32::try_from(values.len()).unwrap_or(u32::MAX),
78        },
79        AccessPath::FullScan => TraceAccess::FullScan,
80    }
81}
82
83#[must_use]
84pub const fn start_event(
85    fingerprint: PlanFingerprint,
86    access: Option<TraceAccess>,
87    executor: TraceExecutorKind,
88) -> QueryTraceEvent {
89    QueryTraceEvent::Start {
90        fingerprint,
91        executor,
92        access,
93    }
94}
95
96#[must_use]
97pub const fn finish_event(
98    fingerprint: PlanFingerprint,
99    access: Option<TraceAccess>,
100    executor: TraceExecutorKind,
101    rows: u64,
102) -> QueryTraceEvent {
103    QueryTraceEvent::Finish {
104        fingerprint,
105        executor,
106        access,
107        rows,
108    }
109}