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