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 candidates: Vec<&str> =
47 self.schema.mutations.iter().map(|m| m.name.as_str()).collect();
48 let suggestion = suggest_similar(name, &candidates);
49 let message = match suggestion.as_slice() {
50 [s] => format!(
51 "Mutation '{name}' not found in schema. Did you mean '{s}'?"
52 ),
53 _ => format!("Mutation '{name}' not found in schema"),
54 };
55 FraiseQLError::Validation {
56 message,
57 path: None,
58 }
59 })?;
60 let fn_name =
61 mutation_def.sql_source.clone().unwrap_or_else(|| format!("fn_{name}"));
62 Ok(super::super::ExplainPlan {
63 sql: format!("SELECT * FROM {fn_name}(...)"),
64 parameters: Vec::new(),
65 estimated_cost: 100,
66 views_accessed: vec![fn_name],
67 query_type: "mutation".to_string(),
68 })
69 },
70 QueryType::Aggregate(ref name) => {
71 let sql_source = self
72 .schema
73 .queries
74 .iter()
75 .find(|q| q.name == *name)
76 .and_then(|q| q.sql_source.clone())
77 .unwrap_or_else(|| "unknown".to_string());
78 Ok(super::super::ExplainPlan {
79 sql: format!("SELECT ... FROM {sql_source} -- aggregate"),
80 parameters: Vec::new(),
81 estimated_cost: 200,
82 views_accessed: vec![sql_source],
83 query_type: "aggregate".to_string(),
84 })
85 },
86 QueryType::Window(ref name) => {
87 let sql_source = self
88 .schema
89 .queries
90 .iter()
91 .find(|q| q.name == *name)
92 .and_then(|q| q.sql_source.clone())
93 .unwrap_or_else(|| "unknown".to_string());
94 Ok(super::super::ExplainPlan {
95 sql: format!("SELECT ... FROM {sql_source} -- window"),
96 parameters: Vec::new(),
97 estimated_cost: 250,
98 views_accessed: vec![sql_source],
99 query_type: "window".to_string(),
100 })
101 },
102 QueryType::IntrospectionSchema | QueryType::IntrospectionType(_) => {
103 Ok(super::super::ExplainPlan {
104 sql: String::new(),
105 parameters: Vec::new(),
106 estimated_cost: 0,
107 views_accessed: Vec::new(),
108 query_type: "introspection".to_string(),
109 })
110 },
111 QueryType::Federation(_) => Ok(super::super::ExplainPlan {
112 sql: String::new(),
113 parameters: Vec::new(),
114 estimated_cost: 0,
115 views_accessed: Vec::new(),
116 query_type: "federation".to_string(),
117 }),
118 QueryType::NodeQuery => Ok(super::super::ExplainPlan {
119 sql: String::new(),
120 parameters: Vec::new(),
121 estimated_cost: 50,
122 views_accessed: Vec::new(),
123 query_type: "node".to_string(),
124 }),
125 }
126 }
127}