fraiseql_core/runtime/executor/
planning.rs1use super::{Executor, QueryType};
4use crate::{
5 db::traits::DatabaseAdapter,
6 error::{FraiseQLError, Result},
7 runtime::suggest_similar,
8};
9
10impl<A: DatabaseAdapter> Executor<A> {
11 pub fn plan_query(
20 &self,
21 query: &str,
22 variables: Option<&serde_json::Value>,
23 ) -> Result<super::super::ExplainPlan> {
24 let query_type = self.classify_query(query)?;
25
26 match query_type {
27 QueryType::Regular => {
28 let query_match = self.matcher.match_query(query, variables)?;
29 let view = query_match
30 .query_def
31 .sql_source
32 .clone()
33 .unwrap_or_else(|| "unknown".to_string());
34 let plan = self.planner.plan(&query_match)?;
35 Ok(super::super::ExplainPlan {
36 sql: plan.sql,
37 parameters: plan.parameters,
38 estimated_cost: plan.estimated_cost,
39 views_accessed: vec![view],
40 query_type: "regular".to_string(),
41 })
42 },
43 QueryType::Mutation { ref name, .. } => {
44 let mutation_def =
45 self.schema.mutations.iter().find(|m| m.name == *name).ok_or_else(|| {
46 let display_names: Vec<String> = self
47 .schema
48 .mutations
49 .iter()
50 .map(|m| self.schema.display_name(&m.name))
51 .collect();
52 let candidate_refs: Vec<&str> =
53 display_names.iter().map(String::as_str).collect();
54 let suggestion = suggest_similar(name, &candidate_refs);
55 let message = match suggestion.as_slice() {
56 [s] => format!(
57 "Mutation '{name}' not found in schema. Did you mean '{s}'?"
58 ),
59 _ => format!("Mutation '{name}' not found in schema"),
60 };
61 FraiseQLError::Validation {
62 message,
63 path: None,
64 }
65 })?;
66 let fn_name =
67 mutation_def.sql_source.clone().unwrap_or_else(|| format!("fn_{name}"));
68 Ok(super::super::ExplainPlan {
69 sql: format!("SELECT * FROM {fn_name}(...)"),
70 parameters: Vec::new(),
71 estimated_cost: 100,
72 views_accessed: vec![fn_name],
73 query_type: "mutation".to_string(),
74 })
75 },
76 QueryType::Aggregate(ref name) => {
77 let sql_source = self
78 .schema
79 .queries
80 .iter()
81 .find(|q| q.name == *name)
82 .and_then(|q| q.sql_source.clone())
83 .unwrap_or_else(|| "unknown".to_string());
84 Ok(super::super::ExplainPlan {
85 sql: format!("SELECT ... FROM {sql_source} -- aggregate"),
86 parameters: Vec::new(),
87 estimated_cost: 200,
88 views_accessed: vec![sql_source],
89 query_type: "aggregate".to_string(),
90 })
91 },
92 QueryType::Window(ref name) => {
93 let sql_source = self
94 .schema
95 .queries
96 .iter()
97 .find(|q| q.name == *name)
98 .and_then(|q| q.sql_source.clone())
99 .unwrap_or_else(|| "unknown".to_string());
100 Ok(super::super::ExplainPlan {
101 sql: format!("SELECT ... FROM {sql_source} -- window"),
102 parameters: Vec::new(),
103 estimated_cost: 250,
104 views_accessed: vec![sql_source],
105 query_type: "window".to_string(),
106 })
107 },
108 QueryType::IntrospectionSchema | QueryType::IntrospectionType(_) => {
109 Ok(super::super::ExplainPlan {
110 sql: String::new(),
111 parameters: Vec::new(),
112 estimated_cost: 0,
113 views_accessed: Vec::new(),
114 query_type: "introspection".to_string(),
115 })
116 },
117 QueryType::Federation(_) => Ok(super::super::ExplainPlan {
118 sql: String::new(),
119 parameters: Vec::new(),
120 estimated_cost: 0,
121 views_accessed: Vec::new(),
122 query_type: "federation".to_string(),
123 }),
124 QueryType::NodeQuery { .. } => Ok(super::super::ExplainPlan {
125 sql: String::new(),
126 parameters: Vec::new(),
127 estimated_cost: 50,
128 views_accessed: Vec::new(),
129 query_type: "node".to_string(),
130 }),
131 }
132 }
133}