icydb_core/db/query/explain/
render.rs1use crate::{
7 db::query::explain::{
8 ExplainExecutionNodeDescriptor,
9 access_projection::access_strategy_label,
10 execution::{execution_mode_label, ordering_source_label},
11 nodes::{
12 execution_mode_detail_label, fast_path_reason, fast_path_selected,
13 predicate_pushdown_mode,
14 },
15 },
16 value::Value,
17};
18use std::{collections::BTreeMap, fmt::Write};
19
20impl ExplainExecutionNodeDescriptor {
21 #[must_use]
23 pub fn render_text_tree(&self) -> String {
24 let mut lines = Vec::new();
25 let mut node_id_counter = 0_u64;
26 self.render_text_tree_into(0, &mut node_id_counter, &mut lines);
27 lines.join("\n")
28 }
29
30 #[must_use]
32 pub fn render_text_tree_verbose(&self) -> String {
33 let mut lines = Vec::new();
34 let mut node_id_counter = 0_u64;
35 self.render_text_tree_verbose_into(0, &mut node_id_counter, &mut lines);
36 lines.join("\n")
37 }
38
39 fn render_text_tree_into(
40 &self,
41 depth: usize,
42 node_id_counter: &mut u64,
43 lines: &mut Vec<String>,
44 ) {
45 let node_id = next_node_id(node_id_counter);
46 let mut line = format!(
47 "{}{} execution_mode={}",
48 " ".repeat(depth),
49 self.node_type.as_str(),
50 execution_mode_label(self.execution_mode)
51 );
52 let _ = write!(line, " node_id={node_id}");
53 let _ = write!(line, " layer={}", self.node_type.layer_label());
54 let _ = write!(
55 line,
56 " execution_mode_detail={}",
57 execution_mode_detail_label(self.execution_mode)
58 );
59 let _ = write!(
60 line,
61 " predicate_pushdown_mode={}",
62 predicate_pushdown_mode(self)
63 );
64 if let Some(fast_path_selected) = fast_path_selected(self) {
65 let _ = write!(line, " fast_path_selected={fast_path_selected}");
66 }
67 if let Some(fast_path_reason) = fast_path_reason(self) {
68 let _ = write!(line, " fast_path_reason={fast_path_reason}");
69 }
70
71 if let Some(access_strategy) = self.access_strategy.as_ref() {
72 let _ = write!(line, " access={}", access_strategy_label(access_strategy));
73 }
74 if let Some(predicate_pushdown) = self.predicate_pushdown.as_ref() {
75 let _ = write!(line, " predicate_pushdown={predicate_pushdown}");
76 }
77 if let Some(residual_predicate) = self.residual_predicate.as_ref() {
78 let _ = write!(line, " residual_predicate={residual_predicate:?}");
79 }
80 if let Some(projection) = self.projection.as_ref() {
81 let _ = write!(line, " projection={projection}");
82 }
83 if let Some(ordering_source) = self.ordering_source {
84 let _ = write!(
85 line,
86 " ordering_source={}",
87 ordering_source_label(ordering_source)
88 );
89 }
90 if let Some(limit) = self.limit {
91 let _ = write!(line, " limit={limit}");
92 }
93 if let Some(cursor) = self.cursor {
94 let _ = write!(line, " cursor={cursor}");
95 }
96 if let Some(covering_scan) = self.covering_scan {
97 let _ = write!(line, " covering_scan={covering_scan}");
98 }
99 if let Some(rows_expected) = self.rows_expected {
100 let _ = write!(line, " rows_expected={rows_expected}");
101 }
102 if !self.node_properties.is_empty() {
103 let _ = write!(
104 line,
105 " node_properties={}",
106 render_node_properties(&self.node_properties)
107 );
108 }
109
110 lines.push(line);
111
112 for child in &self.children {
113 child.render_text_tree_into(depth.saturating_add(1), node_id_counter, lines);
114 }
115 }
116
117 fn render_text_tree_verbose_into(
118 &self,
119 depth: usize,
120 node_id_counter: &mut u64,
121 lines: &mut Vec<String>,
122 ) {
123 let node_id = next_node_id(node_id_counter);
124 let node_indent = " ".repeat(depth);
126 let field_indent = " ".repeat(depth.saturating_add(1));
127 lines.push(format!(
128 "{}{} execution_mode={}",
129 node_indent,
130 self.node_type.as_str(),
131 execution_mode_label(self.execution_mode)
132 ));
133 lines.push(format!("{field_indent}node_id={node_id}"));
134 lines.push(format!(
135 "{}layer={}",
136 field_indent,
137 self.node_type.layer_label()
138 ));
139 lines.push(format!(
140 "{}execution_mode_detail={}",
141 field_indent,
142 execution_mode_detail_label(self.execution_mode)
143 ));
144 lines.push(format!(
145 "{}predicate_pushdown_mode={}",
146 field_indent,
147 predicate_pushdown_mode(self)
148 ));
149 if let Some(fast_path_selected) = fast_path_selected(self) {
150 lines.push(format!(
151 "{field_indent}fast_path_selected={fast_path_selected}"
152 ));
153 }
154 if let Some(fast_path_reason) = fast_path_reason(self) {
155 lines.push(format!("{field_indent}fast_path_reason={fast_path_reason}"));
156 }
157
158 if let Some(access_strategy) = self.access_strategy.as_ref() {
160 lines.push(format!("{field_indent}access_strategy={access_strategy:?}"));
161 }
162 if let Some(predicate_pushdown) = self.predicate_pushdown.as_ref() {
163 lines.push(format!(
164 "{field_indent}predicate_pushdown={predicate_pushdown}"
165 ));
166 }
167 if let Some(residual_predicate) = self.residual_predicate.as_ref() {
168 lines.push(format!(
169 "{field_indent}residual_predicate={residual_predicate:?}"
170 ));
171 }
172 if let Some(projection) = self.projection.as_ref() {
173 lines.push(format!("{field_indent}projection={projection}"));
174 }
175 if let Some(ordering_source) = self.ordering_source {
176 lines.push(format!(
177 "{}ordering_source={}",
178 field_indent,
179 ordering_source_label(ordering_source)
180 ));
181 }
182 if let Some(limit) = self.limit {
183 lines.push(format!("{field_indent}limit={limit}"));
184 }
185 if let Some(cursor) = self.cursor {
186 lines.push(format!("{field_indent}cursor={cursor}"));
187 }
188 if let Some(covering_scan) = self.covering_scan {
189 lines.push(format!("{field_indent}covering_scan={covering_scan}"));
190 }
191 if let Some(rows_expected) = self.rows_expected {
192 lines.push(format!("{field_indent}rows_expected={rows_expected}"));
193 }
194 if !self.node_properties.is_empty() {
195 lines.push(format!(
196 "{}node_properties={}",
197 field_indent,
198 render_node_properties(&self.node_properties)
199 ));
200 }
201
202 for child in &self.children {
204 child.render_text_tree_verbose_into(depth.saturating_add(1), node_id_counter, lines);
205 }
206 }
207}
208
209fn render_node_properties(node_properties: &BTreeMap<String, Value>) -> String {
210 let mut rendered = String::new();
211 let mut first = true;
212 for (key, value) in node_properties {
213 if first {
214 first = false;
215 } else {
216 rendered.push(',');
217 }
218 let _ = write!(rendered, "{key}={value:?}");
219 }
220 rendered
221}
222
223const fn next_node_id(node_id_counter: &mut u64) -> u64 {
224 let node_id = *node_id_counter;
225 *node_id_counter = node_id_counter.saturating_add(1);
226 node_id
227}