fraiseql_cli/commands/
explain.rs1use anyhow::Result;
6use fraiseql_core::graphql::{DEFAULT_MAX_ALIASES, complexity::RequestValidator, 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 score: usize,
35 pub alias_count: usize,
37}
38
39pub fn run(query: &str) -> Result<CommandResult> {
46 let parsed = parse_query(query)?;
48
49 let validator = RequestValidator::default();
51 let metrics = validator.analyze(query)?;
52
53 let depth = metrics.depth;
54 let score = metrics.complexity;
55 let alias_count = metrics.alias_count;
56
57 let mut warnings = Vec::new();
59
60 if depth > 10 {
61 warnings.push(format!(
62 "Query depth {depth} exceeds recommended maximum of 10 - consider breaking into multiple queries"
63 ));
64 }
65
66 if score > 100 {
67 warnings.push(format!(
68 "Query complexity score {score} is high - consider optimizing query structure"
69 ));
70 }
71
72 if alias_count > DEFAULT_MAX_ALIASES {
73 warnings.push(format!("Query has {alias_count} aliases — consider reducing alias count"));
74 }
75
76 let sql = format!(
79 "-- Query execution plan for: {}\n-- Depth: {}, Score: {}, Aliases: {}\nSELECT data FROM v_table LIMIT 1000;",
80 parsed.root_field, depth, score, alias_count
81 );
82
83 let has_warnings = !warnings.is_empty();
84
85 let response = ExplainResponse {
86 query: query.to_string(),
87 sql: Some(sql),
88 estimated_cost: score,
89 complexity: ComplexityInfo {
90 depth,
91 score,
92 alias_count,
93 },
94 warnings: warnings.clone(),
95 };
96
97 let result = if has_warnings {
98 CommandResult::success_with_warnings("explain", serde_json::to_value(&response)?, warnings)
99 } else {
100 CommandResult::success("explain", serde_json::to_value(&response)?)
101 };
102
103 Ok(result)
104}