oxirs_gql/federation/
query_planner.rs1use anyhow::{Context, Result};
4use futures_util::future;
5use serde::{Deserialize, Serialize};
6use std::collections::{HashMap, HashSet, VecDeque};
7use std::sync::Arc;
8
9use super::config::FederationConfig;
10use super::schema_stitcher::SchemaStitcher;
11use crate::ast::{Document, Field, OperationDefinition, Selection, SelectionSet};
12use crate::types::Schema;
13
14pub struct QueryPlanner {
16 #[allow(dead_code)]
17 schema_stitcher: Arc<SchemaStitcher>,
18 config: FederationConfig,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct QueryPlan {
24 pub steps: Vec<QueryStep>,
26 pub estimated_cost: f64,
28 pub can_execute_parallel: bool,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct QueryStep {
35 pub endpoint_id: String,
37 pub query_fragment: String,
39 pub dependencies: Vec<usize>,
41 pub result_variables: Vec<String>,
43}
44
45impl QueryPlanner {
46 pub fn new(schema_stitcher: Arc<SchemaStitcher>, config: FederationConfig) -> Self {
47 Self {
48 schema_stitcher,
49 config,
50 }
51 }
52
53 pub async fn plan_query(&self, query: &Document, merged_schema: &Schema) -> Result<QueryPlan> {
55 let mut steps = Vec::new();
56
57 for definition in &query.definitions {
59 if let crate::ast::Definition::Operation(op) = definition {
60 let service_steps = self.analyze_operation(op, merged_schema).await?;
61 steps.extend(service_steps);
62 }
63 }
64
65 self.optimize_step_order(&mut steps);
67
68 let estimated_cost = self.calculate_execution_cost(&steps);
70
71 let can_execute_parallel = self.can_parallelize(&steps);
73
74 Ok(QueryPlan {
75 steps,
76 estimated_cost,
77 can_execute_parallel,
78 })
79 }
80
81 async fn analyze_operation(
83 &self,
84 operation: &OperationDefinition,
85 schema: &Schema,
86 ) -> Result<Vec<QueryStep>> {
87 let mut steps = Vec::new();
88
89 match operation.operation_type {
90 crate::ast::OperationType::Query => {
91 steps.extend(
92 self.analyze_selection_set(&operation.selection_set, schema)
93 .await?,
94 );
95 }
96 crate::ast::OperationType::Mutation => {
97 steps.extend(
98 self.analyze_selection_set(&operation.selection_set, schema)
99 .await?,
100 );
101 }
102 crate::ast::OperationType::Subscription => {
103 steps.extend(
104 self.analyze_selection_set(&operation.selection_set, schema)
105 .await?,
106 );
107 }
108 }
109
110 Ok(steps)
111 }
112
113 async fn analyze_selection_set(
115 &self,
116 selection_set: &SelectionSet,
117 schema: &Schema,
118 ) -> Result<Vec<QueryStep>> {
119 let mut steps = Vec::new();
120 let mut service_queries: HashMap<String, Vec<String>> = HashMap::new();
121
122 for selection in &selection_set.selections {
123 match selection {
124 Selection::Field(field) => {
125 let service_id = self.determine_field_service(field, schema).await?;
126 service_queries
127 .entry(service_id.clone())
128 .or_default()
129 .push(field.name.clone());
130
131 if let Some(nested_selection_set) = &field.selection_set {
133 let nested_steps =
134 Box::pin(self.analyze_selection_set(nested_selection_set, schema))
135 .await?;
136 steps.extend(nested_steps);
137 }
138 }
139 Selection::InlineFragment(fragment) => {
140 let nested_steps =
141 Box::pin(self.analyze_selection_set(&fragment.selection_set, schema))
142 .await?;
143 steps.extend(nested_steps);
144 }
145 Selection::FragmentSpread(_) => {
146 }
148 }
149 }
150
151 for (service_id, fields) in service_queries {
153 let query_fragment = self.build_query_fragment(&fields);
154 steps.push(QueryStep {
155 endpoint_id: service_id,
156 query_fragment,
157 dependencies: Vec::new(),
158 result_variables: fields,
159 });
160 }
161
162 Ok(steps)
163 }
164
165 async fn determine_field_service(&self, field: &Field, _schema: &Schema) -> Result<String> {
167 for endpoint in &self.config.endpoints {
172 let namespace = endpoint.namespace.as_deref().unwrap_or(&endpoint.id);
173 if field.name.starts_with(&format!("{namespace}_")) {
174 return Ok(endpoint.id.clone());
175 }
176 }
177
178 Ok("local".to_string())
180 }
181
182 fn build_query_fragment(&self, fields: &[String]) -> String {
184 let field_list = fields.join(" ");
185 format!("{{ {field_list} }}")
186 }
187
188 fn optimize_step_order(&self, steps: &mut Vec<QueryStep>) {
190 let mut sorted_steps = Vec::new();
192 let mut remaining_steps: VecDeque<_> = steps.iter().enumerate().collect();
193 let mut resolved_indices = HashSet::new();
194
195 while !remaining_steps.is_empty() {
196 let mut made_progress = false;
197
198 for i in 0..remaining_steps.len() {
199 let (idx, step) = remaining_steps[i];
200
201 let dependencies_resolved = step
203 .dependencies
204 .iter()
205 .all(|dep_idx| resolved_indices.contains(dep_idx));
206
207 if dependencies_resolved {
208 sorted_steps.push(step.clone());
209 resolved_indices.insert(idx);
210 remaining_steps.remove(i);
211 made_progress = true;
212 break;
213 }
214 }
215
216 if !made_progress {
217 tracing::warn!("Unable to resolve all step dependencies, using original order");
219 break;
220 }
221 }
222
223 if sorted_steps.len() == steps.len() {
224 *steps = sorted_steps;
225 }
226 }
227
228 fn calculate_execution_cost(&self, steps: &[QueryStep]) -> f64 {
230 let mut total_cost = 0.0;
231
232 for step in steps {
233 total_cost += 1.0;
235
236 total_cost += step.result_variables.len() as f64 * 0.1;
238
239 total_cost += step.dependencies.len() as f64 * 0.5;
241 }
242
243 total_cost
244 }
245
246 fn can_parallelize(&self, steps: &[QueryStep]) -> bool {
248 for step in steps {
250 if !step.dependencies.is_empty() {
251 return false;
252 }
253 }
254
255 true
256 }
257
258 pub async fn execute_plan(&self, plan: &QueryPlan) -> Result<serde_json::Value> {
260 let mut results: HashMap<usize, serde_json::Value> = HashMap::new();
261
262 if plan.can_execute_parallel {
263 let futures: Vec<_> = plan
265 .steps
266 .iter()
267 .enumerate()
268 .map(|(idx, step)| {
269 let step = step.clone();
270 async move {
271 let result = self.execute_step(&step).await?;
272 Ok::<(usize, serde_json::Value), anyhow::Error>((idx, result))
273 }
274 })
275 .collect();
276
277 let results_vec = future::try_join_all(futures).await?;
278 results.extend(results_vec);
279 } else {
280 for (idx, step) in plan.steps.iter().enumerate() {
282 let result = self.execute_step(step).await?;
283 results.insert(idx, result);
284 }
285 }
286
287 self.merge_execution_results(&results)
289 }
290
291 async fn execute_step(&self, step: &QueryStep) -> Result<serde_json::Value> {
293 let endpoint = self
295 .config
296 .endpoints
297 .iter()
298 .find(|ep| ep.id == step.endpoint_id)
299 .ok_or_else(|| anyhow::anyhow!("Endpoint not found: {}", step.endpoint_id))?;
300
301 let client = reqwest::Client::new();
303
304 let mut request = client.post(&endpoint.url).json(&serde_json::json!({
306 "query": step.query_fragment,
307 "variables": {}
308 }));
309
310 if let Some(auth) = &endpoint.auth_header {
312 request = request.header("Authorization", auth);
313 }
314
315 let response = request
317 .timeout(std::time::Duration::from_secs(endpoint.timeout_secs))
318 .send()
319 .await
320 .context("Failed to execute federated query step")?;
321
322 if !response.status().is_success() {
323 return Err(anyhow::anyhow!(
324 "Query step failed with status: {}",
325 response.status()
326 ));
327 }
328
329 let result: serde_json::Value = response
330 .json()
331 .await
332 .context("Failed to parse query step response")?;
333
334 if let Some(errors) = result.get("errors") {
336 return Err(anyhow::anyhow!(
337 "GraphQL errors in federated step: {}",
338 serde_json::to_string_pretty(errors)?
339 ));
340 }
341
342 Ok(result
343 .get("data")
344 .unwrap_or(&serde_json::Value::Null)
345 .clone())
346 }
347
348 fn merge_execution_results(
350 &self,
351 results: &HashMap<usize, serde_json::Value>,
352 ) -> Result<serde_json::Value> {
353 let mut merged = serde_json::Map::new();
354
355 for result in results.values() {
356 if let serde_json::Value::Object(obj) = result {
357 for (key, value) in obj {
358 merged.insert(key.clone(), value.clone());
359 }
360 }
361 }
362
363 Ok(serde_json::Value::Object(merged))
364 }
365}