1use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35use std::fmt::Write as FmtWrite;
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
39pub enum VisualizationFormat {
40 Dot,
42 Mermaid,
44 AsciiTree,
46 Json,
48 Html,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct PlanNode {
55 pub id: String,
57 pub node_type: String,
59 pub name: String,
61 pub parent_id: Option<String>,
63 pub children: Vec<String>,
65 pub estimated_cost: f64,
67 pub execution_time_ms: Option<u64>,
69 pub metadata: HashMap<String, serde_json::Value>,
71 pub parallel_execution: bool,
73 pub dependencies: Vec<String>,
75}
76
77impl PlanNode {
78 pub fn new(id: String, node_type: String, name: String) -> Self {
80 Self {
81 id,
82 node_type,
83 name,
84 parent_id: None,
85 children: Vec::new(),
86 estimated_cost: 1.0,
87 execution_time_ms: None,
88 metadata: HashMap::new(),
89 parallel_execution: true,
90 dependencies: Vec::new(),
91 }
92 }
93
94 pub fn with_parent(mut self, parent_id: String) -> Self {
96 self.parent_id = Some(parent_id);
97 self
98 }
99
100 pub fn with_child(mut self, child_id: String) -> Self {
102 self.children.push(child_id);
103 self
104 }
105
106 pub fn with_cost(mut self, cost: f64) -> Self {
108 self.estimated_cost = cost;
109 self
110 }
111
112 pub fn with_execution_time(mut self, time_ms: u64) -> Self {
114 self.execution_time_ms = Some(time_ms);
115 self
116 }
117
118 pub fn with_metadata(mut self, key: String, value: serde_json::Value) -> Self {
120 self.metadata.insert(key, value);
121 self
122 }
123
124 pub fn with_parallel(mut self, parallel: bool) -> Self {
126 self.parallel_execution = parallel;
127 self
128 }
129
130 pub fn with_dependency(mut self, dep_id: String) -> Self {
132 self.dependencies.push(dep_id);
133 self
134 }
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct QueryPlan {
140 pub root_id: String,
142 pub nodes: HashMap<String, PlanNode>,
144 pub query: String,
146 pub total_cost: f64,
148 pub total_execution_time_ms: Option<u64>,
150}
151
152impl QueryPlan {
153 pub fn new(root_id: String, query: String) -> Self {
155 Self {
156 root_id,
157 nodes: HashMap::new(),
158 query,
159 total_cost: 0.0,
160 total_execution_time_ms: None,
161 }
162 }
163
164 pub fn add_node(&mut self, node: PlanNode) {
166 self.total_cost += node.estimated_cost;
167 self.nodes.insert(node.id.clone(), node);
168 }
169
170 pub fn root_node(&self) -> Option<&PlanNode> {
172 self.nodes.get(&self.root_id)
173 }
174
175 pub fn get_node(&self, id: &str) -> Option<&PlanNode> {
177 self.nodes.get(id)
178 }
179
180 pub fn leaf_nodes(&self) -> Vec<&PlanNode> {
182 self.nodes
183 .values()
184 .filter(|node| node.children.is_empty())
185 .collect()
186 }
187
188 pub fn execution_levels(&self) -> HashMap<String, usize> {
190 let mut levels = HashMap::new();
191 let mut queue = vec![(self.root_id.clone(), 0)];
192
193 while let Some((node_id, level)) = queue.pop() {
194 levels.insert(node_id.clone(), level);
195
196 if let Some(node) = self.nodes.get(&node_id) {
197 for child_id in &node.children {
198 queue.push((child_id.clone(), level + 1));
199 }
200 }
201 }
202
203 levels
204 }
205
206 pub fn parallel_execution_groups(&self) -> Vec<Vec<String>> {
208 let levels = self.execution_levels();
209 let max_level = levels.values().max().copied().unwrap_or(0);
210
211 let mut groups = vec![Vec::new(); max_level + 1];
212
213 for (node_id, level) in levels {
214 if let Some(node) = self.nodes.get(&node_id) {
215 if node.parallel_execution && node.dependencies.is_empty() {
216 groups[level].push(node_id);
217 }
218 }
219 }
220
221 groups
222 }
223
224 pub fn set_total_execution_time(&mut self, time_ms: u64) {
226 self.total_execution_time_ms = Some(time_ms);
227 }
228}
229
230pub struct QueryPlanVisualizer {
232 pub show_costs: bool,
234 pub show_execution_times: bool,
236 pub show_metadata: bool,
238 pub colors: HashMap<String, String>,
240}
241
242impl QueryPlanVisualizer {
243 pub fn new() -> Self {
245 let mut colors = HashMap::new();
246 colors.insert("Field".to_string(), "#87CEEB".to_string()); colors.insert("Object".to_string(), "#90EE90".to_string()); colors.insert("List".to_string(), "#FFB6C1".to_string()); colors.insert("Defer".to_string(), "#FFD700".to_string()); colors.insert("Stream".to_string(), "#FF6347".to_string()); Self {
253 show_costs: true,
254 show_execution_times: true,
255 show_metadata: false,
256 colors,
257 }
258 }
259
260 pub fn with_costs(mut self, show: bool) -> Self {
262 self.show_costs = show;
263 self
264 }
265
266 pub fn with_execution_times(mut self, show: bool) -> Self {
268 self.show_execution_times = show;
269 self
270 }
271
272 pub fn with_metadata(mut self, show: bool) -> Self {
274 self.show_metadata = show;
275 self
276 }
277
278 pub fn visualize(
280 &self,
281 plan: &QueryPlan,
282 format: VisualizationFormat,
283 ) -> Result<String, VisualizationError> {
284 match format {
285 VisualizationFormat::Dot => self.visualize_dot(plan),
286 VisualizationFormat::Mermaid => self.visualize_mermaid(plan),
287 VisualizationFormat::AsciiTree => self.visualize_ascii_tree(plan),
288 VisualizationFormat::Json => self.visualize_json(plan),
289 VisualizationFormat::Html => self.visualize_html(plan),
290 }
291 }
292
293 fn visualize_dot(&self, plan: &QueryPlan) -> Result<String, VisualizationError> {
295 let mut output = String::new();
296 writeln!(output, "digraph QueryPlan {{")
297 .map_err(|e| VisualizationError::FormatError(e.to_string()))?;
298 writeln!(output, " rankdir=TB;")
299 .map_err(|e| VisualizationError::FormatError(e.to_string()))?;
300 writeln!(output, " node [shape=box, style=filled];")
301 .map_err(|e| VisualizationError::FormatError(e.to_string()))?;
302
303 for (id, node) in &plan.nodes {
305 let default_color = "#FFFFFF".to_string();
306 let color = self.colors.get(&node.node_type).unwrap_or(&default_color);
307
308 let mut label = node.name.clone();
309 if self.show_costs {
310 label.push_str(&format!("\\nCost: {:.2}", node.estimated_cost));
311 }
312 if self.show_execution_times {
313 if let Some(time) = node.execution_time_ms {
314 label.push_str(&format!("\\nTime: {}ms", time));
315 }
316 }
317
318 writeln!(
319 output,
320 " \"{}\" [label=\"{}\", fillcolor=\"{}\"];",
321 id, label, color
322 )
323 .map_err(|e| VisualizationError::FormatError(e.to_string()))?;
324 }
325
326 for node in plan.nodes.values() {
328 for child_id in &node.children {
329 writeln!(output, " \"{}\" -> \"{}\";", node.id, child_id)
330 .map_err(|e| VisualizationError::FormatError(e.to_string()))?;
331 }
332
333 for dep_id in &node.dependencies {
335 writeln!(
336 output,
337 " \"{}\" -> \"{}\" [style=dashed, color=red];",
338 dep_id, node.id
339 )
340 .map_err(|e| VisualizationError::FormatError(e.to_string()))?;
341 }
342 }
343
344 writeln!(output, "}}").map_err(|e| VisualizationError::FormatError(e.to_string()))?;
345 Ok(output)
346 }
347
348 fn visualize_mermaid(&self, plan: &QueryPlan) -> Result<String, VisualizationError> {
350 let mut output = String::new();
351 writeln!(output, "graph TD").map_err(|e| VisualizationError::FormatError(e.to_string()))?;
352
353 for (id, node) in &plan.nodes {
355 let mut label = node.name.clone();
356 if self.show_costs {
357 label.push_str(&format!("<br/>Cost: {:.2}", node.estimated_cost));
358 }
359 if self.show_execution_times {
360 if let Some(time) = node.execution_time_ms {
361 label.push_str(&format!("<br/>Time: {}ms", time));
362 }
363 }
364
365 writeln!(output, " {}[\"{}\"]", id.replace('-', "_"), label)
366 .map_err(|e| VisualizationError::FormatError(e.to_string()))?;
367 }
368
369 for node in plan.nodes.values() {
371 for child_id in &node.children {
372 writeln!(
373 output,
374 " {} --> {}",
375 node.id.replace('-', "_"),
376 child_id.replace('-', "_")
377 )
378 .map_err(|e| VisualizationError::FormatError(e.to_string()))?;
379 }
380
381 for dep_id in &node.dependencies {
383 writeln!(
384 output,
385 " {} -.-> {}",
386 dep_id.replace('-', "_"),
387 node.id.replace('-', "_")
388 )
389 .map_err(|e| VisualizationError::FormatError(e.to_string()))?;
390 }
391 }
392
393 Ok(output)
394 }
395
396 fn visualize_ascii_tree(&self, plan: &QueryPlan) -> Result<String, VisualizationError> {
398 let mut output = String::new();
399 writeln!(output, "Query Plan:")
400 .map_err(|e| VisualizationError::FormatError(e.to_string()))?;
401 writeln!(output, "Total Cost: {:.2}", plan.total_cost)
402 .map_err(|e| VisualizationError::FormatError(e.to_string()))?;
403
404 if let Some(time) = plan.total_execution_time_ms {
405 writeln!(output, "Total Time: {}ms", time)
406 .map_err(|e| VisualizationError::FormatError(e.to_string()))?;
407 }
408
409 writeln!(output).map_err(|e| VisualizationError::FormatError(e.to_string()))?;
410
411 if let Some(root) = plan.root_node() {
412 self.print_tree_node(plan, root, "", true, &mut output)?;
413 }
414
415 Ok(output)
416 }
417
418 fn print_tree_node(
419 &self,
420 plan: &QueryPlan,
421 node: &PlanNode,
422 prefix: &str,
423 is_last: bool,
424 output: &mut String,
425 ) -> Result<(), VisualizationError> {
426 let connector = if is_last { "└── " } else { "├── " };
427 let mut line = format!("{}{}{}", prefix, connector, node.name);
428
429 if self.show_costs {
430 line.push_str(&format!(" (cost: {:.2}", node.estimated_cost));
431 if self.show_execution_times {
432 if let Some(time) = node.execution_time_ms {
433 line.push_str(&format!(", time: {}ms", time));
434 }
435 }
436 line.push(')');
437 } else if self.show_execution_times {
438 if let Some(time) = node.execution_time_ms {
439 line.push_str(&format!(" (time: {}ms)", time));
440 }
441 }
442
443 writeln!(output, "{}", line).map_err(|e| VisualizationError::FormatError(e.to_string()))?;
444
445 let child_prefix = format!("{}{} ", prefix, if is_last { " " } else { "│" });
446
447 for (i, child_id) in node.children.iter().enumerate() {
448 if let Some(child) = plan.get_node(child_id) {
449 let is_last_child = i == node.children.len() - 1;
450 self.print_tree_node(plan, child, &child_prefix, is_last_child, output)?;
451 }
452 }
453
454 Ok(())
455 }
456
457 fn visualize_json(&self, plan: &QueryPlan) -> Result<String, VisualizationError> {
459 serde_json::to_string_pretty(plan)
460 .map_err(|e| VisualizationError::FormatError(e.to_string()))
461 }
462
463 fn visualize_html(&self, plan: &QueryPlan) -> Result<String, VisualizationError> {
465 let json = self.visualize_json(plan)?;
466
467 Ok(format!(
468 r#"<!DOCTYPE html>
469<html>
470<head>
471 <title>Query Plan Visualization</title>
472 <style>
473 body {{ font-family: Arial, sans-serif; margin: 20px; }}
474 .node {{ border: 1px solid #ccc; padding: 10px; margin: 5px; border-radius: 5px; }}
475 .node-Field {{ background-color: #87CEEB; }}
476 .node-Object {{ background-color: #90EE90; }}
477 .node-List {{ background-color: #FFB6C1; }}
478 .node-Defer {{ background-color: #FFD700; }}
479 .node-Stream {{ background-color: #FF6347; }}
480 .metadata {{ font-size: 0.9em; color: #666; }}
481 pre {{ background-color: #f4f4f4; padding: 10px; border-radius: 5px; }}
482 </style>
483</head>
484<body>
485 <h1>Query Plan Visualization</h1>
486 <div id="visualization"></div>
487 <h2>JSON Representation</h2>
488 <pre>{}</pre>
489 <script>
490 const plan = {};
491 // Interactive visualization would go here
492 console.log('Query plan:', plan);
493 </script>
494</body>
495</html>"#,
496 json, json
497 ))
498 }
499
500 pub fn generate_timeline(&self, plan: &QueryPlan) -> String {
502 let mut output = String::new();
503 writeln!(&mut output, "Execution Timeline:").expect("writing to String should not fail");
504 writeln!(&mut output, "==================").expect("writing to String should not fail");
505
506 let levels = plan.execution_levels();
507 let max_level = levels.values().max().copied().unwrap_or(0);
508
509 for level in 0..=max_level {
510 let nodes_at_level: Vec<_> = plan
511 .nodes
512 .iter()
513 .filter(|(id, _)| levels.get(*id).copied() == Some(level))
514 .collect();
515
516 if !nodes_at_level.is_empty() {
517 writeln!(&mut output, "\nLevel {}:", level)
518 .expect("writing to String should not fail");
519 for (_id, node) in nodes_at_level {
520 let time_str = node
521 .execution_time_ms
522 .map(|t| format!(" ({}ms)", t))
523 .unwrap_or_default();
524 let parallel = if node.parallel_execution {
525 " [parallel]"
526 } else {
527 ""
528 };
529 writeln!(
530 &mut output,
531 " - {} [{}]{}{}",
532 node.name, node.node_type, time_str, parallel
533 )
534 .expect("writing to String should not fail");
535 }
536 }
537 }
538
539 output
540 }
541
542 pub fn generate_cost_breakdown(&self, plan: &QueryPlan) -> String {
544 let mut output = String::new();
545 writeln!(&mut output, "Cost Breakdown:").expect("writing to String should not fail");
546 writeln!(&mut output, "===============").expect("writing to String should not fail");
547 writeln!(&mut output, "Total Cost: {:.2}\n", plan.total_cost)
548 .expect("writing to String should not fail");
549
550 let mut nodes: Vec<_> = plan.nodes.values().collect();
551 nodes.sort_by(|a, b| {
552 b.estimated_cost
553 .partial_cmp(&a.estimated_cost)
554 .unwrap_or(std::cmp::Ordering::Equal)
555 });
556
557 for node in nodes {
558 let percentage = (node.estimated_cost / plan.total_cost) * 100.0;
559 writeln!(
560 &mut output,
561 "{:<30} {:>8.2} ({:>5.1}%)",
562 node.name, node.estimated_cost, percentage
563 )
564 .expect("writing to String should not fail");
565 }
566
567 output
568 }
569}
570
571impl Default for QueryPlanVisualizer {
572 fn default() -> Self {
573 Self::new()
574 }
575}
576
577#[derive(Debug, thiserror::Error)]
579pub enum VisualizationError {
580 #[error("Formatting error: {0}")]
582 FormatError(String),
583
584 #[error("Invalid plan structure: {0}")]
586 InvalidPlan(String),
587
588 #[error("Serialization error: {0}")]
590 SerializationError(String),
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596
597 fn create_sample_plan() -> QueryPlan {
598 let mut plan = QueryPlan::new("root".to_string(), "{ user { posts } }".to_string());
599
600 let root = PlanNode::new(
601 "root".to_string(),
602 "Object".to_string(),
603 "Query".to_string(),
604 )
605 .with_cost(10.0)
606 .with_child("user".to_string());
607
608 let user = PlanNode::new("user".to_string(), "Field".to_string(), "user".to_string())
609 .with_parent("root".to_string())
610 .with_cost(5.0)
611 .with_child("posts".to_string())
612 .with_execution_time(15);
613
614 let posts = PlanNode::new("posts".to_string(), "List".to_string(), "posts".to_string())
615 .with_parent("user".to_string())
616 .with_cost(20.0)
617 .with_execution_time(45);
618
619 plan.add_node(root);
620 plan.add_node(user);
621 plan.add_node(posts);
622 plan.set_total_execution_time(60);
623
624 plan
625 }
626
627 #[test]
628 fn test_plan_node_creation() {
629 let node = PlanNode::new(
630 "test".to_string(),
631 "Field".to_string(),
632 "testField".to_string(),
633 )
634 .with_cost(5.0)
635 .with_execution_time(10)
636 .with_parallel(true)
637 .with_metadata("key".to_string(), serde_json::json!("value"));
638
639 assert_eq!(node.id, "test");
640 assert_eq!(node.node_type, "Field");
641 assert_eq!(node.name, "testField");
642 assert_eq!(node.estimated_cost, 5.0);
643 assert_eq!(node.execution_time_ms, Some(10));
644 assert!(node.parallel_execution);
645 assert_eq!(node.metadata.len(), 1);
646 }
647
648 #[test]
649 fn test_query_plan_creation() {
650 let plan = QueryPlan::new("root".to_string(), "{ test }".to_string());
651
652 assert_eq!(plan.root_id, "root");
653 assert_eq!(plan.query, "{ test }");
654 assert_eq!(plan.total_cost, 0.0);
655 assert!(plan.nodes.is_empty());
656 }
657
658 #[test]
659 fn test_add_node_to_plan() {
660 let mut plan = QueryPlan::new("root".to_string(), "{ test }".to_string());
661 let node = PlanNode::new("test".to_string(), "Field".to_string(), "test".to_string())
662 .with_cost(5.0);
663
664 plan.add_node(node);
665
666 assert_eq!(plan.nodes.len(), 1);
667 assert_eq!(plan.total_cost, 5.0);
668 }
669
670 #[test]
671 fn test_root_node() {
672 let plan = create_sample_plan();
673 let root = plan.root_node();
674
675 assert!(root.is_some());
676 assert_eq!(root.expect("should succeed").id, "root");
677 }
678
679 #[test]
680 fn test_leaf_nodes() {
681 let plan = create_sample_plan();
682 let leaves = plan.leaf_nodes();
683
684 assert_eq!(leaves.len(), 1);
685 assert_eq!(leaves[0].id, "posts");
686 }
687
688 #[test]
689 fn test_execution_levels() {
690 let plan = create_sample_plan();
691 let levels = plan.execution_levels();
692
693 assert_eq!(levels.get("root"), Some(&0));
694 assert_eq!(levels.get("user"), Some(&1));
695 assert_eq!(levels.get("posts"), Some(&2));
696 }
697
698 #[test]
699 fn test_parallel_execution_groups() {
700 let mut plan = QueryPlan::new("root".to_string(), "{ test }".to_string());
701
702 let root = PlanNode::new(
703 "root".to_string(),
704 "Object".to_string(),
705 "Query".to_string(),
706 )
707 .with_child("field1".to_string())
708 .with_child("field2".to_string());
709
710 let field1 = PlanNode::new(
711 "field1".to_string(),
712 "Field".to_string(),
713 "field1".to_string(),
714 )
715 .with_parent("root".to_string())
716 .with_parallel(true);
717
718 let field2 = PlanNode::new(
719 "field2".to_string(),
720 "Field".to_string(),
721 "field2".to_string(),
722 )
723 .with_parent("root".to_string())
724 .with_parallel(true);
725
726 plan.add_node(root);
727 plan.add_node(field1);
728 plan.add_node(field2);
729
730 let groups = plan.parallel_execution_groups();
731 assert!(groups[1].len() == 2); }
733
734 #[test]
735 fn test_visualizer_creation() {
736 let visualizer = QueryPlanVisualizer::new();
737
738 assert!(visualizer.show_costs);
739 assert!(visualizer.show_execution_times);
740 assert!(!visualizer.show_metadata);
741 assert!(!visualizer.colors.is_empty());
742 }
743
744 #[test]
745 fn test_visualizer_configuration() {
746 let visualizer = QueryPlanVisualizer::new()
747 .with_costs(false)
748 .with_execution_times(false)
749 .with_metadata(true);
750
751 assert!(!visualizer.show_costs);
752 assert!(!visualizer.show_execution_times);
753 assert!(visualizer.show_metadata);
754 }
755
756 #[test]
757 fn test_dot_visualization() {
758 let plan = create_sample_plan();
759 let visualizer = QueryPlanVisualizer::new();
760
761 let dot = visualizer.visualize(&plan, VisualizationFormat::Dot);
762 assert!(dot.is_ok());
763
764 let output = dot.expect("should succeed");
765 assert!(output.contains("digraph QueryPlan"));
766 assert!(output.contains("root"));
767 assert!(output.contains("user"));
768 assert!(output.contains("posts"));
769 }
770
771 #[test]
772 fn test_mermaid_visualization() {
773 let plan = create_sample_plan();
774 let visualizer = QueryPlanVisualizer::new();
775
776 let mermaid = visualizer.visualize(&plan, VisualizationFormat::Mermaid);
777 assert!(mermaid.is_ok());
778
779 let output = mermaid.expect("should succeed");
780 assert!(output.contains("graph TD"));
781 assert!(output.contains("root"));
782 assert!(output.contains("user"));
783 }
784
785 #[test]
786 fn test_ascii_tree_visualization() {
787 let plan = create_sample_plan();
788 let visualizer = QueryPlanVisualizer::new();
789
790 let tree = visualizer.visualize(&plan, VisualizationFormat::AsciiTree);
791 assert!(tree.is_ok());
792
793 let output = tree.expect("should succeed");
794 assert!(output.contains("Query Plan:"));
795 assert!(output.contains("Total Cost:"));
796 assert!(output.contains("Query"));
797 assert!(output.contains("user"));
798 assert!(output.contains("posts"));
799 }
800
801 #[test]
802 fn test_json_visualization() {
803 let plan = create_sample_plan();
804 let visualizer = QueryPlanVisualizer::new();
805
806 let json = visualizer.visualize(&plan, VisualizationFormat::Json);
807 assert!(json.is_ok());
808
809 let output = json.expect("should succeed");
810 assert!(output.contains("root_id"));
811 assert!(output.contains("nodes"));
812 assert!(output.contains("total_cost"));
813 }
814
815 #[test]
816 fn test_html_visualization() {
817 let plan = create_sample_plan();
818 let visualizer = QueryPlanVisualizer::new();
819
820 let html = visualizer.visualize(&plan, VisualizationFormat::Html);
821 assert!(html.is_ok());
822
823 let output = html.expect("should succeed");
824 assert!(output.contains("<!DOCTYPE html>"));
825 assert!(output.contains("Query Plan Visualization"));
826 assert!(output.contains("<script>"));
827 }
828
829 #[test]
830 fn test_timeline_generation() {
831 let plan = create_sample_plan();
832 let visualizer = QueryPlanVisualizer::new();
833
834 let timeline = visualizer.generate_timeline(&plan);
835 assert!(timeline.contains("Execution Timeline"));
836 assert!(timeline.contains("Level 0"));
837 assert!(timeline.contains("Level 1"));
838 assert!(timeline.contains("Level 2"));
839 }
840
841 #[test]
842 fn test_cost_breakdown() {
843 let plan = create_sample_plan();
844 let visualizer = QueryPlanVisualizer::new();
845
846 let breakdown = visualizer.generate_cost_breakdown(&plan);
847 assert!(breakdown.contains("Cost Breakdown"));
848 assert!(breakdown.contains("Total Cost:"));
849 assert!(breakdown.contains("posts")); }
851
852 #[test]
853 fn test_node_with_dependencies() {
854 let node = PlanNode::new("test".to_string(), "Field".to_string(), "test".to_string())
855 .with_dependency("dep1".to_string())
856 .with_dependency("dep2".to_string());
857
858 assert_eq!(node.dependencies.len(), 2);
859 }
860
861 #[test]
862 fn test_plan_with_dependencies() {
863 let mut plan = QueryPlan::new("root".to_string(), "{ test }".to_string());
864
865 let node1 = PlanNode::new(
866 "node1".to_string(),
867 "Field".to_string(),
868 "field1".to_string(),
869 );
870 let node2 = PlanNode::new(
871 "node2".to_string(),
872 "Field".to_string(),
873 "field2".to_string(),
874 )
875 .with_dependency("node1".to_string());
876
877 plan.add_node(node1);
878 plan.add_node(node2);
879
880 let node2_ref = plan.get_node("node2").expect("should succeed");
881 assert_eq!(node2_ref.dependencies.len(), 1);
882 assert_eq!(node2_ref.dependencies[0], "node1");
883 }
884
885 #[test]
886 fn test_dot_with_dependencies() {
887 let mut plan = QueryPlan::new("root".to_string(), "{ test }".to_string());
888
889 let node1 = PlanNode::new(
890 "node1".to_string(),
891 "Field".to_string(),
892 "field1".to_string(),
893 )
894 .with_cost(1.0);
895 let node2 = PlanNode::new(
896 "node2".to_string(),
897 "Field".to_string(),
898 "field2".to_string(),
899 )
900 .with_dependency("node1".to_string())
901 .with_cost(2.0);
902
903 plan.add_node(node1);
904 plan.add_node(node2);
905
906 let visualizer = QueryPlanVisualizer::new();
907 let dot = visualizer
908 .visualize(&plan, VisualizationFormat::Dot)
909 .expect("should succeed");
910
911 assert!(dot.contains("style=dashed"));
912 assert!(dot.contains("color=red"));
913 }
914
915 #[test]
916 fn test_complex_plan() {
917 let mut plan = QueryPlan::new("root".to_string(), "complex query".to_string());
918
919 let root = PlanNode::new(
921 "root".to_string(),
922 "Object".to_string(),
923 "Query".to_string(),
924 )
925 .with_child("user".to_string())
926 .with_child("posts".to_string());
927
928 let user = PlanNode::new("user".to_string(), "Object".to_string(), "User".to_string())
929 .with_cost(5.0)
930 .with_child("name".to_string())
931 .with_child("email".to_string());
932
933 let name = PlanNode::new("name".to_string(), "Field".to_string(), "name".to_string())
934 .with_cost(1.0);
935
936 let email = PlanNode::new(
937 "email".to_string(),
938 "Field".to_string(),
939 "email".to_string(),
940 )
941 .with_cost(1.0);
942
943 let posts = PlanNode::new("posts".to_string(), "List".to_string(), "posts".to_string())
944 .with_cost(10.0)
945 .with_child("title".to_string());
946
947 let title = PlanNode::new(
948 "title".to_string(),
949 "Field".to_string(),
950 "title".to_string(),
951 )
952 .with_cost(2.0);
953
954 plan.add_node(root);
955 plan.add_node(user);
956 plan.add_node(name);
957 plan.add_node(email);
958 plan.add_node(posts);
959 plan.add_node(title);
960
961 assert_eq!(plan.nodes.len(), 6);
962 assert!(plan.total_cost > 0.0);
963
964 let levels = plan.execution_levels();
965 assert_eq!(levels.len(), 6);
966 }
967
968 #[test]
969 fn test_visualizer_without_costs() {
970 let plan = create_sample_plan();
971 let visualizer = QueryPlanVisualizer::new().with_costs(false);
972
973 let tree = visualizer
974 .visualize(&plan, VisualizationFormat::AsciiTree)
975 .expect("should succeed");
976 assert!(!tree.contains("cost:"));
977 }
978
979 #[test]
980 fn test_visualizer_without_execution_times() {
981 let plan = create_sample_plan();
982 let visualizer = QueryPlanVisualizer::new().with_execution_times(false);
983
984 let tree = visualizer
985 .visualize(&plan, VisualizationFormat::AsciiTree)
986 .expect("should succeed");
987 assert!(!tree.contains("time:"));
988 }
989}