1use crate::{
6 db::query::plan::{AccessPath, AccessPlan, ExecutablePlan, PlanFingerprint},
7 error::{ErrorClass, ErrorOrigin, InternalError},
8};
9use sha2::{Digest, Sha256};
10
11pub trait QueryTraceSink: Send + Sync {
16 fn on_event(&self, event: QueryTraceEvent);
17}
18
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum TraceExecutorKind {
25 Load,
26 Save,
27 Delete,
28 Upsert,
29}
30
31#[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#[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
71pub 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}