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_predicate) = self.residual_filter_predicate.as_ref() {
82 let _ = write!(
83 out,
84 " residual_filter_predicate={residual_filter_predicate:?}"
85 );
86 }
87 if let Some(projection) = self.projection.as_ref() {
88 let _ = write!(out, " projection={projection}");
89 }
90 if let Some(ordering_source) = self.ordering_source {
91 let _ = write!(
92 out,
93 " ordering_source={}",
94 ordering_source_label(ordering_source)
95 );
96 }
97 if let Some(limit) = self.limit {
98 let _ = write!(out, " limit={limit}");
99 }
100 if let Some(cursor) = self.cursor {
101 let _ = write!(out, " cursor={cursor}");
102 }
103 if let Some(covering_scan) = self.covering_scan {
104 let _ = write!(out, " covering_scan={covering_scan}");
105 }
106 if let Some(rows_expected) = self.rows_expected {
107 let _ = write!(out, " rows_expected={rows_expected}");
108 }
109 if !self.node_properties.is_empty() {
110 out.push_str(" node_properties=");
111 write_node_properties(out, &self.node_properties);
112 }
113
114 for child in &self.children {
115 child.render_text_tree_into(depth.saturating_add(1), node_id_counter, out);
116 }
117 }
118
119 fn render_text_tree_verbose_into(
120 &self,
121 base_indent: &str,
122 depth: usize,
123 node_id_counter: &mut u64,
124 out: &mut String,
125 ) {
126 let node_id = *node_id_counter;
127 *node_id_counter = node_id_counter.saturating_add(1);
128
129 push_rendered_line_prefix_with_base_depth(out, base_indent, depth);
132 let _ = write!(
133 out,
134 "{} execution_mode={}",
135 self.node_type.as_str(),
136 execution_mode_label(self.execution_mode)
137 );
138 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
139 let _ = write!(out, "node_id={node_id}");
140 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
141 let _ = write!(out, "layer={}", self.node_type.layer_label());
142 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
143 let _ = write!(
144 out,
145 "execution_mode_detail={}",
146 execution_mode_detail_label(self.execution_mode)
147 );
148 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
149 let _ = write!(
150 out,
151 "predicate_pushdown_mode={}",
152 predicate_pushdown_mode(self)
153 );
154 if let Some(fast_path_selected) = fast_path_selected(self) {
155 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
156 let _ = write!(out, "fast_path_selected={fast_path_selected}");
157 }
158 if let Some(fast_path_reason) = fast_path_reason(self) {
159 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
160 let _ = write!(out, "fast_path_reason={fast_path_reason}");
161 }
162
163 self.render_text_tree_verbose_node_fields(base_indent, depth.saturating_add(1), out);
165
166 for child in &self.children {
168 child.render_text_tree_verbose_into(
169 base_indent,
170 depth.saturating_add(1),
171 node_id_counter,
172 out,
173 );
174 }
175 }
176
177 fn render_text_tree_verbose_node_fields(
178 &self,
179 base_indent: &str,
180 field_depth: usize,
181 out: &mut String,
182 ) {
183 if let Some(access_strategy) = self.access_strategy.as_ref() {
184 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
185 out.push_str("access_strategy=");
186 write_access_strategy_label(out, access_strategy);
187 }
188 if let Some(predicate_pushdown) = self.predicate_pushdown.as_ref() {
189 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
190 let _ = write!(out, "predicate_pushdown={predicate_pushdown}");
191 }
192 if let Some(filter_expr) = self.filter_expr.as_ref() {
193 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
194 let _ = write!(out, "filter_expr={filter_expr}");
195 }
196 if let Some(residual_filter_predicate) = self.residual_filter_predicate.as_ref() {
197 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
198 let _ = write!(
199 out,
200 "residual_filter_predicate={residual_filter_predicate:?}"
201 );
202 }
203 if let Some(projection) = self.projection.as_ref() {
204 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
205 let _ = write!(out, "projection={projection}");
206 }
207 if let Some(ordering_source) = self.ordering_source {
208 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
209 let _ = write!(
210 out,
211 "ordering_source={}",
212 ordering_source_label(ordering_source)
213 );
214 }
215 if let Some(limit) = self.limit {
216 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
217 let _ = write!(out, "limit={limit}");
218 }
219 if let Some(cursor) = self.cursor {
220 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
221 let _ = write!(out, "cursor={cursor}");
222 }
223 if let Some(covering_scan) = self.covering_scan {
224 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
225 let _ = write!(out, "covering_scan={covering_scan}");
226 }
227 if let Some(rows_expected) = self.rows_expected {
228 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
229 let _ = write!(out, "rows_expected={rows_expected}");
230 }
231 if !self.node_properties.is_empty() {
232 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
233 out.push_str("node_properties:");
234
235 for (key, value) in self.node_properties.iter() {
238 push_rendered_line_prefix_with_base_depth(
239 out,
240 base_indent,
241 field_depth.saturating_add(1),
242 );
243 let _ = write!(out, "{key}={value:?}");
244 }
245 }
246 }
247}
248
249fn push_rendered_line_prefix(out: &mut String, depth: usize) {
250 if !out.is_empty() {
251 out.push('\n');
252 }
253
254 for _ in 0..depth {
255 out.push_str(" ");
256 }
257}
258
259fn push_rendered_line_prefix_with_base_depth(out: &mut String, base_indent: &str, depth: usize) {
260 if !out.is_empty() {
261 out.push('\n');
262 }
263 out.push_str(base_indent);
264
265 for _ in 0..depth {
266 out.push_str(" ");
267 }
268}
269
270fn write_node_properties(out: &mut String, node_properties: &ExplainPropertyMap) {
271 let mut first = true;
272 for (key, value) in node_properties.iter() {
273 if first {
274 first = false;
275 } else {
276 out.push(',');
277 }
278 let _ = write!(out, "{key}={value:?}");
279 }
280}