1use crate::{
6 db::query::plan::{AccessPath, AccessPlan, ExecutablePlan, PlanFingerprint},
7 error::{ErrorClass, ErrorOrigin, InternalError},
8 traits::EntityKind,
9};
10use sha2::{Digest, Sha256};
11
12pub trait QueryTraceSink: Send + Sync {
17 fn on_event(&self, event: QueryTraceEvent);
18}
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum TraceExecutorKind {
26 Load,
27 Save,
28 Delete,
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 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: 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<K>(plan: &AccessPlan<K>) -> TraceAccess {
177 match plan {
178 AccessPlan::Path(path) => trace_access_from_path(path),
179 AccessPlan::Union(children) => {
180 TraceAccess::Union {
182 branches: u32::try_from(children.len()).unwrap_or(u32::MAX),
183 }
184 }
185 AccessPlan::Intersection(children) => {
186 TraceAccess::Intersection {
188 branches: u32::try_from(children.len()).unwrap_or(u32::MAX),
189 }
190 }
191 }
192}
193
194fn trace_access_from_path<K>(path: &AccessPath<K>) -> TraceAccess {
195 match path {
196 AccessPath::ByKey(_) => TraceAccess::ByKey,
197 AccessPath::ByKeys(keys) => {
198 TraceAccess::ByKeys {
200 count: u32::try_from(keys.len()).unwrap_or(u32::MAX),
201 }
202 }
203 AccessPath::KeyRange { .. } => TraceAccess::KeyRange,
204 AccessPath::IndexPrefix { index, values } => {
205 TraceAccess::IndexPrefix {
207 name: index.name,
208 prefix_len: u32::try_from(values.len()).unwrap_or(u32::MAX),
209 }
210 }
211 AccessPath::FullScan => TraceAccess::FullScan,
212 }
213}
214
215fn exec_fingerprint(
216 executor: TraceExecutorKind,
217 entity_path: &'static str,
218 detail: Option<&'static str>,
219) -> PlanFingerprint {
220 let mut hasher = Sha256::new();
221 hasher.update(b"execfp:v1");
222 hasher.update([executor_tag(executor)]);
223 write_str(&mut hasher, entity_path);
224 match detail {
225 Some(detail) => {
226 hasher.update([1u8]);
227 write_str(&mut hasher, detail);
228 }
229 None => {
230 hasher.update([0u8]);
231 }
232 }
233 let digest = hasher.finalize();
234 let mut out = [0u8; 32];
235 out.copy_from_slice(&digest);
236 PlanFingerprint::from_bytes(out)
237}
238
239const fn executor_tag(executor: TraceExecutorKind) -> u8 {
240 match executor {
241 TraceExecutorKind::Load => 0x01,
242 TraceExecutorKind::Save => 0x02,
243 TraceExecutorKind::Delete => 0x03,
244 }
245}
246
247fn write_str(hasher: &mut Sha256, value: &str) {
248 let len = u32::try_from(value.len()).unwrap_or(u32::MAX);
250 hasher.update(len.to_be_bytes());
251 hasher.update(value.as_bytes());
252}