1use std::collections::HashMap;
17use std::time::{Duration, Instant};
18
19#[derive(Debug, Clone, PartialEq)]
21pub enum StepType {
22 Parse,
23 Validate,
24 Optimize,
25 ResolveField {
26 field_name: String,
27 parent_type: String,
28 },
29 ExecuteDataSource {
30 query: String,
31 source: String,
32 },
33 Transform,
34 Serialize,
35}
36
37#[derive(Debug, Clone)]
39pub struct ExecutionStep {
40 pub step_id: usize,
41 pub step_type: StepType,
42 pub started_at: Instant,
43 pub duration: Option<Duration>,
44 pub status: StepStatus,
45 pub input: Option<String>,
46 pub output: Option<String>,
47 pub error: Option<String>,
48 pub metadata: HashMap<String, String>,
49}
50
51#[derive(Debug, Clone, PartialEq)]
53pub enum StepStatus {
54 Pending,
55 Running,
56 Completed,
57 Failed,
58 Skipped,
59}
60
61#[derive(Debug, Clone)]
63pub struct ExecutionPlan {
64 pub query_id: String,
65 pub query_text: String,
66 pub variables: HashMap<String, String>,
67 pub steps: Vec<ExecutionStep>,
68 pub total_duration: Option<Duration>,
69 pub created_at: Instant,
70}
71
72impl ExecutionPlan {
73 pub fn new(query_id: String, query_text: String, variables: HashMap<String, String>) -> Self {
75 Self {
76 query_id,
77 query_text,
78 variables,
79 steps: Vec::new(),
80 total_duration: None,
81 created_at: Instant::now(),
82 }
83 }
84
85 pub fn add_step(&mut self, step_type: StepType) -> usize {
87 let step_id = self.steps.len();
88 self.steps.push(ExecutionStep {
89 step_id,
90 step_type,
91 started_at: Instant::now(),
92 duration: None,
93 status: StepStatus::Pending,
94 input: None,
95 output: None,
96 error: None,
97 metadata: HashMap::new(),
98 });
99 step_id
100 }
101
102 pub fn start_step(&mut self, step_id: usize) {
104 if let Some(step) = self.steps.get_mut(step_id) {
105 step.status = StepStatus::Running;
106 step.started_at = Instant::now();
107 }
108 }
109
110 pub fn complete_step(&mut self, step_id: usize, output: Option<String>) {
112 if let Some(step) = self.steps.get_mut(step_id) {
113 step.duration = Some(step.started_at.elapsed());
114 step.status = StepStatus::Completed;
115 step.output = output;
116 }
117 }
118
119 pub fn fail_step(&mut self, step_id: usize, error: String) {
121 if let Some(step) = self.steps.get_mut(step_id) {
122 step.duration = Some(step.started_at.elapsed());
123 step.status = StepStatus::Failed;
124 step.error = Some(error);
125 }
126 }
127
128 pub fn skip_step(&mut self, step_id: usize, reason: String) {
130 if let Some(step) = self.steps.get_mut(step_id) {
131 step.status = StepStatus::Skipped;
132 step.metadata.insert("skip_reason".to_string(), reason);
133 }
134 }
135
136 pub fn set_step_metadata(&mut self, step_id: usize, key: String, value: String) {
138 if let Some(step) = self.steps.get_mut(step_id) {
139 step.metadata.insert(key, value);
140 }
141 }
142
143 pub fn finalize(&mut self) {
145 self.total_duration = Some(self.created_at.elapsed());
146 }
147
148 pub fn summary(&self) -> ExecutionSummary {
150 let mut completed = 0;
151 let mut failed = 0;
152 let mut skipped = 0;
153 let mut total_time = Duration::ZERO;
154
155 for step in &self.steps {
156 match step.status {
157 StepStatus::Completed => {
158 completed += 1;
159 if let Some(duration) = step.duration {
160 total_time += duration;
161 }
162 }
163 StepStatus::Failed => failed += 1,
164 StepStatus::Skipped => skipped += 1,
165 _ => {}
166 }
167 }
168
169 ExecutionSummary {
170 total_steps: self.steps.len(),
171 completed_steps: completed,
172 failed_steps: failed,
173 skipped_steps: skipped,
174 total_duration: self.total_duration.unwrap_or(total_time),
175 }
176 }
177
178 pub fn visualize_tree(&self) -> String {
180 let mut output = String::new();
181 output.push_str(&format!("Query: {} ({})\n", self.query_id, self.query_text));
182 output.push_str("Execution Steps:\n");
183
184 for (i, step) in self.steps.iter().enumerate() {
185 let prefix = if i == self.steps.len() - 1 {
186 "└─"
187 } else {
188 "├─"
189 };
190
191 let status_icon = match step.status {
192 StepStatus::Completed => "✓",
193 StepStatus::Failed => "✗",
194 StepStatus::Skipped => "○",
195 StepStatus::Running => "→",
196 StepStatus::Pending => "◦",
197 };
198
199 let duration_str = step
200 .duration
201 .map(|d| format!(" [{:.2}ms]", d.as_secs_f64() * 1000.0))
202 .unwrap_or_default();
203
204 output.push_str(&format!(
205 "{} {} {:?}{}\n",
206 prefix, status_icon, step.step_type, duration_str
207 ));
208
209 if let Some(ref error) = step.error {
210 output.push_str(&format!(" Error: {}\n", error));
211 }
212 }
213
214 output
215 }
216
217 pub fn to_json(&self) -> String {
219 let mut json = String::from("{");
220 json.push_str(&format!("\"query_id\":\"{}\",", self.query_id));
221 json.push_str(&format!(
222 "\"query_text\":\"{}\",",
223 self.query_text.replace('"', "\\\"")
224 ));
225
226 json.push_str("\"steps\":[");
228 for (i, step) in self.steps.iter().enumerate() {
229 if i > 0 {
230 json.push(',');
231 }
232 json.push_str(&format!(
233 "{{\"id\":{},\"type\":\"{:?}\",\"status\":\"{:?}\"",
234 step.step_id, step.step_type, step.status
235 ));
236 if let Some(duration) = step.duration {
237 json.push_str(&format!(
238 ",\"duration_ms\":{}",
239 duration.as_secs_f64() * 1000.0
240 ));
241 }
242 json.push('}');
243 }
244 json.push_str("],");
245
246 let summary = self.summary();
248 json.push_str(&format!(
249 "\"summary\":{{\"total\":{},\"completed\":{},\"failed\":{}}}",
250 summary.total_steps, summary.completed_steps, summary.failed_steps
251 ));
252
253 json.push('}');
254 json
255 }
256}
257
258#[derive(Debug, Clone)]
260pub struct ExecutionSummary {
261 pub total_steps: usize,
262 pub completed_steps: usize,
263 pub failed_steps: usize,
264 pub skipped_steps: usize,
265 pub total_duration: Duration,
266}
267
268pub struct QueryDebugger {
270 plans: HashMap<String, ExecutionPlan>,
271 max_plans: usize,
272}
273
274impl QueryDebugger {
275 pub fn new(max_plans: usize) -> Self {
277 Self {
278 plans: HashMap::new(),
279 max_plans,
280 }
281 }
282
283 pub fn start_query(
285 &mut self,
286 query_id: String,
287 query_text: String,
288 variables: HashMap<String, String>,
289 ) -> String {
290 if self.plans.len() >= self.max_plans {
292 if let Some(oldest_id) = self.plans.keys().next().cloned() {
294 self.plans.remove(&oldest_id);
295 }
296 }
297
298 let plan = ExecutionPlan::new(query_id.clone(), query_text, variables);
299 self.plans.insert(query_id.clone(), plan);
300 query_id
301 }
302
303 pub fn add_step(&mut self, query_id: &str, step_type: StepType) -> Option<usize> {
305 self.plans
306 .get_mut(query_id)
307 .map(|plan| plan.add_step(step_type))
308 }
309
310 pub fn start_step(&mut self, query_id: &str, step_id: usize) {
312 if let Some(plan) = self.plans.get_mut(query_id) {
313 plan.start_step(step_id);
314 }
315 }
316
317 pub fn complete_step(&mut self, query_id: &str, step_id: usize, output: Option<String>) {
319 if let Some(plan) = self.plans.get_mut(query_id) {
320 plan.complete_step(step_id, output);
321 }
322 }
323
324 pub fn fail_step(&mut self, query_id: &str, step_id: usize, error: String) {
326 if let Some(plan) = self.plans.get_mut(query_id) {
327 plan.fail_step(step_id, error);
328 }
329 }
330
331 pub fn finalize_query(&mut self, query_id: &str) {
333 if let Some(plan) = self.plans.get_mut(query_id) {
334 plan.finalize();
335 }
336 }
337
338 pub fn get_plan(&self, query_id: &str) -> Option<&ExecutionPlan> {
340 self.plans.get(query_id)
341 }
342
343 pub fn get_all_plans(&self) -> Vec<&ExecutionPlan> {
345 self.plans.values().collect()
346 }
347
348 pub fn get_summary(&self, query_id: &str) -> Option<ExecutionSummary> {
350 self.plans.get(query_id).map(|plan| plan.summary())
351 }
352
353 pub fn clear(&mut self) {
355 self.plans.clear();
356 }
357
358 pub fn plan_count(&self) -> usize {
360 self.plans.len()
361 }
362
363 pub fn generate_report(&self) -> String {
365 let mut report = String::from("=== Query Execution Debug Report ===\n\n");
366 report.push_str(&format!("Total queries tracked: {}\n\n", self.plans.len()));
367
368 for plan in self.plans.values() {
369 report.push_str(&format!("Query ID: {}\n", plan.query_id));
370 report.push_str(&format!("Query: {}\n", plan.query_text));
371
372 let summary = plan.summary();
373 report.push_str(&format!(
374 "Steps: {} total, {} completed, {} failed\n",
375 summary.total_steps, summary.completed_steps, summary.failed_steps
376 ));
377 report.push_str(&format!(
378 "Duration: {:.2}ms\n",
379 summary.total_duration.as_secs_f64() * 1000.0
380 ));
381 report.push('\n');
382 }
383
384 report
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
393 fn test_execution_plan_creation() {
394 let plan = ExecutionPlan::new(
395 "query-1".to_string(),
396 "{ user { id name } }".to_string(),
397 HashMap::new(),
398 );
399
400 assert_eq!(plan.query_id, "query-1");
401 assert_eq!(plan.steps.len(), 0);
402 }
403
404 #[test]
405 fn test_add_step() {
406 let mut plan =
407 ExecutionPlan::new("query-1".to_string(), "query".to_string(), HashMap::new());
408
409 let step_id = plan.add_step(StepType::Parse);
410 assert_eq!(step_id, 0);
411 assert_eq!(plan.steps.len(), 1);
412 assert_eq!(plan.steps[0].status, StepStatus::Pending);
413 }
414
415 #[test]
416 fn test_start_complete_step() {
417 let mut plan =
418 ExecutionPlan::new("query-1".to_string(), "query".to_string(), HashMap::new());
419
420 let step_id = plan.add_step(StepType::Parse);
421 plan.start_step(step_id);
422 assert_eq!(plan.steps[step_id].status, StepStatus::Running);
423
424 plan.complete_step(step_id, Some("parsed".to_string()));
425 assert_eq!(plan.steps[step_id].status, StepStatus::Completed);
426 assert!(plan.steps[step_id].duration.is_some());
427 }
428
429 #[test]
430 fn test_fail_step() {
431 let mut plan =
432 ExecutionPlan::new("query-1".to_string(), "query".to_string(), HashMap::new());
433
434 let step_id = plan.add_step(StepType::Validate);
435 plan.start_step(step_id);
436 plan.fail_step(step_id, "Validation error".to_string());
437
438 assert_eq!(plan.steps[step_id].status, StepStatus::Failed);
439 assert_eq!(
440 plan.steps[step_id].error,
441 Some("Validation error".to_string())
442 );
443 }
444
445 #[test]
446 fn test_skip_step() {
447 let mut plan =
448 ExecutionPlan::new("query-1".to_string(), "query".to_string(), HashMap::new());
449
450 let step_id = plan.add_step(StepType::Optimize);
451 plan.skip_step(step_id, "Optimization disabled".to_string());
452
453 assert_eq!(plan.steps[step_id].status, StepStatus::Skipped);
454 assert_eq!(
455 plan.steps[step_id].metadata.get("skip_reason"),
456 Some(&"Optimization disabled".to_string())
457 );
458 }
459
460 #[test]
461 fn test_execution_summary() {
462 let mut plan =
463 ExecutionPlan::new("query-1".to_string(), "query".to_string(), HashMap::new());
464
465 let step1 = plan.add_step(StepType::Parse);
466 plan.start_step(step1);
467 plan.complete_step(step1, None);
468
469 let step2 = plan.add_step(StepType::Validate);
470 plan.start_step(step2);
471 plan.fail_step(step2, "Error".to_string());
472
473 let step3 = plan.add_step(StepType::Optimize);
474 plan.skip_step(step3, "Skipped".to_string());
475
476 let summary = plan.summary();
477 assert_eq!(summary.total_steps, 3);
478 assert_eq!(summary.completed_steps, 1);
479 assert_eq!(summary.failed_steps, 1);
480 assert_eq!(summary.skipped_steps, 1);
481 }
482
483 #[test]
484 fn test_visualize_tree() {
485 let mut plan = ExecutionPlan::new(
486 "query-1".to_string(),
487 "{ user { id } }".to_string(),
488 HashMap::new(),
489 );
490
491 plan.add_step(StepType::Parse);
492 plan.complete_step(0, None);
493
494 plan.add_step(StepType::Validate);
495 plan.complete_step(1, None);
496
497 let tree = plan.visualize_tree();
498 assert!(tree.contains("Query: query-1"));
499 assert!(tree.contains("Parse"));
500 assert!(tree.contains("Validate"));
501 }
502
503 #[test]
504 fn test_to_json() {
505 let mut plan =
506 ExecutionPlan::new("query-1".to_string(), "query".to_string(), HashMap::new());
507
508 plan.add_step(StepType::Parse);
509 plan.complete_step(0, None);
510
511 let json = plan.to_json();
512 assert!(json.contains("query_id"));
513 assert!(json.contains("steps"));
514 assert!(json.contains("summary"));
515 }
516
517 #[test]
518 fn test_query_debugger_start() {
519 let mut debugger = QueryDebugger::new(100);
520
521 let query_id =
522 debugger.start_query("query-1".to_string(), "query".to_string(), HashMap::new());
523
524 assert_eq!(query_id, "query-1");
525 assert_eq!(debugger.plan_count(), 1);
526 }
527
528 #[test]
529 fn test_query_debugger_add_step() {
530 let mut debugger = QueryDebugger::new(100);
531
532 debugger.start_query("query-1".to_string(), "query".to_string(), HashMap::new());
533 let step_id = debugger.add_step("query-1", StepType::Parse);
534
535 assert_eq!(step_id, Some(0));
536 }
537
538 #[test]
539 fn test_query_debugger_complete_flow() {
540 let mut debugger = QueryDebugger::new(100);
541
542 debugger.start_query("query-1".to_string(), "query".to_string(), HashMap::new());
543
544 let step_id = debugger
545 .add_step("query-1", StepType::Parse)
546 .expect("should succeed");
547 debugger.start_step("query-1", step_id);
548 debugger.complete_step("query-1", step_id, Some("parsed".to_string()));
549
550 debugger.finalize_query("query-1");
551
552 let plan = debugger.get_plan("query-1").expect("should succeed");
553 assert_eq!(plan.steps[0].status, StepStatus::Completed);
554 assert!(plan.total_duration.is_some());
555 }
556
557 #[test]
558 fn test_query_debugger_get_summary() {
559 let mut debugger = QueryDebugger::new(100);
560
561 debugger.start_query("query-1".to_string(), "query".to_string(), HashMap::new());
562
563 let step_id = debugger
564 .add_step("query-1", StepType::Parse)
565 .expect("should succeed");
566 debugger.complete_step("query-1", step_id, None);
567
568 let summary = debugger.get_summary("query-1").expect("should succeed");
569 assert_eq!(summary.completed_steps, 1);
570 }
571
572 #[test]
573 fn test_query_debugger_max_plans() {
574 let mut debugger = QueryDebugger::new(2);
575
576 debugger.start_query("query-1".to_string(), "query".to_string(), HashMap::new());
577 debugger.start_query("query-2".to_string(), "query".to_string(), HashMap::new());
578 debugger.start_query("query-3".to_string(), "query".to_string(), HashMap::new());
579
580 assert_eq!(debugger.plan_count(), 2);
582 }
583
584 #[test]
585 fn test_query_debugger_get_all_plans() {
586 let mut debugger = QueryDebugger::new(100);
587
588 debugger.start_query("query-1".to_string(), "query".to_string(), HashMap::new());
589 debugger.start_query("query-2".to_string(), "query".to_string(), HashMap::new());
590
591 let plans = debugger.get_all_plans();
592 assert_eq!(plans.len(), 2);
593 }
594
595 #[test]
596 fn test_query_debugger_clear() {
597 let mut debugger = QueryDebugger::new(100);
598
599 debugger.start_query("query-1".to_string(), "query".to_string(), HashMap::new());
600 assert_eq!(debugger.plan_count(), 1);
601
602 debugger.clear();
603 assert_eq!(debugger.plan_count(), 0);
604 }
605
606 #[test]
607 fn test_query_debugger_generate_report() {
608 let mut debugger = QueryDebugger::new(100);
609
610 debugger.start_query(
611 "query-1".to_string(),
612 "{ user { id } }".to_string(),
613 HashMap::new(),
614 );
615 debugger.add_step("query-1", StepType::Parse);
616
617 let report = debugger.generate_report();
618 assert!(report.contains("Debug Report"));
619 assert!(report.contains("query-1"));
620 }
621
622 #[test]
623 fn test_step_metadata() {
624 let mut plan =
625 ExecutionPlan::new("query-1".to_string(), "query".to_string(), HashMap::new());
626
627 let step_id = plan.add_step(StepType::ExecuteDataSource {
628 query: "SELECT * FROM users".to_string(),
629 source: "postgres".to_string(),
630 });
631
632 plan.set_step_metadata(step_id, "rows_returned".to_string(), "100".to_string());
633
634 assert_eq!(
635 plan.steps[step_id].metadata.get("rows_returned"),
636 Some(&"100".to_string())
637 );
638 }
639
640 #[test]
641 fn test_resolve_field_step() {
642 let mut plan =
643 ExecutionPlan::new("query-1".to_string(), "query".to_string(), HashMap::new());
644
645 let step_id = plan.add_step(StepType::ResolveField {
646 field_name: "user".to_string(),
647 parent_type: "Query".to_string(),
648 });
649
650 if let StepType::ResolveField { field_name, .. } = &plan.steps[step_id].step_type {
651 assert_eq!(field_name, "user");
652 } else {
653 panic!("Expected ResolveField step");
654 }
655 }
656
657 #[test]
658 fn test_finalize_sets_total_duration() {
659 let mut plan =
660 ExecutionPlan::new("query-1".to_string(), "query".to_string(), HashMap::new());
661
662 assert!(plan.total_duration.is_none());
663
664 plan.finalize();
665
666 assert!(plan.total_duration.is_some());
667 }
668
669 #[test]
670 fn test_multiple_steps_different_types() {
671 let mut plan =
672 ExecutionPlan::new("query-1".to_string(), "query".to_string(), HashMap::new());
673
674 plan.add_step(StepType::Parse);
675 plan.add_step(StepType::Validate);
676 plan.add_step(StepType::Optimize);
677 plan.add_step(StepType::Transform);
678 plan.add_step(StepType::Serialize);
679
680 assert_eq!(plan.steps.len(), 5);
681 assert!(matches!(plan.steps[0].step_type, StepType::Parse));
682 assert!(matches!(plan.steps[1].step_type, StepType::Validate));
683 assert!(matches!(plan.steps[4].step_type, StepType::Serialize));
684 }
685}