Skip to main content

icydb_core/db/executor/
trace.rs

1//! Executor query tracing boundary.
2//!
3//! Tracing is optional, injected by the caller, and must not affect execution semantics.
4
5use crate::{
6    db::query::plan::{AccessPath, AccessPlan, ExecutablePlan, PlanFingerprint},
7    error::{ErrorClass, ErrorOrigin, InternalError},
8};
9use sha2::{Digest, Sha256};
10
11///
12/// QueryTraceSink
13///
14
15pub trait QueryTraceSink: Send + Sync {
16    fn on_event(&self, event: QueryTraceEvent);
17}
18
19///
20/// TraceExecutorKind
21///
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum TraceExecutorKind {
25    Load,
26    Save,
27    Delete,
28    Upsert,
29}
30
31///
32/// TraceAccess
33///
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum TraceAccess {
37    ByKey,
38    ByKeys { count: u32 },
39    KeyRange,
40    IndexPrefix { name: &'static str, prefix_len: u32 },
41    FullScan,
42    UniqueIndex { name: &'static str },
43}
44
45///
46/// QueryTraceEvent
47///
48
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub enum QueryTraceEvent {
51    Start {
52        fingerprint: PlanFingerprint,
53        executor: TraceExecutorKind,
54        access: Option<TraceAccess>,
55    },
56    Finish {
57        fingerprint: PlanFingerprint,
58        executor: TraceExecutorKind,
59        access: Option<TraceAccess>,
60        rows: u64,
61    },
62    Error {
63        fingerprint: PlanFingerprint,
64        executor: TraceExecutorKind,
65        access: Option<TraceAccess>,
66        class: ErrorClass,
67        origin: ErrorOrigin,
68    },
69}
70
71///
72/// TraceScope
73///
74
75pub struct TraceScope {
76    sink: &'static dyn QueryTraceSink,
77    fingerprint: PlanFingerprint,
78    executor: TraceExecutorKind,
79    access: Option<TraceAccess>,
80}
81
82impl TraceScope {
83    fn new(
84        sink: &'static dyn QueryTraceSink,
85        fingerprint: PlanFingerprint,
86        executor: TraceExecutorKind,
87        access: Option<TraceAccess>,
88    ) -> Self {
89        sink.on_event(QueryTraceEvent::Start {
90            fingerprint,
91            executor,
92            access,
93        });
94        Self {
95            sink,
96            fingerprint,
97            executor,
98            access,
99        }
100    }
101
102    pub(crate) fn finish(self, rows: u64) {
103        self.sink.on_event(QueryTraceEvent::Finish {
104            fingerprint: self.fingerprint,
105            executor: self.executor,
106            access: self.access,
107            rows,
108        });
109    }
110
111    pub(crate) fn error(self, err: &InternalError) {
112        self.sink.on_event(QueryTraceEvent::Error {
113            fingerprint: self.fingerprint,
114            executor: self.executor,
115            access: self.access,
116            class: err.class,
117            origin: err.origin,
118        });
119    }
120}
121
122pub fn start_plan_trace<E: crate::traits::EntityKind>(
123    sink: Option<&'static dyn QueryTraceSink>,
124    executor: TraceExecutorKind,
125    plan: &ExecutablePlan<E>,
126) -> Option<TraceScope> {
127    let sink = sink?;
128    let access = trace_access_from_plan(plan.access());
129    let fingerprint = plan.fingerprint();
130    Some(TraceScope::new(sink, fingerprint, executor, access))
131}
132
133pub fn start_exec_trace(
134    sink: Option<&'static dyn QueryTraceSink>,
135    executor: TraceExecutorKind,
136    entity_path: &'static str,
137    access: Option<TraceAccess>,
138    detail: Option<&'static str>,
139) -> Option<TraceScope> {
140    let sink = sink?;
141    let fingerprint = exec_fingerprint(executor, entity_path, detail);
142    Some(TraceScope::new(sink, fingerprint, executor, access))
143}
144
145fn trace_access_from_plan(plan: &AccessPlan) -> Option<TraceAccess> {
146    match plan {
147        AccessPlan::Path(path) => Some(trace_access_from_path(path)),
148        AccessPlan::Union(_) | AccessPlan::Intersection(_) => None,
149    }
150}
151
152fn trace_access_from_path(path: &AccessPath) -> TraceAccess {
153    match path {
154        AccessPath::ByKey(_) => TraceAccess::ByKey,
155        AccessPath::ByKeys(keys) => TraceAccess::ByKeys {
156            count: u32::try_from(keys.len()).unwrap_or(u32::MAX),
157        },
158        AccessPath::KeyRange { .. } => TraceAccess::KeyRange,
159        AccessPath::IndexPrefix { index, values } => TraceAccess::IndexPrefix {
160            name: index.name,
161            prefix_len: u32::try_from(values.len()).unwrap_or(u32::MAX),
162        },
163        AccessPath::FullScan => TraceAccess::FullScan,
164    }
165}
166
167fn exec_fingerprint(
168    executor: TraceExecutorKind,
169    entity_path: &'static str,
170    detail: Option<&'static str>,
171) -> PlanFingerprint {
172    let mut hasher = Sha256::new();
173    hasher.update(b"execfp:v1");
174    hasher.update([executor_tag(executor)]);
175    write_str(&mut hasher, entity_path);
176    match detail {
177        Some(detail) => {
178            hasher.update([1u8]);
179            write_str(&mut hasher, detail);
180        }
181        None => {
182            hasher.update([0u8]);
183        }
184    }
185    let digest = hasher.finalize();
186    let mut out = [0u8; 32];
187    out.copy_from_slice(&digest);
188    PlanFingerprint::from_bytes(out)
189}
190
191const fn executor_tag(executor: TraceExecutorKind) -> u8 {
192    match executor {
193        TraceExecutorKind::Load => 0x01,
194        TraceExecutorKind::Save => 0x02,
195        TraceExecutorKind::Delete => 0x03,
196        TraceExecutorKind::Upsert => 0x04,
197    }
198}
199
200fn write_str(hasher: &mut Sha256, value: &str) {
201    let len = u32::try_from(value.len()).unwrap_or(u32::MAX);
202    hasher.update(len.to_be_bytes());
203    hasher.update(value.as_bytes());
204}