fraiseql_cli/commands/
explain.rs1use anyhow::Result;
6use fraiseql_core::graphql::{complexity::ComplexityAnalyzer, parse_query};
7use serde::Serialize;
8
9use crate::output::CommandResult;
10
11#[derive(Debug, Serialize)]
13pub struct ExplainResponse {
14 pub query: String,
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub sql: Option<String>,
19 pub estimated_cost: usize,
21 pub complexity: ComplexityInfo,
23 #[serde(skip_serializing_if = "Vec::is_empty")]
25 pub warnings: Vec<String>,
26}
27
28#[derive(Debug, Serialize)]
30pub struct ComplexityInfo {
31 pub depth: usize,
33 pub field_count: usize,
35 pub score: usize,
37}
38
39pub fn run(query: &str) -> Result<CommandResult> {
41 let parsed = parse_query(query)?;
43
44 let analyzer = ComplexityAnalyzer::new();
46 let (depth, field_count, score) = analyzer.analyze_complexity(query);
47
48 let mut warnings = Vec::new();
50
51 if depth > 10 {
52 warnings.push(format!(
53 "Query depth {depth} exceeds recommended maximum of 10 - consider breaking into multiple queries"
54 ));
55 }
56
57 if field_count > 50 {
58 warnings.push(format!(
59 "Query requests {field_count} fields - consider using pagination or field selection"
60 ));
61 }
62
63 if score > 500 {
64 warnings.push(format!(
65 "Query complexity score {score} is high - consider optimizing query structure"
66 ));
67 }
68
69 let sql = format!(
72 "-- Query execution plan for: {}\n-- Depth: {}, Fields: {}, Cost: {}\nSELECT data FROM v_table LIMIT 1000;",
73 parsed.root_field, depth, field_count, score
74 );
75
76 let has_warnings = !warnings.is_empty();
77
78 let response = ExplainResponse {
79 query: query.to_string(),
80 sql: Some(sql),
81 estimated_cost: score,
82 complexity: ComplexityInfo {
83 depth,
84 field_count,
85 score,
86 },
87 warnings: warnings.clone(),
88 };
89
90 let result = if has_warnings {
91 CommandResult::success_with_warnings("explain", serde_json::to_value(&response)?, warnings)
92 } else {
93 CommandResult::success("explain", serde_json::to_value(&response)?)
94 };
95
96 Ok(result)
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_explain_simple_query() {
105 let query = "query { users { id } }";
106 let result = run(query);
107
108 assert!(result.is_ok());
109 let cmd_result = result.unwrap();
110 assert_eq!(cmd_result.status, "success");
111 }
112
113 #[test]
114 fn test_explain_invalid_query_fails() {
115 let query = "query { invalid {";
116 let result = run(query);
117
118 assert!(result.is_err());
119 }
120
121 #[test]
122 fn test_explain_detects_deep_nesting() {
123 let query = "query { a { b { c { d { e { f { g { h { i { j { k { l } } } } } } } } } } } }";
124 let result = run(query);
125
126 assert!(result.is_ok());
127 let cmd_result = result.unwrap();
128 if let Some(warnings) = cmd_result.data {
129 assert!(!warnings.to_string().is_empty());
132 }
133 }
134}