Skip to main content

oxirs_gql/federation/
query_planner.rs

1//! Query planning for federated GraphQL queries
2
3use 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
14/// Query planner for federated queries
15pub struct QueryPlanner {
16    #[allow(dead_code)]
17    schema_stitcher: Arc<SchemaStitcher>,
18    config: FederationConfig,
19}
20
21/// Execution plan for a federated query
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct QueryPlan {
24    /// List of execution steps
25    pub steps: Vec<QueryStep>,
26    /// Estimated execution cost
27    pub estimated_cost: f64,
28    /// Whether parallel execution is possible
29    pub can_execute_parallel: bool,
30}
31
32/// Individual execution step in a query plan
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct QueryStep {
35    /// Target endpoint for this step
36    pub endpoint_id: String,
37    /// GraphQL query fragment for this step
38    pub query_fragment: String,
39    /// Dependencies on other steps
40    pub dependencies: Vec<usize>,
41    /// Expected result variables
42    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    /// Plan execution for a federated query
54    pub async fn plan_query(&self, query: &Document, merged_schema: &Schema) -> Result<QueryPlan> {
55        let mut steps = Vec::new();
56
57        // Analyze the query to identify which services need to be involved
58        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        // Optimize step ordering
66        self.optimize_step_order(&mut steps);
67
68        // Calculate estimated cost
69        let estimated_cost = self.calculate_execution_cost(&steps);
70
71        // Determine if parallel execution is possible
72        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    /// Analyze an operation to determine required services
82    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    /// Analyze a selection set to determine required services
114    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                    // Recursively analyze nested selections
132                    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                    // Fragment spreads would need additional processing
147                }
148            }
149        }
150
151        // Create query steps for each service
152        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    /// Determine which service owns a particular field
166    async fn determine_field_service(&self, field: &Field, _schema: &Schema) -> Result<String> {
167        // This is a simplified implementation
168        // In practice, you'd need to analyze the field's type and ownership
169
170        // For now, look for service prefix in field type
171        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        // Default to local service
179        Ok("local".to_string())
180    }
181
182    /// Build a GraphQL query fragment for specific fields
183    fn build_query_fragment(&self, fields: &[String]) -> String {
184        let field_list = fields.join(" ");
185        format!("{{ {field_list} }}")
186    }
187
188    /// Optimize the ordering of execution steps
189    fn optimize_step_order(&self, steps: &mut Vec<QueryStep>) {
190        // Topological sort based on dependencies
191        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                // Check if all dependencies are resolved
202                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                // Circular dependency or other issue
218                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    /// Calculate estimated execution cost
229    fn calculate_execution_cost(&self, steps: &[QueryStep]) -> f64 {
230        let mut total_cost = 0.0;
231
232        for step in steps {
233            // Base cost per step
234            total_cost += 1.0;
235
236            // Add cost based on query complexity
237            total_cost += step.result_variables.len() as f64 * 0.1;
238
239            // Add cost for dependencies (serialization overhead)
240            total_cost += step.dependencies.len() as f64 * 0.5;
241        }
242
243        total_cost
244    }
245
246    /// Determine if steps can be executed in parallel
247    fn can_parallelize(&self, steps: &[QueryStep]) -> bool {
248        // Check if any step has dependencies
249        for step in steps {
250            if !step.dependencies.is_empty() {
251                return false;
252            }
253        }
254
255        true
256    }
257
258    /// Execute a query plan
259    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            // Execute all steps in parallel
264            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            // Execute steps sequentially based on dependencies
281            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        // Merge results
288        self.merge_execution_results(&results)
289    }
290
291    /// Execute a single query step
292    async fn execute_step(&self, step: &QueryStep) -> Result<serde_json::Value> {
293        // Find the endpoint
294        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        // Create HTTP client
302        let client = reqwest::Client::new();
303
304        // Build request
305        let mut request = client.post(&endpoint.url).json(&serde_json::json!({
306            "query": step.query_fragment,
307            "variables": {}
308        }));
309
310        // Add authentication if provided
311        if let Some(auth) = &endpoint.auth_header {
312            request = request.header("Authorization", auth);
313        }
314
315        // Execute request
316        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        // Check for GraphQL errors
335        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    /// Merge execution results from multiple steps
349    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}