Skip to main content

oxirs_gql/
query_debugger.rs

1//! Query debugging and analysis tools
2//!
3//! This module provides comprehensive debugging capabilities:
4//! - Query explanation and execution plans
5//! - Performance profiling
6//! - SPARQL translation preview
7//! - Query complexity analysis
8//! - Execution tracing
9
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::time::{Duration, Instant};
13
14/// Query execution phase
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub enum ExecutionPhase {
17    /// Parsing GraphQL query
18    Parsing,
19    /// Validating query
20    Validation,
21    /// Translating to SPARQL
22    Translation,
23    /// Executing SPARQL query
24    Execution,
25    /// Resolving fields
26    Resolution,
27    /// Formatting response
28    Formatting,
29}
30
31impl std::fmt::Display for ExecutionPhase {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        match self {
34            ExecutionPhase::Parsing => write!(f, "Parsing"),
35            ExecutionPhase::Validation => write!(f, "Validation"),
36            ExecutionPhase::Translation => write!(f, "Translation"),
37            ExecutionPhase::Execution => write!(f, "Execution"),
38            ExecutionPhase::Resolution => write!(f, "Resolution"),
39            ExecutionPhase::Formatting => write!(f, "Formatting"),
40        }
41    }
42}
43
44/// Timing information for an execution phase
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct PhaseTimings {
47    pub phase: ExecutionPhase,
48    pub duration: Duration,
49    pub start_time: u64,
50    pub end_time: u64,
51}
52
53impl PhaseTimings {
54    pub fn new(phase: ExecutionPhase, duration: Duration, start_time: u64) -> Self {
55        Self {
56            phase,
57            duration,
58            start_time,
59            end_time: start_time + duration.as_millis() as u64,
60        }
61    }
62}
63
64/// Query execution trace
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ExecutionTrace {
67    /// Query that was executed
68    pub query: String,
69    /// Variables used
70    pub variables: HashMap<String, serde_json::Value>,
71    /// Phase timings
72    pub timings: Vec<PhaseTimings>,
73    /// Total execution time
74    pub total_duration: Duration,
75    /// Generated SPARQL queries
76    pub sparql_queries: Vec<String>,
77    /// Number of results returned
78    pub result_count: usize,
79    /// Errors encountered
80    pub errors: Vec<String>,
81    /// Warnings
82    pub warnings: Vec<String>,
83}
84
85impl ExecutionTrace {
86    pub fn new(query: String) -> Self {
87        Self {
88            query,
89            variables: HashMap::new(),
90            timings: Vec::new(),
91            total_duration: Duration::from_secs(0),
92            sparql_queries: Vec::new(),
93            result_count: 0,
94            errors: Vec::new(),
95            warnings: Vec::new(),
96        }
97    }
98
99    pub fn add_timing(&mut self, phase: ExecutionPhase, duration: Duration, start_time: u64) {
100        self.timings
101            .push(PhaseTimings::new(phase, duration, start_time));
102    }
103
104    pub fn add_sparql_query(&mut self, query: String) {
105        self.sparql_queries.push(query);
106    }
107
108    pub fn add_error(&mut self, error: String) {
109        self.errors.push(error);
110    }
111
112    pub fn add_warning(&mut self, warning: String) {
113        self.warnings.push(warning);
114    }
115
116    /// Generate a summary report
117    pub fn generate_report(&self) -> String {
118        let mut report = String::new();
119
120        report.push_str("=== Query Execution Trace ===\n\n");
121
122        // Query
123        report.push_str("Query:\n");
124        report.push_str(&format!("{}\n\n", self.query));
125
126        // Variables
127        if !self.variables.is_empty() {
128            report.push_str("Variables:\n");
129            for (key, value) in &self.variables {
130                report.push_str(&format!("  {}: {}\n", key, value));
131            }
132            report.push('\n');
133        }
134
135        // Timings
136        report.push_str("Execution Timings:\n");
137        for timing in &self.timings {
138            report.push_str(&format!(
139                "  {:<15} {:>8.2}ms\n",
140                format!("{}:", timing.phase),
141                timing.duration.as_secs_f64() * 1000.0
142            ));
143        }
144        report.push_str(&format!(
145            "  {:<15} {:>8.2}ms\n\n",
146            "Total:",
147            self.total_duration.as_secs_f64() * 1000.0
148        ));
149
150        // SPARQL queries
151        if !self.sparql_queries.is_empty() {
152            report.push_str("Generated SPARQL Queries:\n");
153            for (idx, query) in self.sparql_queries.iter().enumerate() {
154                report.push_str(&format!("\nQuery #{}:\n", idx + 1));
155                report.push_str(&format!("{}\n", query));
156            }
157            report.push('\n');
158        }
159
160        // Results
161        report.push_str(&format!("Results: {} records\n\n", self.result_count));
162
163        // Errors
164        if !self.errors.is_empty() {
165            report.push_str("Errors:\n");
166            for error in &self.errors {
167                report.push_str(&format!("  - {}\n", error));
168            }
169            report.push('\n');
170        }
171
172        // Warnings
173        if !self.warnings.is_empty() {
174            report.push_str("Warnings:\n");
175            for warning in &self.warnings {
176                report.push_str(&format!("  - {}\n", warning));
177            }
178            report.push('\n');
179        }
180
181        report
182    }
183}
184
185/// Query complexity metrics
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct ComplexityMetrics {
188    /// Total complexity score
189    pub total_score: u32,
190    /// Maximum depth
191    pub max_depth: u32,
192    /// Number of fields
193    pub field_count: u32,
194    /// Number of lists
195    pub list_count: u32,
196    /// Number of fragments
197    pub fragment_count: u32,
198    /// Field-level complexity scores
199    pub field_scores: HashMap<String, u32>,
200}
201
202impl ComplexityMetrics {
203    pub fn new() -> Self {
204        Self {
205            total_score: 0,
206            max_depth: 0,
207            field_count: 0,
208            list_count: 0,
209            fragment_count: 0,
210            field_scores: HashMap::new(),
211        }
212    }
213
214    pub fn add_field_score(&mut self, field: String, score: u32) {
215        self.field_scores.insert(field, score);
216        self.total_score += score;
217    }
218
219    /// Check if complexity exceeds threshold
220    pub fn exceeds_threshold(&self, threshold: u32) -> bool {
221        self.total_score > threshold
222    }
223
224    /// Generate complexity report
225    pub fn generate_report(&self) -> String {
226        let mut report = String::new();
227
228        report.push_str("=== Query Complexity Analysis ===\n\n");
229        report.push_str(&format!("Total Complexity Score: {}\n", self.total_score));
230        report.push_str(&format!("Maximum Depth: {}\n", self.max_depth));
231        report.push_str(&format!("Field Count: {}\n", self.field_count));
232        report.push_str(&format!("List Count: {}\n", self.list_count));
233        report.push_str(&format!("Fragment Count: {}\n\n", self.fragment_count));
234
235        if !self.field_scores.is_empty() {
236            report.push_str("Field-level Complexity:\n");
237            let mut sorted_fields: Vec<_> = self.field_scores.iter().collect();
238            sorted_fields.sort_by(|a, b| b.1.cmp(a.1));
239
240            for (field, score) in sorted_fields.iter().take(10) {
241                report.push_str(&format!("  {:<30} {}\n", field, score));
242            }
243        }
244
245        report
246    }
247}
248
249impl Default for ComplexityMetrics {
250    fn default() -> Self {
251        Self::new()
252    }
253}
254
255/// Query explanation with execution plan
256#[derive(Debug, Clone, Serialize, Deserialize)]
257pub struct QueryExplanation {
258    /// GraphQL query
259    pub query: String,
260    /// Execution plan steps
261    pub plan_steps: Vec<PlanStep>,
262    /// Estimated cost
263    pub estimated_cost: f64,
264    /// Recommendations for optimization
265    pub recommendations: Vec<String>,
266}
267
268impl QueryExplanation {
269    pub fn new(query: String) -> Self {
270        Self {
271            query,
272            plan_steps: Vec::new(),
273            estimated_cost: 0.0,
274            recommendations: Vec::new(),
275        }
276    }
277
278    pub fn add_step(&mut self, step: PlanStep) {
279        self.estimated_cost += step.estimated_cost;
280        self.plan_steps.push(step);
281    }
282
283    pub fn add_recommendation(&mut self, recommendation: String) {
284        self.recommendations.push(recommendation);
285    }
286
287    /// Generate explanation report
288    pub fn generate_report(&self) -> String {
289        let mut report = String::new();
290
291        report.push_str("=== Query Execution Plan ===\n\n");
292        report.push_str("Query:\n");
293        report.push_str(&format!("{}\n\n", self.query));
294
295        report.push_str("Execution Steps:\n");
296        for (idx, step) in self.plan_steps.iter().enumerate() {
297            report.push_str(&format!(
298                "{}. {} (cost: {:.2})\n",
299                idx + 1,
300                step.description,
301                step.estimated_cost
302            ));
303            if !step.details.is_empty() {
304                report.push_str(&format!("   {}\n", step.details));
305            }
306        }
307
308        report.push_str(&format!(
309            "\nTotal Estimated Cost: {:.2}\n\n",
310            self.estimated_cost
311        ));
312
313        if !self.recommendations.is_empty() {
314            report.push_str("Optimization Recommendations:\n");
315            for (idx, rec) in self.recommendations.iter().enumerate() {
316                report.push_str(&format!("  {}. {}\n", idx + 1, rec));
317            }
318        }
319
320        report
321    }
322}
323
324/// Execution plan step
325#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct PlanStep {
327    /// Step description
328    pub description: String,
329    /// Step details
330    pub details: String,
331    /// Estimated cost
332    pub estimated_cost: f64,
333    /// Expected result count
334    pub expected_results: Option<usize>,
335}
336
337impl PlanStep {
338    pub fn new(description: String, estimated_cost: f64) -> Self {
339        Self {
340            description,
341            details: String::new(),
342            estimated_cost,
343            expected_results: None,
344        }
345    }
346
347    pub fn with_details(mut self, details: String) -> Self {
348        self.details = details;
349        self
350    }
351
352    pub fn with_expected_results(mut self, count: usize) -> Self {
353        self.expected_results = Some(count);
354        self
355    }
356}
357
358/// Query debugger for profiling and analysis
359pub struct QueryDebugger {
360    /// Enable tracing
361    pub enable_tracing: bool,
362    /// Enable profiling
363    pub enable_profiling: bool,
364    /// Enable SPARQL preview
365    pub enable_sparql_preview: bool,
366    /// Complexity threshold
367    pub complexity_threshold: u32,
368}
369
370impl QueryDebugger {
371    pub fn new() -> Self {
372        Self {
373            enable_tracing: true,
374            enable_profiling: true,
375            enable_sparql_preview: true,
376            complexity_threshold: 1000,
377        }
378    }
379
380    pub fn with_tracing(mut self, enabled: bool) -> Self {
381        self.enable_tracing = enabled;
382        self
383    }
384
385    pub fn with_profiling(mut self, enabled: bool) -> Self {
386        self.enable_profiling = enabled;
387        self
388    }
389
390    pub fn with_sparql_preview(mut self, enabled: bool) -> Self {
391        self.enable_sparql_preview = enabled;
392        self
393    }
394
395    pub fn with_complexity_threshold(mut self, threshold: u32) -> Self {
396        self.complexity_threshold = threshold;
397        self
398    }
399
400    /// Start a new execution trace
401    pub fn start_trace(&self, query: String) -> ExecutionTrace {
402        ExecutionTrace::new(query)
403    }
404
405    /// Analyze query complexity
406    pub fn analyze_complexity(&self, _query: &str) -> ComplexityMetrics {
407        // Simplified implementation - in production, this would parse the query
408        // and calculate actual complexity
409        ComplexityMetrics::new()
410    }
411
412    /// Generate query explanation
413    pub fn explain_query(&self, query: String) -> QueryExplanation {
414        let mut explanation = QueryExplanation::new(query);
415
416        // Simplified implementation - in production, this would analyze the query
417        explanation.add_step(
418            PlanStep::new("Parse GraphQL query".to_string(), 1.0)
419                .with_details("Parse query into AST".to_string()),
420        );
421
422        explanation.add_step(
423            PlanStep::new("Validate query".to_string(), 2.0)
424                .with_details("Validate against schema".to_string()),
425        );
426
427        explanation.add_step(
428            PlanStep::new("Translate to SPARQL".to_string(), 3.0)
429                .with_details("Generate SPARQL query from GraphQL".to_string()),
430        );
431
432        explanation.add_step(
433            PlanStep::new("Execute SPARQL query".to_string(), 10.0)
434                .with_details("Execute against RDF store".to_string())
435                .with_expected_results(100),
436        );
437
438        explanation
439            .add_recommendation("Consider adding pagination to limit result set".to_string());
440
441        explanation
442    }
443}
444
445impl Default for QueryDebugger {
446    fn default() -> Self {
447        Self::new()
448    }
449}
450
451/// Timer for measuring execution phases
452pub struct PhaseTimer {
453    start: Instant,
454    phase: ExecutionPhase,
455}
456
457impl PhaseTimer {
458    pub fn start(phase: ExecutionPhase) -> Self {
459        Self {
460            start: Instant::now(),
461            phase,
462        }
463    }
464
465    pub fn finish(self) -> (ExecutionPhase, Duration) {
466        (self.phase, self.start.elapsed())
467    }
468}
469
470#[cfg(test)]
471mod tests {
472    use super::*;
473
474    #[test]
475    fn test_execution_phase_display() {
476        assert_eq!(ExecutionPhase::Parsing.to_string(), "Parsing");
477        assert_eq!(ExecutionPhase::Validation.to_string(), "Validation");
478        assert_eq!(ExecutionPhase::Execution.to_string(), "Execution");
479    }
480
481    #[test]
482    fn test_execution_trace() {
483        let mut trace = ExecutionTrace::new("{ user { name } }".to_string());
484        trace.add_timing(ExecutionPhase::Parsing, Duration::from_millis(10), 0);
485        trace.add_timing(ExecutionPhase::Execution, Duration::from_millis(50), 10);
486        trace.add_sparql_query("SELECT ?name WHERE { ?user foaf:name ?name }".to_string());
487        trace.result_count = 42;
488
489        assert_eq!(trace.timings.len(), 2);
490        assert_eq!(trace.sparql_queries.len(), 1);
491        assert_eq!(trace.result_count, 42);
492    }
493
494    #[test]
495    fn test_execution_trace_report() {
496        let mut trace = ExecutionTrace::new("{ user { name } }".to_string());
497        trace.add_timing(ExecutionPhase::Parsing, Duration::from_millis(10), 0);
498        trace.total_duration = Duration::from_millis(100);
499
500        let report = trace.generate_report();
501        assert!(report.contains("Query Execution Trace"));
502        assert!(report.contains("user"));
503        assert!(report.contains("Parsing"));
504    }
505
506    #[test]
507    fn test_complexity_metrics() {
508        let mut metrics = ComplexityMetrics::new();
509        metrics.add_field_score("user".to_string(), 10);
510        metrics.add_field_score("posts".to_string(), 20);
511        metrics.max_depth = 3;
512        metrics.field_count = 5;
513
514        assert_eq!(metrics.total_score, 30);
515        assert_eq!(metrics.field_count, 5);
516        assert!(!metrics.exceeds_threshold(100));
517        assert!(metrics.exceeds_threshold(20));
518    }
519
520    #[test]
521    fn test_complexity_report() {
522        let mut metrics = ComplexityMetrics::new();
523        metrics.add_field_score("user.posts".to_string(), 50);
524        metrics.total_score = 50;
525
526        let report = metrics.generate_report();
527        assert!(report.contains("Complexity Analysis"));
528        assert!(report.contains("50"));
529    }
530
531    #[test]
532    fn test_query_explanation() {
533        let mut explanation = QueryExplanation::new("{ user { name } }".to_string());
534        explanation.add_step(PlanStep::new("Parse query".to_string(), 1.0));
535        explanation.add_step(PlanStep::new("Execute query".to_string(), 10.0));
536        explanation.add_recommendation("Use caching".to_string());
537
538        assert_eq!(explanation.plan_steps.len(), 2);
539        assert_eq!(explanation.estimated_cost, 11.0);
540        assert_eq!(explanation.recommendations.len(), 1);
541    }
542
543    #[test]
544    fn test_query_explanation_report() {
545        let mut explanation = QueryExplanation::new("{ user { name } }".to_string());
546        explanation.add_step(PlanStep::new("Parse".to_string(), 1.0));
547
548        let report = explanation.generate_report();
549        assert!(report.contains("Execution Plan"));
550        assert!(report.contains("Parse"));
551    }
552
553    #[test]
554    fn test_plan_step() {
555        let step = PlanStep::new("Execute SPARQL".to_string(), 10.0)
556            .with_details("Query RDF store".to_string())
557            .with_expected_results(100);
558
559        assert_eq!(step.description, "Execute SPARQL");
560        assert_eq!(step.estimated_cost, 10.0);
561        assert_eq!(step.expected_results, Some(100));
562    }
563
564    #[test]
565    fn test_query_debugger_creation() {
566        let debugger = QueryDebugger::new()
567            .with_tracing(true)
568            .with_profiling(true)
569            .with_complexity_threshold(500);
570
571        assert!(debugger.enable_tracing);
572        assert!(debugger.enable_profiling);
573        assert_eq!(debugger.complexity_threshold, 500);
574    }
575
576    #[test]
577    fn test_query_debugger_trace() {
578        let debugger = QueryDebugger::new();
579        let trace = debugger.start_trace("{ user { name } }".to_string());
580
581        assert_eq!(trace.query, "{ user { name } }");
582        assert_eq!(trace.timings.len(), 0);
583    }
584
585    #[test]
586    fn test_query_debugger_complexity() {
587        let debugger = QueryDebugger::new();
588        let metrics = debugger.analyze_complexity("{ user { name } }");
589
590        assert_eq!(metrics.total_score, 0); // Simplified implementation
591    }
592
593    #[test]
594    fn test_query_debugger_explain() {
595        let debugger = QueryDebugger::new();
596        let explanation = debugger.explain_query("{ user { name } }".to_string());
597
598        assert!(!explanation.plan_steps.is_empty());
599        assert!(explanation.estimated_cost > 0.0);
600    }
601
602    #[test]
603    fn test_phase_timer() {
604        let timer = PhaseTimer::start(ExecutionPhase::Parsing);
605        std::thread::sleep(Duration::from_millis(10));
606        let (phase, duration) = timer.finish();
607
608        assert_eq!(phase, ExecutionPhase::Parsing);
609        assert!(duration.as_millis() >= 10);
610    }
611}