Skip to main content

fraiseql_cli/commands/
explain.rs

1//! Explain command - show query execution plan and complexity analysis
2//!
3//! Usage: fraiseql explain `<query>` --schema `<schema.compiled.json>` `[--json]`
4
5use anyhow::Result;
6use fraiseql_core::graphql::{complexity::ComplexityAnalyzer, parse_query};
7use serde::{Deserialize, Serialize};
8
9use crate::output::CommandResult;
10
11/// Request for explain command
12#[derive(Debug, Deserialize)]
13#[allow(dead_code)]
14pub struct ExplainRequest {
15    /// GraphQL query to analyze
16    pub query: String,
17}
18
19/// Response with execution plan and complexity info
20#[derive(Debug, Serialize)]
21pub struct ExplainResponse {
22    /// The analyzed query string
23    pub query:          String,
24    /// Compiled SQL representation (if available)
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub sql:            Option<String>,
27    /// Estimated query execution cost
28    pub estimated_cost: usize,
29    /// Complexity metrics
30    pub complexity:     ComplexityInfo,
31    /// Warnings about query structure
32    #[serde(skip_serializing_if = "Vec::is_empty")]
33    pub warnings:       Vec<String>,
34}
35
36/// Complexity analysis metrics for a query
37#[derive(Debug, Serialize)]
38pub struct ComplexityInfo {
39    /// Maximum nesting depth of the query
40    pub depth:       usize,
41    /// Total number of fields requested
42    pub field_count: usize,
43    /// Overall complexity score
44    pub score:       usize,
45}
46
47/// Run explain command
48pub fn run(query: &str) -> Result<CommandResult> {
49    // Parse the query to validate syntax
50    let parsed = parse_query(query)?;
51
52    // Analyze complexity
53    let analyzer = ComplexityAnalyzer::new();
54    let (depth, field_count, score) = analyzer.analyze_complexity(query);
55
56    // Generate warnings for unusual patterns
57    let mut warnings = Vec::new();
58
59    if depth > 10 {
60        warnings.push(format!(
61            "Query depth {depth} exceeds recommended maximum of 10 - consider breaking into multiple queries"
62        ));
63    }
64
65    if field_count > 50 {
66        warnings.push(format!(
67            "Query requests {field_count} fields - consider using pagination or field selection"
68        ));
69    }
70
71    if score > 500 {
72        warnings.push(format!(
73            "Query complexity score {score} is high - consider optimizing query structure"
74        ));
75    }
76
77    // Generate SQL representation (simplified for now)
78    // In a real implementation, this would use the QueryPlanner
79    let sql = format!(
80        "-- Query execution plan for: {}\n-- Depth: {}, Fields: {}, Cost: {}\nSELECT data FROM v_table LIMIT 1000;",
81        parsed.root_field, depth, field_count, score
82    );
83
84    let has_warnings = !warnings.is_empty();
85
86    let response = ExplainResponse {
87        query:          query.to_string(),
88        sql:            Some(sql),
89        estimated_cost: score,
90        complexity:     ComplexityInfo {
91            depth,
92            field_count,
93            score,
94        },
95        warnings:       warnings.clone(),
96    };
97
98    let result = if has_warnings {
99        CommandResult::success_with_warnings("explain", serde_json::to_value(&response)?, warnings)
100    } else {
101        CommandResult::success("explain", serde_json::to_value(&response)?)
102    };
103
104    Ok(result)
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_explain_simple_query() {
113        let query = "query { users { id } }";
114        let result = run(query);
115
116        assert!(result.is_ok());
117        let cmd_result = result.unwrap();
118        assert_eq!(cmd_result.status, "success");
119    }
120
121    #[test]
122    fn test_explain_invalid_query_fails() {
123        let query = "query { invalid {";
124        let result = run(query);
125
126        assert!(result.is_err());
127    }
128
129    #[test]
130    fn test_explain_detects_deep_nesting() {
131        let query = "query { a { b { c { d { e { f { g { h { i { j { k { l } } } } } } } } } } } }";
132        let result = run(query);
133
134        assert!(result.is_ok());
135        let cmd_result = result.unwrap();
136        if let Some(warnings) = cmd_result.data {
137            // Should have warnings for deep nesting
138            // Response structure: the data field contains ExplainResponse as JSON
139            assert!(!warnings.to_string().is_empty());
140        }
141    }
142}