elif_orm/loading/optimizer/
plan.rs

1use crate::{
2    error::{OrmError, OrmResult},
3    relationships::{RelationshipType, metadata::RelationshipMetadata},
4};
5use std::collections::{HashMap, HashSet};
6
7/// Represents a node in the query execution plan
8#[derive(Debug, Clone)]
9pub struct QueryNode {
10    /// Unique identifier for this node
11    pub id: String,
12    /// Table to query
13    pub table: String,
14    /// Type of relationship from parent
15    pub relationship_type: Option<RelationshipType>,
16    /// Full relationship metadata if available
17    pub relationship_metadata: Option<RelationshipMetadata>,
18    /// Parent node ID (None for root)
19    pub parent_id: Option<String>,
20    /// Child node IDs
21    pub children: Vec<String>,
22    /// Depth in the relationship tree
23    pub depth: usize,
24    /// Estimated row count (can be updated from metadata)
25    pub estimated_rows: usize,
26    /// Whether this node can be executed in parallel with siblings
27    pub parallel_safe: bool,
28    /// Foreign key used to join with parent
29    pub foreign_key: Option<String>,
30    /// Additional constraints for optimization
31    pub constraints: Vec<String>,
32    /// Column names available in this table (for better query construction)
33    pub available_columns: Vec<String>,
34    /// Index hints for the optimizer
35    pub index_hints: Vec<String>,
36}
37
38impl QueryNode {
39    /// Create a new query node
40    pub fn new(id: String, table: String) -> Self {
41        Self {
42            id,
43            table,
44            relationship_type: None,
45            relationship_metadata: None,
46            parent_id: None,
47            children: Vec::new(),
48            depth: 0,
49            estimated_rows: 1000, // Default estimate
50            parallel_safe: true,
51            foreign_key: None,
52            constraints: Vec::new(),
53            available_columns: Vec::new(),
54            index_hints: Vec::new(),
55        }
56    }
57
58    /// Create a root node (no parent)
59    pub fn root(id: String, table: String) -> Self {
60        let mut node = Self::new(id, table);
61        node.depth = 0;
62        node
63    }
64
65    /// Create a child node with parent relationship
66    pub fn child(
67        id: String, 
68        table: String, 
69        parent_id: String, 
70        relationship_type: RelationshipType,
71        foreign_key: String
72    ) -> Self {
73        let mut node = Self::new(id, table);
74        node.parent_id = Some(parent_id);
75        node.relationship_type = Some(relationship_type);
76        node.foreign_key = Some(foreign_key);
77        node
78    }
79
80    /// Create a child node with full relationship metadata
81    pub fn child_with_metadata(
82        id: String,
83        table: String,
84        parent_id: String,
85        metadata: RelationshipMetadata
86    ) -> Self {
87        let mut node = Self::new(id, table);
88        node.parent_id = Some(parent_id);
89        node.relationship_type = Some(metadata.relationship_type);
90        node.relationship_metadata = Some(metadata.clone());
91        node.foreign_key = Some(metadata.foreign_key.primary_column().to_string());
92        
93        // Set estimated rows based on relationship type
94        node.estimated_rows = match metadata.relationship_type {
95            RelationshipType::HasMany | RelationshipType::ManyToMany | RelationshipType::MorphMany => 10000,
96            _ => 1, // HasOne, BelongsTo, MorphOne, MorphTo
97        };
98        
99        // Set parallel safety based on relationship characteristics
100        node.parallel_safe = !metadata.relationship_type.requires_pivot();
101        
102        node
103    }
104
105    /// Add a child node ID
106    pub fn add_child(&mut self, child_id: String) {
107        if !self.children.contains(&child_id) {
108            self.children.push(child_id);
109        }
110    }
111
112    /// Set the depth for this node
113    pub fn set_depth(&mut self, depth: usize) {
114        self.depth = depth;
115    }
116
117    /// Set row count estimate
118    pub fn set_estimated_rows(&mut self, rows: usize) {
119        self.estimated_rows = rows;
120    }
121
122    /// Set parallel safety
123    pub fn set_parallel_safe(&mut self, safe: bool) {
124        self.parallel_safe = safe;
125    }
126
127    /// Add a constraint
128    pub fn add_constraint(&mut self, constraint: String) {
129        self.constraints.push(constraint);
130    }
131
132    /// Check if this node is a root node
133    pub fn is_root(&self) -> bool {
134        self.parent_id.is_none()
135    }
136
137    /// Check if this node is a leaf node
138    pub fn is_leaf(&self) -> bool {
139        self.children.is_empty()
140    }
141
142    /// Update metadata for this node
143    pub fn set_metadata(&mut self, metadata: RelationshipMetadata) {
144        self.relationship_type = Some(metadata.relationship_type);
145        self.relationship_metadata = Some(metadata.clone());
146        self.foreign_key = Some(metadata.foreign_key.primary_column().to_string());
147        
148        // Update estimates based on metadata
149        self.estimated_rows = match metadata.relationship_type {
150            RelationshipType::HasMany | RelationshipType::ManyToMany | RelationshipType::MorphMany => 10000,
151            _ => 1,
152        };
153        
154        self.parallel_safe = !metadata.relationship_type.requires_pivot();
155    }
156
157    /// Set column information for better query construction
158    pub fn set_columns(&mut self, columns: Vec<String>) {
159        self.available_columns = columns;
160    }
161
162    /// Add index hints for optimization
163    pub fn add_index_hint(&mut self, index: String) {
164        if !self.index_hints.contains(&index) {
165            self.index_hints.push(index);
166        }
167    }
168
169    /// Get the primary key column name (defaults to "id")
170    pub fn primary_key(&self) -> &str {
171        if let Some(metadata) = &self.relationship_metadata {
172            &metadata.local_key
173        } else {
174            "id"
175        }
176    }
177
178    /// Get the foreign key column name for relationships
179    pub fn get_foreign_key(&self) -> Option<&str> {
180        self.foreign_key.as_deref()
181    }
182
183    /// Check if this node represents a collection relationship
184    pub fn is_collection(&self) -> bool {
185        self.relationship_type
186            .map(|rt| rt.is_collection())
187            .unwrap_or(false)
188    }
189
190    /// Check if this node requires a pivot table
191    pub fn requires_pivot(&self) -> bool {
192        self.relationship_type
193            .map(|rt| rt.requires_pivot())
194            .unwrap_or(false)
195    }
196}
197
198/// Query execution plan for optimized loading
199#[derive(Debug)]
200pub struct QueryPlan {
201    /// All nodes in the plan, keyed by ID
202    pub nodes: HashMap<String, QueryNode>,
203    /// Root node IDs (entry points)
204    pub roots: Vec<String>,
205    /// Execution phases (nodes that can run in parallel)
206    pub execution_phases: Vec<Vec<String>>,
207    /// Maximum depth of the plan
208    pub max_depth: usize,
209    /// Total estimated rows
210    pub total_estimated_rows: usize,
211    /// Plan metadata
212    pub metadata: HashMap<String, serde_json::Value>,
213}
214
215impl QueryPlan {
216    /// Create an empty query plan
217    pub fn new() -> Self {
218        Self {
219            nodes: HashMap::new(),
220            roots: Vec::new(),
221            execution_phases: Vec::new(),
222            max_depth: 0,
223            total_estimated_rows: 0,
224            metadata: HashMap::new(),
225        }
226    }
227
228    /// Add a node to the plan
229    pub fn add_node(&mut self, node: QueryNode) {
230        self.max_depth = self.max_depth.max(node.depth);
231        self.total_estimated_rows += node.estimated_rows;
232        
233        if node.parent_id.is_none() {
234            self.roots.push(node.id.clone());
235        }
236        
237        // Update parent-child relationships
238        if let Some(parent_id) = &node.parent_id {
239            if let Some(parent) = self.nodes.get_mut(parent_id) {
240                parent.add_child(node.id.clone());
241            }
242        }
243        
244        self.nodes.insert(node.id.clone(), node);
245    }
246
247    /// Build execution phases (groups of nodes that can run in parallel)
248    pub fn build_execution_phases(&mut self) -> OrmResult<()> {
249        let mut phases = Vec::new();
250        let mut visited = HashSet::new();
251        let mut current_depth = 0;
252        
253        // Validate plan before building phases
254        self.validate()?;
255        
256        while visited.len() < self.nodes.len() && current_depth <= self.max_depth {
257            let mut phase_nodes = Vec::new();
258            
259            // Find all nodes at the current depth that haven't been visited
260            for (id, node) in &self.nodes {
261                if !visited.contains(id) && node.depth == current_depth {
262                    // Check if all parent nodes have been visited
263                    let ready = if let Some(parent_id) = &node.parent_id {
264                        visited.contains(parent_id)
265                    } else {
266                        true // Root nodes are always ready
267                    };
268                    
269                    if ready && node.parallel_safe {
270                        phase_nodes.push(id.clone());
271                    }
272                }
273            }
274            
275            // If no parallel-safe nodes found, add sequential nodes
276            if phase_nodes.is_empty() {
277                for (id, node) in &self.nodes {
278                    if !visited.contains(id) && node.depth == current_depth {
279                        let ready = if let Some(parent_id) = &node.parent_id {
280                            visited.contains(parent_id)
281                        } else {
282                            true
283                        };
284                        
285                        if ready {
286                            phase_nodes.push(id.clone());
287                            break; // Only one sequential node per phase
288                        }
289                    }
290                }
291            }
292            
293            if phase_nodes.is_empty() {
294                current_depth += 1;
295                continue;
296            }
297            
298            // Mark phase nodes as visited
299            for id in &phase_nodes {
300                visited.insert(id.clone());
301            }
302            
303            if !phase_nodes.is_empty() {
304                phases.push(phase_nodes);
305            }
306            
307            current_depth += 1;
308        }
309        
310        self.execution_phases = phases;
311        Ok(())
312    }
313
314    /// Validate the query plan for consistency
315    pub fn validate(&self) -> OrmResult<()> {
316        // Check for circular dependencies
317        for root_id in &self.roots {
318            let mut visited = HashSet::new();
319            let mut path = Vec::new();
320            
321            if self.has_cycle_from(root_id, &mut visited, &mut path) {
322                return Err(OrmError::Query("Circular dependency detected in query plan".into()));
323            }
324        }
325        
326        // Validate parent-child relationships
327        for (id, node) in &self.nodes {
328            if let Some(parent_id) = &node.parent_id {
329                if !self.nodes.contains_key(parent_id) {
330                    return Err(OrmError::Query(format!("Parent node '{}' not found for node '{}'", parent_id, id)));
331                }
332            }
333            
334            for child_id in &node.children {
335                if !self.nodes.contains_key(child_id) {
336                    return Err(OrmError::Query(format!("Child node '{}' not found for node '{}'", child_id, id)));
337                }
338            }
339        }
340        
341        Ok(())
342    }
343
344    /// DFS helper for cycle detection
345    fn has_cycle_from(
346        &self,
347        node_id: &str,
348        visited: &mut HashSet<String>,
349        path: &mut Vec<String>,
350    ) -> bool {
351        if path.contains(&node_id.to_string()) {
352            return true; // Found a cycle
353        }
354        
355        if visited.contains(node_id) {
356            return false; // Already processed this subtree
357        }
358        
359        path.push(node_id.to_string());
360        visited.insert(node_id.to_string());
361        
362        if let Some(node) = self.nodes.get(node_id) {
363            for child_id in &node.children {
364                if self.has_cycle_from(child_id, visited, path) {
365                    return true;
366                }
367            }
368        }
369        
370        path.pop();
371        false
372    }
373
374    /// Get nodes at a specific depth
375    pub fn nodes_at_depth(&self, depth: usize) -> Vec<&QueryNode> {
376        self.nodes
377            .values()
378            .filter(|node| node.depth == depth)
379            .collect()
380    }
381
382    /// Get all leaf nodes
383    pub fn leaf_nodes(&self) -> Vec<&QueryNode> {
384        self.nodes
385            .values()
386            .filter(|node| node.is_leaf())
387            .collect()
388    }
389
390    /// Calculate plan complexity score
391    pub fn complexity_score(&self) -> f64 {
392        let depth_penalty = self.max_depth as f64 * 1.5;
393        let node_penalty = self.nodes.len() as f64 * 0.5;
394        let row_penalty = (self.total_estimated_rows as f64).log10() * 2.0;
395        
396        depth_penalty + node_penalty + row_penalty
397    }
398
399    /// Add metadata to the plan
400    pub fn add_metadata(&mut self, key: String, value: serde_json::Value) {
401        self.metadata.insert(key, value);
402    }
403
404    /// Get plan statistics
405    pub fn statistics(&self) -> PlanStatistics {
406        PlanStatistics {
407            total_nodes: self.nodes.len(),
408            root_nodes: self.roots.len(),
409            leaf_nodes: self.leaf_nodes().len(),
410            max_depth: self.max_depth,
411            total_estimated_rows: self.total_estimated_rows,
412            execution_phases: self.execution_phases.len(),
413            complexity_score: self.complexity_score(),
414            parallel_nodes: self.nodes.values().filter(|n| n.parallel_safe).count(),
415        }
416    }
417}
418
419impl Default for QueryPlan {
420    fn default() -> Self {
421        Self::new()
422    }
423}
424
425/// Statistics about a query plan
426#[derive(Debug, Clone)]
427pub struct PlanStatistics {
428    pub total_nodes: usize,
429    pub root_nodes: usize,
430    pub leaf_nodes: usize,
431    pub max_depth: usize,
432    pub total_estimated_rows: usize,
433    pub execution_phases: usize,
434    pub complexity_score: f64,
435    pub parallel_nodes: usize,
436}