icydb_core/db/query/explain/
render.rs1use crate::db::query::explain::{
7 ExplainExecutionNodeDescriptor, ExplainPropertyMap,
8 access_projection::write_access_strategy_label,
9 execution::{execution_mode_label, ordering_source_label},
10 nodes::{
11 execution_mode_detail_label, fast_path_reason, fast_path_selected, predicate_pushdown_mode,
12 },
13};
14use std::fmt::Write;
15
16impl ExplainExecutionNodeDescriptor {
17 #[must_use]
19 pub fn render_text_tree(&self) -> String {
20 let mut out = String::new();
21 let mut node_id_counter = 0_u64;
22 self.render_text_tree_into(0, &mut node_id_counter, &mut out);
23 out
24 }
25
26 #[must_use]
28 pub fn render_text_tree_verbose(&self) -> String {
29 self.render_text_tree_verbose_with_indent("")
30 }
31
32 #[must_use]
35 pub fn render_text_tree_verbose_with_indent(&self, indent: &str) -> String {
36 let mut out = String::new();
37 let mut node_id_counter = 0_u64;
38 self.render_text_tree_verbose_into(indent, 0, &mut node_id_counter, &mut out);
39 out
40 }
41
42 fn render_text_tree_into(&self, depth: usize, node_id_counter: &mut u64, out: &mut String) {
43 let node_id = *node_id_counter;
44 *node_id_counter = node_id_counter.saturating_add(1);
45 push_rendered_line_prefix(out, depth);
46 let _ = write!(
47 out,
48 "{} execution_mode={}",
49 self.node_type.as_str(),
50 execution_mode_label(self.execution_mode)
51 );
52 let _ = write!(out, " node_id={node_id}");
53 let _ = write!(out, " layer={}", self.node_type.layer_label());
54 let _ = write!(
55 out,
56 " execution_mode_detail={}",
57 execution_mode_detail_label(self.execution_mode)
58 );
59 let _ = write!(
60 out,
61 " predicate_pushdown_mode={}",
62 predicate_pushdown_mode(self)
63 );
64 if let Some(fast_path_selected) = fast_path_selected(self) {
65 let _ = write!(out, " fast_path_selected={fast_path_selected}");
66 }
67 if let Some(fast_path_reason) = fast_path_reason(self) {
68 let _ = write!(out, " fast_path_reason={fast_path_reason}");
69 }
70
71 if let Some(access_strategy) = self.access_strategy.as_ref() {
72 out.push_str(" access=");
73 write_access_strategy_label(out, access_strategy);
74 }
75 if let Some(predicate_pushdown) = self.predicate_pushdown.as_ref() {
76 let _ = write!(out, " predicate_pushdown={predicate_pushdown}");
77 }
78 if let Some(filter_expr) = self.filter_expr.as_ref() {
79 let _ = write!(out, " filter_expr={filter_expr}");
80 }
81 if let Some(residual_filter_expr) = self.residual_filter_expr.as_ref() {
82 let _ = write!(out, " residual_filter_expr={residual_filter_expr}");
83 }
84 if let Some(residual_filter_predicate) = self.residual_filter_predicate.as_ref() {
85 let _ = write!(
86 out,
87 " residual_filter_predicate={residual_filter_predicate:?}"
88 );
89 }
90 if let Some(projection) = self.projection.as_ref() {
91 let _ = write!(out, " projection={projection}");
92 }
93 if let Some(ordering_source) = self.ordering_source {
94 let _ = write!(
95 out,
96 " ordering_source={}",
97 ordering_source_label(ordering_source)
98 );
99 }
100 if let Some(limit) = self.limit {
101 let _ = write!(out, " limit={limit}");
102 }
103 if let Some(cursor) = self.cursor {
104 let _ = write!(out, " cursor={cursor}");
105 }
106 if let Some(covering_scan) = self.covering_scan {
107 let _ = write!(out, " covering_scan={covering_scan}");
108 }
109 if let Some(rows_expected) = self.rows_expected {
110 let _ = write!(out, " rows_expected={rows_expected}");
111 }
112 if !self.node_properties.is_empty() {
113 out.push_str(" node_properties=");
114 write_node_properties(out, &self.node_properties);
115 }
116
117 for child in &self.children {
118 child.render_text_tree_into(depth.saturating_add(1), node_id_counter, out);
119 }
120 }
121
122 fn render_text_tree_verbose_into(
123 &self,
124 base_indent: &str,
125 depth: usize,
126 node_id_counter: &mut u64,
127 out: &mut String,
128 ) {
129 let node_id = *node_id_counter;
130 *node_id_counter = node_id_counter.saturating_add(1);
131
132 push_rendered_line_prefix_with_base_depth(out, base_indent, depth);
135 let _ = write!(
136 out,
137 "{} execution_mode={}",
138 self.node_type.as_str(),
139 execution_mode_label(self.execution_mode)
140 );
141 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
142 let _ = write!(out, "node_id={node_id}");
143 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
144 let _ = write!(out, "layer={}", self.node_type.layer_label());
145 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
146 let _ = write!(
147 out,
148 "execution_mode_detail={}",
149 execution_mode_detail_label(self.execution_mode)
150 );
151 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
152 let _ = write!(
153 out,
154 "predicate_pushdown_mode={}",
155 predicate_pushdown_mode(self)
156 );
157 if let Some(fast_path_selected) = fast_path_selected(self) {
158 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
159 let _ = write!(out, "fast_path_selected={fast_path_selected}");
160 }
161 if let Some(fast_path_reason) = fast_path_reason(self) {
162 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
163 let _ = write!(out, "fast_path_reason={fast_path_reason}");
164 }
165
166 self.render_text_tree_verbose_node_fields(base_indent, depth.saturating_add(1), out);
168
169 for child in &self.children {
171 child.render_text_tree_verbose_into(
172 base_indent,
173 depth.saturating_add(1),
174 node_id_counter,
175 out,
176 );
177 }
178 }
179
180 fn render_text_tree_verbose_node_fields(
181 &self,
182 base_indent: &str,
183 field_depth: usize,
184 out: &mut String,
185 ) {
186 if let Some(access_strategy) = self.access_strategy.as_ref() {
187 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
188 out.push_str("access_strategy=");
189 write_access_strategy_label(out, access_strategy);
190 }
191 if let Some(predicate_pushdown) = self.predicate_pushdown.as_ref() {
192 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
193 let _ = write!(out, "predicate_pushdown={predicate_pushdown}");
194 }
195 if let Some(filter_expr) = self.filter_expr.as_ref() {
196 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
197 let _ = write!(out, "filter_expr={filter_expr}");
198 }
199 if let Some(residual_filter_expr) = self.residual_filter_expr.as_ref() {
200 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
201 let _ = write!(out, "residual_filter_expr={residual_filter_expr}");
202 }
203 if let Some(residual_filter_predicate) = self.residual_filter_predicate.as_ref() {
204 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
205 let _ = write!(
206 out,
207 "residual_filter_predicate={residual_filter_predicate:?}"
208 );
209 }
210 if let Some(projection) = self.projection.as_ref() {
211 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
212 let _ = write!(out, "projection={projection}");
213 }
214 if let Some(ordering_source) = self.ordering_source {
215 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
216 let _ = write!(
217 out,
218 "ordering_source={}",
219 ordering_source_label(ordering_source)
220 );
221 }
222 if let Some(limit) = self.limit {
223 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
224 let _ = write!(out, "limit={limit}");
225 }
226 if let Some(cursor) = self.cursor {
227 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
228 let _ = write!(out, "cursor={cursor}");
229 }
230 if let Some(covering_scan) = self.covering_scan {
231 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
232 let _ = write!(out, "covering_scan={covering_scan}");
233 }
234 if let Some(rows_expected) = self.rows_expected {
235 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
236 let _ = write!(out, "rows_expected={rows_expected}");
237 }
238 if !self.node_properties.is_empty() {
239 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
240 out.push_str("node_properties:");
241
242 for (key, value) in self.node_properties.iter() {
245 push_rendered_line_prefix_with_base_depth(
246 out,
247 base_indent,
248 field_depth.saturating_add(1),
249 );
250 let _ = write!(out, "{key}={value:?}");
251 }
252 }
253 }
254}
255
256fn push_rendered_line_prefix(out: &mut String, depth: usize) {
257 if !out.is_empty() {
258 out.push('\n');
259 }
260
261 for _ in 0..depth {
262 out.push_str(" ");
263 }
264}
265
266fn push_rendered_line_prefix_with_base_depth(out: &mut String, base_indent: &str, depth: usize) {
267 if !out.is_empty() {
268 out.push('\n');
269 }
270 out.push_str(base_indent);
271
272 for _ in 0..depth {
273 out.push_str(" ");
274 }
275}
276
277fn write_node_properties(out: &mut String, node_properties: &ExplainPropertyMap) {
278 let mut first = true;
279 for (key, value) in node_properties.iter() {
280 if first {
281 first = false;
282 } else {
283 out.push(',');
284 }
285 let _ = write!(out, "{key}={value:?}");
286 }
287}