Skip to main content

oxirs_arq/
debug_utilities.rs

1//! Debugging Utilities for SPARQL Queries
2//!
3//! This module provides comprehensive debugging tools for SPARQL query development,
4//! troubleshooting, and performance analysis. It includes query inspection, execution
5//! tracing, breakpoints, variable tracking, and plan visualization.
6//!
7//! # Features
8//!
9//! - **Query Inspector**: Analyze query structure and patterns
10//! - **Execution Debugger**: Step-by-step execution with breakpoints
11//! - **Performance Profiler**: Identify bottlenecks and slow operations
12//! - **Variable Tracker**: Monitor variable bindings throughout execution
13//! - **Plan Visualizer**: Generate visual representations of query plans
14//! - **Rewrite Tracker**: Track query rewriting transformations
15//!
16//! # Example
17//!
18//! ```rust,ignore
19//! use oxirs_arq::debug_utilities::{QueryDebugger, DebugConfig};
20//!
21//! let config = DebugConfig::default()
22//!     .with_breakpoints(true)
23//!     .with_variable_tracking(true);
24//!
25//! let mut debugger = QueryDebugger::new(config)?;
26//!
27//! // Set breakpoint
28//! debugger.add_breakpoint(DebugBreakpoint::AfterJoin { join_id: 0 });
29//!
30//! // Execute with debugging
31//! let result = debugger.execute_with_debug(&query, &dataset)?;
32//!
33//! // Inspect execution trace
34//! let trace = debugger.get_execution_trace();
35//! ```
36
37use 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/// Configuration for query debugging
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct DebugConfig {
51    /// Enable execution tracing
52    pub enable_tracing: bool,
53
54    /// Enable breakpoint support
55    pub enable_breakpoints: bool,
56
57    /// Enable variable tracking
58    pub enable_variable_tracking: bool,
59
60    /// Enable performance profiling
61    pub enable_profiling: bool,
62
63    /// Enable plan visualization
64    pub enable_plan_visualization: bool,
65
66    /// Maximum trace entries to keep
67    pub max_trace_entries: usize,
68
69    /// Trace detail level (0-3)
70    pub trace_detail_level: usize,
71
72    /// Enable query rewrite tracking
73    pub track_rewrites: bool,
74
75    /// Enable memory tracking
76    pub track_memory: bool,
77
78    /// Output format for visualizations
79    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    /// Enable breakpoints
101    pub fn with_breakpoints(mut self, enabled: bool) -> Self {
102        self.enable_breakpoints = enabled;
103        self
104    }
105
106    /// Enable variable tracking
107    pub fn with_variable_tracking(mut self, enabled: bool) -> Self {
108        self.enable_variable_tracking = enabled;
109        self
110    }
111
112    /// Set trace detail level
113    pub fn with_trace_level(mut self, level: usize) -> Self {
114        self.trace_detail_level = level.min(3);
115        self
116    }
117
118    /// Set visualization format
119    pub fn with_visualization_format(mut self, format: VisualizationFormat) -> Self {
120        self.visualization_format = format;
121        self
122    }
123}
124
125/// Visualization output formats
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
127pub enum VisualizationFormat {
128    /// Plain text tree structure
129    Text,
130    /// DOT graph format (Graphviz)
131    Dot,
132    /// JSON structure
133    Json,
134    /// Mermaid diagram
135    Mermaid,
136}
137
138/// Query debugger for SPARQL queries
139pub struct QueryDebugger {
140    /// Debug configuration
141    config: DebugConfig,
142
143    /// Active breakpoints
144    breakpoints: Vec<DebugBreakpoint>,
145
146    /// Execution trace
147    trace: VecDeque<TraceEntry>,
148
149    /// Variable binding history
150    variable_history: HashMap<String, Vec<VariableBinding>>,
151
152    /// Performance profiler (reserved for future use)
153    #[allow(dead_code)]
154    profiler: Arc<Profiler>,
155
156    /// Metrics registry (reserved for future use)
157    #[allow(dead_code)]
158    metrics: Arc<MetricsRegistry>,
159
160    /// Query rewrite history
161    rewrite_history: Vec<RewriteStep>,
162
163    /// Current execution state
164    state: ExecutionState,
165
166    /// Breakpoint hit counter
167    breakpoint_hits: usize,
168}
169
170/// Debug breakpoint types
171#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
172pub enum DebugBreakpoint {
173    /// Break before executing a triple pattern
174    BeforeTriplePattern { pattern_id: usize },
175
176    /// Break after executing a triple pattern
177    AfterTriplePattern { pattern_id: usize },
178
179    /// Break before a join operation
180    BeforeJoin { join_id: usize },
181
182    /// Break after a join operation
183    AfterJoin { join_id: usize },
184
185    /// Break when a filter is evaluated
186    OnFilter { filter_id: usize },
187
188    /// Break when a variable is bound
189    OnVariableBound { variable: String },
190
191    /// Break when result count exceeds threshold
192    OnResultCountExceeds { threshold: usize },
193
194    /// Break on execution time threshold
195    OnTimeExceeds { threshold: Duration },
196
197    /// Break on memory usage threshold
198    OnMemoryExceeds { threshold_bytes: usize },
199
200    /// Conditional breakpoint with custom expression
201    Conditional { condition: String },
202}
203
204/// Execution trace entry
205#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct TraceEntry {
207    /// Sequence number
208    pub seq: usize,
209
210    /// Timestamp
211    pub timestamp: Duration,
212
213    /// Operation type
214    pub operation: Operation,
215
216    /// Execution time for this operation
217    pub duration: Duration,
218
219    /// Number of results at this point
220    pub result_count: usize,
221
222    /// Memory usage (bytes)
223    pub memory_usage: usize,
224
225    /// Additional metadata
226    pub metadata: HashMap<String, String>,
227}
228
229/// Operation types in execution trace
230#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
231pub enum Operation {
232    /// Query parsing
233    Parse,
234
235    /// Algebra generation
236    GenerateAlgebra,
237
238    /// Query optimization
239    Optimize,
240
241    /// Plan generation
242    GeneratePlan,
243
244    /// Triple pattern scan
245    ScanPattern { pattern_id: usize },
246
247    /// Join operation
248    Join { join_id: usize, join_type: JoinType },
249
250    /// Filter evaluation
251    EvaluateFilter { filter_id: usize },
252
253    /// Projection
254    Project { variables: Vec<String> },
255
256    /// Aggregation
257    Aggregate { functions: Vec<String> },
258
259    /// Sorting
260    Sort { variables: Vec<String> },
261
262    /// Distinct operation
263    Distinct,
264
265    /// Limit/Offset
266    Slice { offset: usize, limit: usize },
267
268    /// Custom operation
269    Custom { name: String },
270}
271
272/// Join operation types
273#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
274pub enum JoinType {
275    /// Inner join
276    Inner,
277    /// Left outer join (OPTIONAL)
278    LeftOuter,
279    /// Minus
280    Minus,
281    /// Union
282    Union,
283}
284
285/// Variable binding at a specific point in execution
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct VariableBinding {
288    /// Variable name
289    pub variable: String,
290
291    /// Bound value (as string representation)
292    pub value: String,
293
294    /// Binding timestamp
295    pub timestamp: Duration,
296
297    /// Source operation
298    pub source: String,
299}
300
301/// Query rewrite transformation step
302#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct RewriteStep {
304    /// Step number
305    pub step: usize,
306
307    /// Rewrite rule applied
308    pub rule: String,
309
310    /// Description of transformation
311    pub description: String,
312
313    /// Algebra before rewrite
314    pub before: String,
315
316    /// Algebra after rewrite
317    pub after: String,
318
319    /// Estimated improvement
320    pub improvement: Option<f64>,
321}
322
323/// Current execution state
324#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
325pub enum ExecutionState {
326    /// Not started
327    Idle,
328
329    /// Currently executing
330    Running,
331
332    /// Paused at breakpoint
333    Paused,
334
335    /// Execution completed
336    Completed,
337
338    /// Execution failed
339    Failed,
340}
341
342impl QueryDebugger {
343    /// Create a new query debugger
344    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    /// Add a breakpoint
362    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    /// Remove a breakpoint
370    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    /// Clear all breakpoints
379    pub fn clear_breakpoints(&mut self) {
380        self.breakpoints.clear();
381        info!("Cleared all breakpoints");
382    }
383
384    /// Record a trace entry
385    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        // Trim trace if it exceeds max entries
403        while self.trace.len() > self.config.max_trace_entries {
404            self.trace.pop_front();
405        }
406    }
407
408    /// Track a variable binding
409    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        // Check for variable binding breakpoint
427        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    /// Record a query rewrite step
435    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, // Could calculate cost difference
453        };
454
455        self.rewrite_history.push(step);
456    }
457
458    /// Check if a breakpoint should be triggered
459    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    /// Handle breakpoint hit
489    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    /// Resume execution after breakpoint
496    pub fn resume(&mut self) {
497        if self.state == ExecutionState::Paused {
498            self.state = ExecutionState::Running;
499            info!("Resuming execution");
500        }
501    }
502
503    /// Get execution trace
504    pub fn get_execution_trace(&self) -> &VecDeque<TraceEntry> {
505        &self.trace
506    }
507
508    /// Get variable binding history for a specific variable
509    pub fn get_variable_history(&self, variable: &str) -> Option<&Vec<VariableBinding>> {
510        self.variable_history.get(variable)
511    }
512
513    /// Get all variable histories
514    pub fn get_all_variable_histories(&self) -> &HashMap<String, Vec<VariableBinding>> {
515        &self.variable_history
516    }
517
518    /// Get query rewrite history
519    pub fn get_rewrite_history(&self) -> &[RewriteStep] {
520        &self.rewrite_history
521    }
522
523    /// Get current execution state
524    pub fn get_state(&self) -> ExecutionState {
525        self.state
526    }
527
528    /// Get breakpoint hit count
529    pub fn get_breakpoint_hits(&self) -> usize {
530        self.breakpoint_hits
531    }
532
533    /// Visualize query plan
534    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    /// Visualize as text tree
548    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    /// Recursive helper for text visualization
555    #[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    /// Visualize as DOT graph (Graphviz format)
627    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    /// Recursive helper for DOT visualization
640    #[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        // Recurse for child nodes
676        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    /// Visualize as JSON
698    fn visualize_as_json(&self, algebra: &Algebra) -> Result<String> {
699        // Simple JSON representation
700        Ok(serde_json::to_string_pretty(&format!("{:?}", algebra))?)
701    }
702
703    /// Visualize as Mermaid diagram
704    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    /// Recursive helper for Mermaid visualization
714    #[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        // Recurse for child nodes
743        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    /// Generate debug report
758    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    /// Get slowest operations
772    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    /// Get elapsed time since start
785    fn get_elapsed_time(&self) -> Duration {
786        self.trace
787            .back()
788            .map(|e| e.timestamp)
789            .unwrap_or(Duration::ZERO)
790    }
791
792    /// Estimate current memory usage
793    fn estimate_memory_usage(&self) -> usize {
794        // Simple estimation based on trace size and variable history
795        let trace_mem = self.trace.len() * std::mem::size_of::<TraceEntry>();
796        let var_mem = self.variable_history.len() * 256; // Rough estimate
797        trace_mem + var_mem
798    }
799
800    /// Reset debugger state
801    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/// Debug report summary
812#[derive(Debug, Clone, Serialize, Deserialize)]
813pub struct DebugReport {
814    /// Total operations executed
815    pub total_operations: usize,
816
817    /// Total execution duration
818    pub total_duration: Duration,
819
820    /// Number of breakpoint hits
821    pub breakpoint_hits: usize,
822
823    /// Number of variables tracked
824    pub variables_tracked: usize,
825
826    /// Number of rewrites applied
827    pub rewrites_applied: usize,
828
829    /// Current execution state
830    pub state: ExecutionState,
831
832    /// Slowest operations
833    pub slowest_operations: Vec<(Operation, Duration)>,
834
835    /// Peak memory usage
836    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        // Add breakpoint
873        debugger.add_breakpoint(DebugBreakpoint::BeforeJoin { join_id: 0 });
874        assert_eq!(debugger.breakpoints.len(), 1);
875
876        // Remove breakpoint
877        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}