velesdb_core/velesql/explain/
formatter.rs1use std::fmt::{self, Write as _};
7
8use super::{FilterStrategy, IndexType, PlanNode, QueryPlan};
9
10impl QueryPlan {
11 #[must_use]
13 pub fn to_tree(&self) -> String {
14 let mut output = String::from("Query Plan:\n");
15 Self::render_node(&self.root, &mut output, "", true);
16
17 let _ = write!(
18 output,
19 "\nEstimated cost: {:.3}ms\n",
20 self.estimated_cost_ms
21 );
22
23 if let Some(ref idx) = self.index_used {
24 let _ = writeln!(output, "Index used: {}", idx.as_str());
25 }
26
27 if self.filter_strategy != FilterStrategy::None {
28 let _ = writeln!(output, "Filter strategy: {}", self.filter_strategy.as_str());
29 }
30
31 if let Some(hit) = self.cache_hit {
32 let _ = writeln!(output, "Cache hit: {hit}");
33 }
34 if let Some(count) = self.plan_reuse_count {
35 let _ = writeln!(output, "Plan reuse count: {count}");
36 }
37
38 output
39 }
40
41 pub(crate) fn render_node(node: &PlanNode, output: &mut String, prefix: &str, is_last: bool) {
42 let connector = if is_last { "└─ " } else { "├─ " };
43 let child_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " });
44
45 match node {
46 PlanNode::VectorSearch(vs) => {
47 let _ = writeln!(output, "{prefix}{connector}VectorSearch");
48 let _ = writeln!(output, "{child_prefix}├─ Collection: {}", vs.collection);
49 let _ = writeln!(output, "{child_prefix}├─ ef_search: {}", vs.ef_search);
50 let _ = writeln!(output, "{child_prefix}└─ Candidates: {}", vs.candidates);
51 }
52 PlanNode::Filter(f) => {
53 let _ = writeln!(output, "{prefix}{connector}Filter");
54 let _ = writeln!(output, "{child_prefix}├─ Conditions: {}", f.conditions);
55 let _ = writeln!(
56 output,
57 "{child_prefix}└─ Selectivity: {:.1}%",
58 f.selectivity * 100.0
59 );
60 }
61 PlanNode::Limit(l) => {
62 let _ = writeln!(output, "{prefix}{connector}Limit: {}", l.count);
63 }
64 PlanNode::Offset(o) => {
65 let _ = writeln!(output, "{prefix}{connector}Offset: {}", o.count);
66 }
67 PlanNode::TableScan(ts) => {
68 let _ = writeln!(output, "{prefix}{connector}TableScan: {}", ts.collection);
69 }
70 PlanNode::IndexLookup(il) => {
71 let _ = writeln!(
72 output,
73 "{prefix}{connector}IndexLookup({}.{})",
74 il.label, il.property
75 );
76 let _ = writeln!(output, "{child_prefix}└─ Value: {}", il.value);
77 }
78 PlanNode::Sequence(nodes) => {
79 for (i, child) in nodes.iter().enumerate() {
80 Self::render_node(child, output, prefix, i == nodes.len() - 1);
81 }
82 }
83 PlanNode::MatchTraversal(mt) => {
84 let _ = writeln!(output, "{prefix}{connector}MatchTraversal");
85 let _ = writeln!(output, "{child_prefix}├─ Strategy: {}", mt.strategy);
86 if !mt.start_labels.is_empty() {
87 let _ = writeln!(
88 output,
89 "{child_prefix}├─ Start Labels: [{}]",
90 mt.start_labels.join(", ")
91 );
92 }
93 let _ = writeln!(output, "{child_prefix}├─ Max Depth: {}", mt.max_depth);
94 let _ = writeln!(
95 output,
96 "{child_prefix}├─ Relationships: {}",
97 mt.relationship_count
98 );
99 if let Some(threshold) = mt.similarity_threshold {
100 let _ = writeln!(
101 output,
102 "{child_prefix}└─ Similarity Threshold: {:.2}",
103 threshold
104 );
105 } else {
106 let _ = writeln!(
107 output,
108 "{child_prefix}└─ Similarity: {}",
109 if mt.has_similarity { "yes" } else { "no" }
110 );
111 }
112 }
113 }
114 }
115
116 pub fn to_json(&self) -> Result<String, serde_json::Error> {
122 serde_json::to_string_pretty(self)
123 }
124}
125
126impl IndexType {
127 #[must_use]
129 pub const fn as_str(&self) -> &'static str {
130 match self {
131 Self::Hnsw => "HNSW",
132 Self::Flat => "Flat",
133 Self::BinaryQuantization => "BinaryQuantization",
134 Self::Property => "PropertyIndex",
135 }
136 }
137}
138
139impl FilterStrategy {
140 #[must_use]
142 pub const fn as_str(&self) -> &'static str {
143 match self {
144 Self::None => "none",
145 Self::PreFilter => "pre-filtering (high selectivity)",
146 Self::PostFilter => "post-filtering (low selectivity)",
147 }
148 }
149}
150
151impl super::super::ast::CompareOp {
152 #[must_use]
154 pub const fn as_str(&self) -> &'static str {
155 match self {
156 Self::Eq => "=",
157 Self::NotEq => "!=",
158 Self::Gt => ">",
159 Self::Gte => ">=",
160 Self::Lt => "<",
161 Self::Lte => "<=",
162 }
163 }
164}
165
166impl fmt::Display for QueryPlan {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 write!(f, "{}", self.to_tree())
169 }
170}