1use std::time::Instant;
7
8use serde::{Deserialize, Serialize};
9use tracing::{Level, debug, span, warn};
10
11use crate::error::Result;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct QueryPhaseSpan {
16 pub phase: String,
18
19 pub duration_us: u64,
21
22 pub success: bool,
24
25 pub error: Option<String>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct QueryExecutionTrace {
35 pub query_id: String,
37
38 pub query: String,
40
41 pub phases: Vec<QueryPhaseSpan>,
43
44 pub total_duration_us: u64,
46
47 pub success: bool,
49
50 pub error: Option<String>,
52
53 pub result_count: Option<usize>,
55}
56
57pub struct QueryTraceBuilder {
83 query_id: String,
84 query: String,
85 phases: Vec<QueryPhaseSpan>,
86 start: Instant,
87}
88
89impl QueryTraceBuilder {
90 #[must_use]
97 pub fn new(query_id: &str, query: &str) -> Self {
98 let query_str = if query.len() > 500 {
99 format!("{}...", &query[..500])
100 } else {
101 query.to_string()
102 };
103
104 Self {
105 query_id: query_id.to_string(),
106 query: query_str,
107 phases: Vec::new(),
108 start: Instant::now(),
109 }
110 }
111
112 pub fn record_phase_success(&mut self, phase_name: &str, duration_us: u64) {
119 self.phases.push(QueryPhaseSpan {
120 phase: phase_name.to_string(),
121 duration_us,
122 success: true,
123 error: None,
124 });
125
126 debug!(phase = phase_name, duration_us = duration_us, "Query phase completed");
127 }
128
129 pub fn record_phase_error(&mut self, phase_name: &str, duration_us: u64, error: &str) {
137 self.phases.push(QueryPhaseSpan {
138 phase: phase_name.to_string(),
139 duration_us,
140 success: false,
141 error: Some(error.to_string()),
142 });
143
144 warn!(
145 phase = phase_name,
146 duration_us = duration_us,
147 error = error,
148 "Query phase failed"
149 );
150 }
151
152 pub fn finish(
164 self,
165 success: bool,
166 error: Option<&str>,
167 result_count: Option<usize>,
168 ) -> Result<QueryExecutionTrace> {
169 let total_duration_us = self.start.elapsed().as_micros() as u64;
170
171 Ok(QueryExecutionTrace {
172 query_id: self.query_id.clone(),
173 query: self.query,
174 phases: self.phases,
175 total_duration_us,
176 success,
177 error: error.map(|e| e.to_string()),
178 result_count,
179 })
180 }
181
182 pub fn query_id(&self) -> &str {
184 &self.query_id
185 }
186
187 pub fn elapsed_us(&self) -> u64 {
189 self.start.elapsed().as_micros() as u64
190 }
191}
192
193impl QueryExecutionTrace {
194 pub fn average_phase_duration_us(&self) -> u64 {
196 if self.phases.is_empty() {
197 0
198 } else {
199 self.phases.iter().map(|p| p.duration_us).sum::<u64>() / self.phases.len() as u64
200 }
201 }
202
203 pub fn slowest_phase(&self) -> Option<&QueryPhaseSpan> {
205 self.phases.iter().max_by_key(|p| p.duration_us)
206 }
207
208 pub fn to_log_string(&self) -> String {
212 let status = if self.success { "success" } else { "error" };
213 let phases_str = self
214 .phases
215 .iter()
216 .map(|p| format!("{}={}us", p.phase, p.duration_us))
217 .collect::<Vec<_>>()
218 .join(" ");
219
220 let error_str = self.error.as_ref().map(|e| format!(" error={}", e)).unwrap_or_default();
221
222 format!(
223 "query_id={} status={} total={}us phases=[{}]{}",
224 self.query_id, status, self.total_duration_us, phases_str, error_str
225 )
226 }
227}
228
229pub fn create_query_span(query_id: &str, query_text: &str) -> tracing::Span {
245 span!(
246 Level::DEBUG,
247 "query_execute",
248 query_id = query_id,
249 query = truncate_query(query_text, 100),
250 )
251}
252
253pub fn create_phase_span(phase_name: &str, query_id: &str) -> tracing::Span {
260 span!(Level::DEBUG, "query_phase", phase = phase_name, query_id = query_id)
261}
262
263fn truncate_query(query: &str, max_len: usize) -> String {
267 if query.len() > max_len {
268 format!("{}...", &query[..max_len])
269 } else {
270 query.to_string()
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn test_trace_builder_new() {
280 let builder = QueryTraceBuilder::new("query_1", "{ user { id } }");
281 assert_eq!(builder.query_id, "query_1");
282 assert_eq!(builder.query, "{ user { id } }");
283 assert!(builder.phases.is_empty());
284 }
285
286 #[test]
287 fn test_trace_builder_truncate_long_query() {
288 let long_query = "a".repeat(600);
289 let builder = QueryTraceBuilder::new("query_1", &long_query);
290 assert!(builder.query.len() < 600);
291 assert!(builder.query.ends_with("..."));
292 }
293
294 #[test]
295 fn test_record_phase_success() {
296 let mut builder = QueryTraceBuilder::new("query_1", "{ user { id } }");
297 builder.record_phase_success("parse", 100);
298 builder.record_phase_success("validate", 50);
299
300 assert_eq!(builder.phases.len(), 2);
301 assert_eq!(builder.phases[0].phase, "parse");
302 assert_eq!(builder.phases[0].duration_us, 100);
303 assert!(builder.phases[0].success);
304 }
305
306 #[test]
307 fn test_record_phase_error() {
308 let mut builder = QueryTraceBuilder::new("query_1", "{ user { id } }");
309 builder.record_phase_error("parse", 100, "Invalid syntax");
310
311 assert_eq!(builder.phases.len(), 1);
312 assert_eq!(builder.phases[0].phase, "parse");
313 assert!(!builder.phases[0].success);
314 assert_eq!(builder.phases[0].error, Some("Invalid syntax".to_string()));
315 }
316
317 #[test]
318 fn test_finish_success() {
319 let mut builder = QueryTraceBuilder::new("query_1", "{ user { id } }");
320 builder.record_phase_success("parse", 100);
321 builder.record_phase_success("execute", 500);
322
323 let trace = builder.finish(true, None, Some(10)).unwrap();
324 assert!(trace.success);
325 assert_eq!(trace.query_id, "query_1");
326 assert_eq!(trace.phases.len(), 2);
327 assert_eq!(trace.result_count, Some(10));
328 }
330
331 #[test]
332 fn test_finish_error() {
333 let mut builder = QueryTraceBuilder::new("query_1", "{ user { id } }");
334 builder.record_phase_success("parse", 100);
335 builder.record_phase_error("execute", 50, "Database connection failed");
336
337 let trace = builder.finish(false, Some("Database connection failed"), None).unwrap();
338 assert!(!trace.success);
339 assert_eq!(trace.error, Some("Database connection failed".to_string()));
340 }
341
342 #[test]
343 fn test_average_phase_duration() {
344 let mut builder = QueryTraceBuilder::new("query_1", "{ user { id } }");
345 builder.record_phase_success("parse", 100);
346 builder.record_phase_success("validate", 200);
347 builder.record_phase_success("execute", 300);
348
349 let trace = builder.finish(true, None, None).unwrap();
350 assert_eq!(trace.average_phase_duration_us(), 200);
351 }
352
353 #[test]
354 fn test_slowest_phase() {
355 let mut builder = QueryTraceBuilder::new("query_1", "{ user { id } }");
356 builder.record_phase_success("parse", 100);
357 builder.record_phase_success("execute", 500);
358 builder.record_phase_success("cache_check", 50);
359
360 let trace = builder.finish(true, None, None).unwrap();
361 let slowest = trace.slowest_phase().unwrap();
362 assert_eq!(slowest.phase, "execute");
363 assert_eq!(slowest.duration_us, 500);
364 }
365
366 #[test]
367 fn test_to_log_string_success() {
368 let mut builder = QueryTraceBuilder::new("query_1", "{ user { id } }");
369 builder.record_phase_success("parse", 100);
370 builder.record_phase_success("execute", 500);
371
372 let trace = builder.finish(true, None, Some(5)).unwrap();
373 let log_str = trace.to_log_string();
374 assert!(log_str.contains("query_id=query_1"));
375 assert!(log_str.contains("status=success"));
376 assert!(log_str.contains("parse=100us"));
377 assert!(log_str.contains("execute=500us"));
378 }
379
380 #[test]
381 fn test_to_log_string_error() {
382 let mut builder = QueryTraceBuilder::new("query_1", "{ user { id } }");
383 builder.record_phase_success("parse", 100);
384 builder.record_phase_error("validate", 50, "Type mismatch");
385
386 let trace = builder.finish(false, Some("Type mismatch"), None).unwrap();
387 let log_str = trace.to_log_string();
388 assert!(log_str.contains("status=error"));
389 assert!(log_str.contains("error=Type mismatch"));
390 }
391
392 #[test]
393 fn test_average_phase_duration_empty() {
394 let builder = QueryTraceBuilder::new("query_1", "{ user { id } }");
395 let trace = builder.finish(true, None, None).unwrap();
396 assert_eq!(trace.average_phase_duration_us(), 0);
397 }
398
399 #[test]
400 fn test_elapsed_us() {
401 let builder = QueryTraceBuilder::new("query_1", "{ user { id } }");
402 let elapsed = builder.elapsed_us();
403 let _ = elapsed;
405 }
406
407 #[test]
408 fn test_trace_serialization() {
409 let mut builder = QueryTraceBuilder::new("query_1", "{ user { id } }");
410 builder.record_phase_success("parse", 100);
411
412 let trace = builder.finish(true, None, Some(5)).unwrap();
413 let json = serde_json::to_string(&trace).expect("serialize should work");
414 let restored: QueryExecutionTrace =
415 serde_json::from_str(&json).expect("deserialize should work");
416
417 assert_eq!(restored.query_id, trace.query_id);
418 assert_eq!(restored.phases.len(), trace.phases.len());
419 }
420
421 #[test]
422 fn test_query_phase_span_serialize() {
423 let span = QueryPhaseSpan {
424 phase: "parse".to_string(),
425 duration_us: 100,
426 success: true,
427 error: None,
428 };
429
430 let json = serde_json::to_string(&span).expect("serialize should work");
431 let restored: QueryPhaseSpan =
432 serde_json::from_str(&json).expect("deserialize should work");
433
434 assert_eq!(restored.phase, span.phase);
435 assert_eq!(restored.duration_us, span.duration_us);
436 }
437
438 #[test]
439 fn test_truncate_query_helper() {
440 assert_eq!(truncate_query("hello", 100), "hello");
441 assert!(truncate_query(&"a".repeat(200), 50).ends_with("..."));
442 }
443}