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