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}
29
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
35pub enum TraceAccess {
36 ByKey,
37 ByKeys { count: u32 },
38 KeyRange,
39 IndexPrefix { name: &'static str, prefix_len: u32 },
40 FullScan,
41 UniqueIndex { name: &'static str },
42 Union { branches: u32 },
43 Intersection { branches: u32 },
44}
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51pub enum TracePhase {
52 Access,
53 Filter,
54 Order,
55 Page,
56 DeleteLimit,
57}
58
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
64pub enum QueryTraceEvent {
65 Start {
66 fingerprint: PlanFingerprint,
67 executor: TraceExecutorKind,
68 access: Option<TraceAccess>,
69 },
70 Phase {
71 fingerprint: PlanFingerprint,
72 executor: TraceExecutorKind,
73 access: Option<TraceAccess>,
74 phase: TracePhase,
75 rows: u64,
76 },
77 Finish {
78 fingerprint: PlanFingerprint,
79 executor: TraceExecutorKind,
80 access: Option<TraceAccess>,
81 rows: u64,
82 },
83 Error {
84 fingerprint: PlanFingerprint,
85 executor: TraceExecutorKind,
86 access: Option<TraceAccess>,
87 class: ErrorClass,
88 origin: ErrorOrigin,
89 },
90}
91
92pub struct TraceScope {
97 sink: &'static dyn QueryTraceSink,
98 fingerprint: PlanFingerprint,
99 executor: TraceExecutorKind,
100 access: Option<TraceAccess>,
101}
102
103impl TraceScope {
104 fn new(
105 sink: &'static dyn QueryTraceSink,
106 fingerprint: PlanFingerprint,
107 executor: TraceExecutorKind,
108 access: Option<TraceAccess>,
109 ) -> Self {
110 sink.on_event(QueryTraceEvent::Start {
111 fingerprint,
112 executor,
113 access,
114 });
115 Self {
116 sink,
117 fingerprint,
118 executor,
119 access,
120 }
121 }
122
123 pub(crate) fn finish(self, rows: u64) {
124 self.sink.on_event(QueryTraceEvent::Finish {
125 fingerprint: self.fingerprint,
126 executor: self.executor,
127 access: self.access,
128 rows,
129 });
130 }
131
132 pub(crate) fn phase(&self, phase: TracePhase, rows: u64) {
133 self.sink.on_event(QueryTraceEvent::Phase {
134 fingerprint: self.fingerprint,
135 executor: self.executor,
136 access: self.access,
137 phase,
138 rows,
139 });
140 }
141
142 pub(crate) fn error(self, err: &InternalError) {
143 self.sink.on_event(QueryTraceEvent::Error {
144 fingerprint: self.fingerprint,
145 executor: self.executor,
146 access: self.access,
147 class: err.class,
148 origin: err.origin,
149 });
150 }
151}
152
153pub fn start_plan_trace<E: crate::traits::EntityKind>(
154 sink: Option<&'static dyn QueryTraceSink>,
155 executor: TraceExecutorKind,
156 plan: &ExecutablePlan<E>,
157) -> Option<TraceScope> {
158 let sink = sink?;
159 let access = Some(trace_access_from_plan(plan.access()));
160 let fingerprint = plan.fingerprint();
161 Some(TraceScope::new(sink, fingerprint, executor, access))
162}
163
164pub fn start_exec_trace(
165 sink: Option<&'static dyn QueryTraceSink>,
166 executor: TraceExecutorKind,
167 entity_path: &'static str,
168 access: Option<TraceAccess>,
169 detail: Option<&'static str>,
170) -> Option<TraceScope> {
171 let sink = sink?;
172 let fingerprint = exec_fingerprint(executor, entity_path, detail);
173 Some(TraceScope::new(sink, fingerprint, executor, access))
174}
175
176fn trace_access_from_plan(plan: &AccessPlan) -> TraceAccess {
177 match plan {
178 AccessPlan::Path(path) => trace_access_from_path(path),
179 AccessPlan::Union(children) => TraceAccess::Union {
180 branches: u32::try_from(children.len()).unwrap_or(u32::MAX),
181 },
182 AccessPlan::Intersection(children) => TraceAccess::Intersection {
183 branches: u32::try_from(children.len()).unwrap_or(u32::MAX),
184 },
185 }
186}
187
188fn trace_access_from_path(path: &AccessPath) -> TraceAccess {
189 match path {
190 AccessPath::ByKey(_) => TraceAccess::ByKey,
191 AccessPath::ByKeys(keys) => TraceAccess::ByKeys {
192 count: u32::try_from(keys.len()).unwrap_or(u32::MAX),
193 },
194 AccessPath::KeyRange { .. } => TraceAccess::KeyRange,
195 AccessPath::IndexPrefix { index, values } => TraceAccess::IndexPrefix {
196 name: index.name,
197 prefix_len: u32::try_from(values.len()).unwrap_or(u32::MAX),
198 },
199 AccessPath::FullScan => TraceAccess::FullScan,
200 }
201}
202
203fn exec_fingerprint(
204 executor: TraceExecutorKind,
205 entity_path: &'static str,
206 detail: Option<&'static str>,
207) -> PlanFingerprint {
208 let mut hasher = Sha256::new();
209 hasher.update(b"execfp:v1");
210 hasher.update([executor_tag(executor)]);
211 write_str(&mut hasher, entity_path);
212 match detail {
213 Some(detail) => {
214 hasher.update([1u8]);
215 write_str(&mut hasher, detail);
216 }
217 None => {
218 hasher.update([0u8]);
219 }
220 }
221 let digest = hasher.finalize();
222 let mut out = [0u8; 32];
223 out.copy_from_slice(&digest);
224 PlanFingerprint::from_bytes(out)
225}
226
227const fn executor_tag(executor: TraceExecutorKind) -> u8 {
228 match executor {
229 TraceExecutorKind::Load => 0x01,
230 TraceExecutorKind::Save => 0x02,
231 TraceExecutorKind::Delete => 0x03,
232 }
233}
234
235fn write_str(hasher: &mut Sha256, value: &str) {
236 let len = u32::try_from(value.len()).unwrap_or(u32::MAX);
237 hasher.update(len.to_be_bytes());
238 hasher.update(value.as_bytes());
239}