1use crate::algebra::Algebra;
38use anyhow::Result;
39use scirs2_core::metrics::MetricsRegistry;
40use scirs2_core::profiling::Profiler;
41use serde::{Deserialize, Serialize};
42use std::collections::{HashMap, VecDeque};
43use std::fmt;
44use std::sync::Arc;
45use std::time::Duration;
46use tracing::info;
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct DebugConfig {
51 pub enable_tracing: bool,
53
54 pub enable_breakpoints: bool,
56
57 pub enable_variable_tracking: bool,
59
60 pub enable_profiling: bool,
62
63 pub enable_plan_visualization: bool,
65
66 pub max_trace_entries: usize,
68
69 pub trace_detail_level: usize,
71
72 pub track_rewrites: bool,
74
75 pub track_memory: bool,
77
78 pub visualization_format: VisualizationFormat,
80}
81
82impl Default for DebugConfig {
83 fn default() -> Self {
84 Self {
85 enable_tracing: true,
86 enable_breakpoints: true,
87 enable_variable_tracking: true,
88 enable_profiling: true,
89 enable_plan_visualization: true,
90 max_trace_entries: 10000,
91 trace_detail_level: 2,
92 track_rewrites: true,
93 track_memory: true,
94 visualization_format: VisualizationFormat::Text,
95 }
96 }
97}
98
99impl DebugConfig {
100 pub fn with_breakpoints(mut self, enabled: bool) -> Self {
102 self.enable_breakpoints = enabled;
103 self
104 }
105
106 pub fn with_variable_tracking(mut self, enabled: bool) -> Self {
108 self.enable_variable_tracking = enabled;
109 self
110 }
111
112 pub fn with_trace_level(mut self, level: usize) -> Self {
114 self.trace_detail_level = level.min(3);
115 self
116 }
117
118 pub fn with_visualization_format(mut self, format: VisualizationFormat) -> Self {
120 self.visualization_format = format;
121 self
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
127pub enum VisualizationFormat {
128 Text,
130 Dot,
132 Json,
134 Mermaid,
136}
137
138pub struct QueryDebugger {
140 config: DebugConfig,
142
143 breakpoints: Vec<DebugBreakpoint>,
145
146 trace: VecDeque<TraceEntry>,
148
149 variable_history: HashMap<String, Vec<VariableBinding>>,
151
152 #[allow(dead_code)]
154 profiler: Arc<Profiler>,
155
156 #[allow(dead_code)]
158 metrics: Arc<MetricsRegistry>,
159
160 rewrite_history: Vec<RewriteStep>,
162
163 state: ExecutionState,
165
166 breakpoint_hits: usize,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
172pub enum DebugBreakpoint {
173 BeforeTriplePattern { pattern_id: usize },
175
176 AfterTriplePattern { pattern_id: usize },
178
179 BeforeJoin { join_id: usize },
181
182 AfterJoin { join_id: usize },
184
185 OnFilter { filter_id: usize },
187
188 OnVariableBound { variable: String },
190
191 OnResultCountExceeds { threshold: usize },
193
194 OnTimeExceeds { threshold: Duration },
196
197 OnMemoryExceeds { threshold_bytes: usize },
199
200 Conditional { condition: String },
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct TraceEntry {
207 pub seq: usize,
209
210 pub timestamp: Duration,
212
213 pub operation: Operation,
215
216 pub duration: Duration,
218
219 pub result_count: usize,
221
222 pub memory_usage: usize,
224
225 pub metadata: HashMap<String, String>,
227}
228
229#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
231pub enum Operation {
232 Parse,
234
235 GenerateAlgebra,
237
238 Optimize,
240
241 GeneratePlan,
243
244 ScanPattern { pattern_id: usize },
246
247 Join { join_id: usize, join_type: JoinType },
249
250 EvaluateFilter { filter_id: usize },
252
253 Project { variables: Vec<String> },
255
256 Aggregate { functions: Vec<String> },
258
259 Sort { variables: Vec<String> },
261
262 Distinct,
264
265 Slice { offset: usize, limit: usize },
267
268 Custom { name: String },
270}
271
272#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
274pub enum JoinType {
275 Inner,
277 LeftOuter,
279 Minus,
281 Union,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct VariableBinding {
288 pub variable: String,
290
291 pub value: String,
293
294 pub timestamp: Duration,
296
297 pub source: String,
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct RewriteStep {
304 pub step: usize,
306
307 pub rule: String,
309
310 pub description: String,
312
313 pub before: String,
315
316 pub after: String,
318
319 pub improvement: Option<f64>,
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
325pub enum ExecutionState {
326 Idle,
328
329 Running,
331
332 Paused,
334
335 Completed,
337
338 Failed,
340}
341
342impl QueryDebugger {
343 pub fn new(config: DebugConfig) -> Result<Self> {
345 let profiler = Arc::new(Profiler::new());
346 let metrics = Arc::new(MetricsRegistry::new());
347
348 Ok(Self {
349 config,
350 breakpoints: Vec::new(),
351 trace: VecDeque::new(),
352 variable_history: HashMap::new(),
353 profiler,
354 metrics,
355 rewrite_history: Vec::new(),
356 state: ExecutionState::Idle,
357 breakpoint_hits: 0,
358 })
359 }
360
361 pub fn add_breakpoint(&mut self, breakpoint: DebugBreakpoint) {
363 if self.config.enable_breakpoints {
364 info!("Adding breakpoint: {:?}", breakpoint);
365 self.breakpoints.push(breakpoint);
366 }
367 }
368
369 pub fn remove_breakpoint(&mut self, index: usize) -> Option<DebugBreakpoint> {
371 if index < self.breakpoints.len() {
372 Some(self.breakpoints.remove(index))
373 } else {
374 None
375 }
376 }
377
378 pub fn clear_breakpoints(&mut self) {
380 self.breakpoints.clear();
381 info!("Cleared all breakpoints");
382 }
383
384 pub fn record_trace(&mut self, operation: Operation, duration: Duration, result_count: usize) {
386 if !self.config.enable_tracing {
387 return;
388 }
389
390 let entry = TraceEntry {
391 seq: self.trace.len(),
392 timestamp: self.get_elapsed_time(),
393 operation,
394 duration,
395 result_count,
396 memory_usage: self.estimate_memory_usage(),
397 metadata: HashMap::new(),
398 };
399
400 self.trace.push_back(entry);
401
402 while self.trace.len() > self.config.max_trace_entries {
404 self.trace.pop_front();
405 }
406 }
407
408 pub fn track_variable(&mut self, variable: String, value: String, source: String) {
410 if !self.config.enable_variable_tracking {
411 return;
412 }
413
414 let binding = VariableBinding {
415 variable: variable.clone(),
416 value,
417 timestamp: self.get_elapsed_time(),
418 source,
419 };
420
421 self.variable_history
422 .entry(variable.clone())
423 .or_default()
424 .push(binding);
425
426 if let Some(_bp) = self.breakpoints.iter().find(
428 |bp| matches!(bp, DebugBreakpoint::OnVariableBound { variable: v } if v == &variable),
429 ) {
430 self.hit_breakpoint();
431 }
432 }
433
434 pub fn record_rewrite(
436 &mut self,
437 rule: String,
438 description: String,
439 before: &Algebra,
440 after: &Algebra,
441 ) {
442 if !self.config.track_rewrites {
443 return;
444 }
445
446 let step = RewriteStep {
447 step: self.rewrite_history.len(),
448 rule,
449 description,
450 before: format!("{:?}", before),
451 after: format!("{:?}", after),
452 improvement: None, };
454
455 self.rewrite_history.push(step);
456 }
457
458 pub fn should_break(&self, operation: &Operation, result_count: usize) -> bool {
460 if !self.config.enable_breakpoints {
461 return false;
462 }
463
464 for bp in &self.breakpoints {
465 let should_break = match (bp, operation) {
466 (
467 DebugBreakpoint::BeforeTriplePattern { pattern_id: bp_id },
468 Operation::ScanPattern { pattern_id: op_id },
469 ) => bp_id == op_id,
470 (
471 DebugBreakpoint::BeforeJoin { join_id: bp_id },
472 Operation::Join { join_id: op_id, .. },
473 ) => bp_id == op_id,
474 (DebugBreakpoint::OnResultCountExceeds { threshold }, _) => {
475 result_count > *threshold
476 }
477 _ => false,
478 };
479
480 if should_break {
481 return true;
482 }
483 }
484
485 false
486 }
487
488 fn hit_breakpoint(&mut self) {
490 self.breakpoint_hits += 1;
491 self.state = ExecutionState::Paused;
492 info!("Breakpoint hit #{}", self.breakpoint_hits);
493 }
494
495 pub fn resume(&mut self) {
497 if self.state == ExecutionState::Paused {
498 self.state = ExecutionState::Running;
499 info!("Resuming execution");
500 }
501 }
502
503 pub fn get_execution_trace(&self) -> &VecDeque<TraceEntry> {
505 &self.trace
506 }
507
508 pub fn get_variable_history(&self, variable: &str) -> Option<&Vec<VariableBinding>> {
510 self.variable_history.get(variable)
511 }
512
513 pub fn get_all_variable_histories(&self) -> &HashMap<String, Vec<VariableBinding>> {
515 &self.variable_history
516 }
517
518 pub fn get_rewrite_history(&self) -> &[RewriteStep] {
520 &self.rewrite_history
521 }
522
523 pub fn get_state(&self) -> ExecutionState {
525 self.state
526 }
527
528 pub fn get_breakpoint_hits(&self) -> usize {
530 self.breakpoint_hits
531 }
532
533 pub fn visualize_plan(&self, algebra: &Algebra) -> Result<String> {
535 if !self.config.enable_plan_visualization {
536 return Ok("Plan visualization disabled".to_string());
537 }
538
539 match self.config.visualization_format {
540 VisualizationFormat::Text => self.visualize_as_text(algebra),
541 VisualizationFormat::Dot => self.visualize_as_dot(algebra),
542 VisualizationFormat::Json => self.visualize_as_json(algebra),
543 VisualizationFormat::Mermaid => self.visualize_as_mermaid(algebra),
544 }
545 }
546
547 fn visualize_as_text(&self, algebra: &Algebra) -> Result<String> {
549 let mut output = String::new();
550 self.visualize_algebra_recursive(algebra, 0, &mut output);
551 Ok(output)
552 }
553
554 #[allow(clippy::only_used_in_recursion)]
556 fn visualize_algebra_recursive(&self, algebra: &Algebra, depth: usize, output: &mut String) {
557 let indent = " ".repeat(depth);
558
559 match algebra {
560 Algebra::Bgp(patterns) => {
561 output.push_str(&format!("{}BGP ({} patterns)\n", indent, patterns.len()));
562 }
563 Algebra::Join { left, right } => {
564 output.push_str(&format!("{}JOIN\n", indent));
565 self.visualize_algebra_recursive(left, depth + 1, output);
566 self.visualize_algebra_recursive(right, depth + 1, output);
567 }
568 Algebra::LeftJoin { left, right, .. } => {
569 output.push_str(&format!("{}LEFT JOIN (OPTIONAL)\n", indent));
570 self.visualize_algebra_recursive(left, depth + 1, output);
571 self.visualize_algebra_recursive(right, depth + 1, output);
572 }
573 Algebra::Union { left, right } => {
574 output.push_str(&format!("{}UNION\n", indent));
575 self.visualize_algebra_recursive(left, depth + 1, output);
576 self.visualize_algebra_recursive(right, depth + 1, output);
577 }
578 Algebra::Filter { pattern, condition } => {
579 output.push_str(&format!("{}FILTER: {:?}\n", indent, condition));
580 self.visualize_algebra_recursive(pattern, depth + 1, output);
581 }
582 Algebra::Project { pattern, variables } => {
583 output.push_str(&format!(
584 "{}PROJECT: [{}]\n",
585 indent,
586 variables
587 .iter()
588 .map(|v| v.name())
589 .collect::<Vec<_>>()
590 .join(", ")
591 ));
592 self.visualize_algebra_recursive(pattern, depth + 1, output);
593 }
594 Algebra::Distinct { pattern } => {
595 output.push_str(&format!("{}DISTINCT\n", indent));
596 self.visualize_algebra_recursive(pattern, depth + 1, output);
597 }
598 Algebra::OrderBy {
599 pattern,
600 conditions,
601 } => {
602 output.push_str(&format!("{}ORDER BY ({} keys)\n", indent, conditions.len()));
603 self.visualize_algebra_recursive(pattern, depth + 1, output);
604 }
605 Algebra::Slice {
606 pattern,
607 offset,
608 limit,
609 } => {
610 output.push_str(&format!(
611 "{}SLICE (offset={:?}, limit={:?})\n",
612 indent, offset, limit
613 ));
614 self.visualize_algebra_recursive(pattern, depth + 1, output);
615 }
616 Algebra::Group { pattern, .. } => {
617 output.push_str(&format!("{}GROUP\n", indent));
618 self.visualize_algebra_recursive(pattern, depth + 1, output);
619 }
620 _ => {
621 output.push_str(&format!("{}{:?}\n", indent, algebra));
622 }
623 }
624 }
625
626 fn visualize_as_dot(&self, algebra: &Algebra) -> Result<String> {
628 let mut output = String::from("digraph query_plan {\n");
629 output.push_str(" rankdir=TB;\n");
630 output.push_str(" node [shape=box];\n\n");
631
632 let mut node_id = 0;
633 self.visualize_dot_recursive(algebra, &mut node_id, None, &mut output);
634
635 output.push_str("}\n");
636 Ok(output)
637 }
638
639 #[allow(clippy::only_used_in_recursion)]
641 fn visualize_dot_recursive(
642 &self,
643 algebra: &Algebra,
644 node_id: &mut usize,
645 parent_id: Option<usize>,
646 output: &mut String,
647 ) -> usize {
648 let current_id = *node_id;
649 *node_id += 1;
650
651 let label = match algebra {
652 Algebra::Bgp(patterns) => format!("BGP\\n{} patterns", patterns.len()),
653 Algebra::Join { .. } => "JOIN".to_string(),
654 Algebra::LeftJoin { .. } => "LEFT JOIN".to_string(),
655 Algebra::Union { .. } => "UNION".to_string(),
656 Algebra::Filter { .. } => "FILTER".to_string(),
657 Algebra::Project { variables, .. } => {
658 format!("PROJECT\\n{} vars", variables.len())
659 }
660 Algebra::Distinct { .. } => "DISTINCT".to_string(),
661 Algebra::OrderBy { .. } => "ORDER BY".to_string(),
662 _ => format!("{:?}", algebra)
663 .split('{')
664 .next()
665 .unwrap_or("Unknown")
666 .to_string(),
667 };
668
669 output.push_str(&format!(" n{} [label=\"{}\"];\n", current_id, label));
670
671 if let Some(pid) = parent_id {
672 output.push_str(&format!(" n{} -> n{};\n", pid, current_id));
673 }
674
675 match algebra {
677 Algebra::Join { left, right }
678 | Algebra::LeftJoin { left, right, .. }
679 | Algebra::Union { left, right } => {
680 self.visualize_dot_recursive(left, node_id, Some(current_id), output);
681 self.visualize_dot_recursive(right, node_id, Some(current_id), output);
682 }
683 Algebra::Filter { pattern, .. }
684 | Algebra::Project { pattern, .. }
685 | Algebra::Distinct { pattern }
686 | Algebra::OrderBy { pattern, .. }
687 | Algebra::Slice { pattern, .. }
688 | Algebra::Group { pattern, .. } => {
689 self.visualize_dot_recursive(pattern, node_id, Some(current_id), output);
690 }
691 _ => {}
692 }
693
694 current_id
695 }
696
697 fn visualize_as_json(&self, algebra: &Algebra) -> Result<String> {
699 Ok(serde_json::to_string_pretty(&format!("{:?}", algebra))?)
701 }
702
703 fn visualize_as_mermaid(&self, algebra: &Algebra) -> Result<String> {
705 let mut output = String::from("graph TD\n");
706
707 let mut node_id = 0;
708 self.visualize_mermaid_recursive(algebra, &mut node_id, None, &mut output);
709
710 Ok(output)
711 }
712
713 #[allow(clippy::only_used_in_recursion)]
715 fn visualize_mermaid_recursive(
716 &self,
717 algebra: &Algebra,
718 node_id: &mut usize,
719 parent_id: Option<usize>,
720 output: &mut String,
721 ) -> usize {
722 let current_id = *node_id;
723 *node_id += 1;
724
725 let label = match algebra {
726 Algebra::Bgp(patterns) => format!("BGP<br/>{} patterns", patterns.len()),
727 Algebra::Join { .. } => "JOIN".to_string(),
728 Algebra::Filter { .. } => "FILTER".to_string(),
729 _ => format!("{:?}", algebra)
730 .split('{')
731 .next()
732 .unwrap_or("Unknown")
733 .to_string(),
734 };
735
736 output.push_str(&format!(" N{}[{}]\n", current_id, label));
737
738 if let Some(pid) = parent_id {
739 output.push_str(&format!(" N{} --> N{}\n", pid, current_id));
740 }
741
742 match algebra {
744 Algebra::Join { left, right } | Algebra::Union { left, right } => {
745 self.visualize_mermaid_recursive(left, node_id, Some(current_id), output);
746 self.visualize_mermaid_recursive(right, node_id, Some(current_id), output);
747 }
748 Algebra::Filter { pattern, .. } | Algebra::Project { pattern, .. } => {
749 self.visualize_mermaid_recursive(pattern, node_id, Some(current_id), output);
750 }
751 _ => {}
752 }
753
754 current_id
755 }
756
757 pub fn generate_report(&self) -> DebugReport {
759 DebugReport {
760 total_operations: self.trace.len(),
761 total_duration: self.get_elapsed_time(),
762 breakpoint_hits: self.breakpoint_hits,
763 variables_tracked: self.variable_history.len(),
764 rewrites_applied: self.rewrite_history.len(),
765 state: self.state,
766 slowest_operations: self.get_slowest_operations(5),
767 memory_peak: self.trace.iter().map(|e| e.memory_usage).max().unwrap_or(0),
768 }
769 }
770
771 fn get_slowest_operations(&self, count: usize) -> Vec<(Operation, Duration)> {
773 let mut ops: Vec<_> = self
774 .trace
775 .iter()
776 .map(|e| (e.operation.clone(), e.duration))
777 .collect();
778
779 ops.sort_by(|a, b| b.1.cmp(&a.1));
780 ops.truncate(count);
781 ops
782 }
783
784 fn get_elapsed_time(&self) -> Duration {
786 self.trace
787 .back()
788 .map(|e| e.timestamp)
789 .unwrap_or(Duration::ZERO)
790 }
791
792 fn estimate_memory_usage(&self) -> usize {
794 let trace_mem = self.trace.len() * std::mem::size_of::<TraceEntry>();
796 let var_mem = self.variable_history.len() * 256; trace_mem + var_mem
798 }
799
800 pub fn reset(&mut self) {
802 self.trace.clear();
803 self.variable_history.clear();
804 self.rewrite_history.clear();
805 self.state = ExecutionState::Idle;
806 self.breakpoint_hits = 0;
807 info!("Debugger reset");
808 }
809}
810
811#[derive(Debug, Clone, Serialize, Deserialize)]
813pub struct DebugReport {
814 pub total_operations: usize,
816
817 pub total_duration: Duration,
819
820 pub breakpoint_hits: usize,
822
823 pub variables_tracked: usize,
825
826 pub rewrites_applied: usize,
828
829 pub state: ExecutionState,
831
832 pub slowest_operations: Vec<(Operation, Duration)>,
834
835 pub memory_peak: usize,
837}
838
839impl fmt::Display for DebugReport {
840 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
841 writeln!(f, "=== Query Debug Report ===")?;
842 writeln!(f, "Total Operations: {}", self.total_operations)?;
843 writeln!(f, "Total Duration: {:?}", self.total_duration)?;
844 writeln!(f, "Breakpoint Hits: {}", self.breakpoint_hits)?;
845 writeln!(f, "Variables Tracked: {}", self.variables_tracked)?;
846 writeln!(f, "Rewrites Applied: {}", self.rewrites_applied)?;
847 writeln!(f, "Execution State: {:?}", self.state)?;
848 writeln!(f, "Peak Memory: {} bytes", self.memory_peak)?;
849 writeln!(f, "\nSlowest Operations:")?;
850 for (i, (op, duration)) in self.slowest_operations.iter().enumerate() {
851 writeln!(f, " {}. {:?} - {:?}", i + 1, op, duration)?;
852 }
853 Ok(())
854 }
855}
856
857#[cfg(test)]
858mod tests {
859 use super::*;
860
861 #[test]
862 fn test_debugger_creation() {
863 let config = DebugConfig::default();
864 let debugger = QueryDebugger::new(config);
865 assert!(debugger.is_ok());
866 }
867
868 #[test]
869 fn test_breakpoint_management() {
870 let mut debugger = QueryDebugger::new(DebugConfig::default()).unwrap();
871
872 debugger.add_breakpoint(DebugBreakpoint::BeforeJoin { join_id: 0 });
874 assert_eq!(debugger.breakpoints.len(), 1);
875
876 let removed = debugger.remove_breakpoint(0);
878 assert!(removed.is_some());
879 assert_eq!(debugger.breakpoints.len(), 0);
880 }
881
882 #[test]
883 fn test_trace_recording() {
884 let mut debugger = QueryDebugger::new(DebugConfig::default()).unwrap();
885
886 let op = Operation::Parse;
887 debugger.record_trace(op.clone(), Duration::from_millis(10), 0);
888
889 assert_eq!(debugger.trace.len(), 1);
890 assert_eq!(debugger.trace[0].operation, op);
891 }
892
893 #[test]
894 fn test_variable_tracking() {
895 let mut debugger = QueryDebugger::new(DebugConfig::default()).unwrap();
896
897 debugger.track_variable(
898 "x".to_string(),
899 "value1".to_string(),
900 "pattern_0".to_string(),
901 );
902
903 let history = debugger.get_variable_history("x");
904 assert!(history.is_some());
905 assert_eq!(history.unwrap().len(), 1);
906 }
907
908 #[test]
909 fn test_rewrite_tracking() {
910 let mut debugger = QueryDebugger::new(DebugConfig::default()).unwrap();
911
912 use crate::algebra::Algebra;
913
914 let before = Algebra::Bgp(vec![]);
915 let after = Algebra::Bgp(vec![]);
916
917 debugger.record_rewrite(
918 "test_rule".to_string(),
919 "Test rewrite".to_string(),
920 &before,
921 &after,
922 );
923
924 assert_eq!(debugger.rewrite_history.len(), 1);
925 }
926
927 #[test]
928 fn test_execution_state_transitions() {
929 let mut debugger = QueryDebugger::new(DebugConfig::default()).unwrap();
930
931 assert_eq!(debugger.get_state(), ExecutionState::Idle);
932
933 debugger.state = ExecutionState::Running;
934 assert_eq!(debugger.get_state(), ExecutionState::Running);
935
936 debugger.hit_breakpoint();
937 assert_eq!(debugger.get_state(), ExecutionState::Paused);
938
939 debugger.resume();
940 assert_eq!(debugger.get_state(), ExecutionState::Running);
941 }
942
943 #[test]
944 fn test_debug_report_generation() {
945 let mut debugger = QueryDebugger::new(DebugConfig::default()).unwrap();
946
947 debugger.record_trace(Operation::Parse, Duration::from_millis(5), 0);
948 debugger.record_trace(Operation::GenerateAlgebra, Duration::from_millis(10), 0);
949
950 let report = debugger.generate_report();
951 assert_eq!(report.total_operations, 2);
952 }
953
954 #[test]
955 fn test_config_builder() {
956 let config = DebugConfig::default()
957 .with_breakpoints(false)
958 .with_variable_tracking(true)
959 .with_trace_level(3)
960 .with_visualization_format(VisualizationFormat::Dot);
961
962 assert!(!config.enable_breakpoints);
963 assert!(config.enable_variable_tracking);
964 assert_eq!(config.trace_detail_level, 3);
965 assert_eq!(config.visualization_format, VisualizationFormat::Dot);
966 }
967
968 #[test]
969 fn test_debugger_reset() {
970 let mut debugger = QueryDebugger::new(DebugConfig::default()).unwrap();
971
972 debugger.record_trace(Operation::Parse, Duration::from_millis(5), 0);
973 debugger.track_variable("x".to_string(), "val".to_string(), "src".to_string());
974
975 debugger.reset();
976
977 assert_eq!(debugger.trace.len(), 0);
978 assert_eq!(debugger.variable_history.len(), 0);
979 assert_eq!(debugger.get_state(), ExecutionState::Idle);
980 }
981}