icydb_core/db/query/explain/
render.rs1use crate::db::query::explain::{
7 ExplainExecutionNodeDescriptor, ExplainPropertyMap, FinalizedQueryDiagnostics,
8 execution::{execution_mode_label, ordering_source_label},
9 nodes::{
10 execution_mode_detail_label, fast_path_reason, fast_path_selected, predicate_pushdown_mode,
11 },
12};
13use crate::db::query::plan::explain_access_strategy_label;
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 #[cfg(test)]
29 pub fn render_text_tree_verbose(&self) -> String {
30 self.render_text_tree_verbose_with_indent("")
31 }
32
33 #[must_use]
36 pub fn render_text_tree_verbose_with_indent(&self, indent: &str) -> String {
37 let mut out = String::new();
38 let mut node_id_counter = 0_u64;
39 self.render_text_tree_verbose_into(indent, 0, &mut node_id_counter, &mut out);
40 out
41 }
42
43 fn render_text_tree_into(&self, depth: usize, node_id_counter: &mut u64, out: &mut String) {
44 let node_id = *node_id_counter;
45 *node_id_counter = node_id_counter.saturating_add(1);
46 push_rendered_line_prefix(out, depth);
47 let _ = write!(
48 out,
49 "{} execution_mode={}",
50 self.node_type.as_str(),
51 execution_mode_label(self.execution_mode)
52 );
53 let _ = write!(out, " node_id={node_id}");
54 let _ = write!(out, " layer={}", self.node_type.layer_label());
55 let _ = write!(
56 out,
57 " execution_mode_detail={}",
58 execution_mode_detail_label(self.execution_mode)
59 );
60 let _ = write!(
61 out,
62 " predicate_pushdown_mode={}",
63 predicate_pushdown_mode(self)
64 );
65 if let Some(fast_path_selected) = fast_path_selected(self) {
66 let _ = write!(out, " fast_path_selected={fast_path_selected}");
67 }
68 if let Some(fast_path_reason) = fast_path_reason(self) {
69 let _ = write!(out, " fast_path_reason={fast_path_reason}");
70 }
71
72 if let Some(access_strategy) = self.access_strategy.as_ref() {
73 out.push_str(" access=");
74 out.push_str(explain_access_strategy_label(access_strategy).as_str());
75 }
76 if let Some(predicate_pushdown) = self.predicate_pushdown.as_ref() {
77 let _ = write!(out, " predicate_pushdown={predicate_pushdown}");
78 }
79 if let Some(filter_expr) = self.filter_expr.as_ref() {
80 let _ = write!(out, " filter_expr={filter_expr}");
81 }
82 if let Some(residual_filter_expr) = self.residual_filter_expr.as_ref() {
83 let _ = write!(out, " residual_filter_expr={residual_filter_expr}");
84 }
85 if let Some(residual_filter_predicate) = self.residual_filter_predicate.as_ref() {
86 let _ = write!(
87 out,
88 " residual_filter_predicate={residual_filter_predicate:?}"
89 );
90 }
91 if let Some(projection) = self.projection.as_ref() {
92 let _ = write!(out, " projection={projection}");
93 }
94 if let Some(ordering_source) = self.ordering_source {
95 let _ = write!(
96 out,
97 " ordering_source={}",
98 ordering_source_label(ordering_source)
99 );
100 }
101 if let Some(limit) = self.limit {
102 let _ = write!(out, " limit={limit}");
103 }
104 if let Some(cursor) = self.cursor {
105 let _ = write!(out, " cursor={cursor}");
106 }
107 if let Some(covering_scan) = self.covering_scan {
108 let _ = write!(out, " covering_scan={covering_scan}");
109 }
110 if let Some(rows_expected) = self.rows_expected {
111 let _ = write!(out, " rows_expected={rows_expected}");
112 }
113 if !self.node_properties.is_empty() {
114 out.push_str(" node_properties=");
115 write_node_properties(out, &self.node_properties);
116 }
117
118 for child in &self.children {
119 child.render_text_tree_into(depth.saturating_add(1), node_id_counter, out);
120 }
121 }
122
123 fn render_text_tree_verbose_into(
124 &self,
125 base_indent: &str,
126 depth: usize,
127 node_id_counter: &mut u64,
128 out: &mut String,
129 ) {
130 let node_id = *node_id_counter;
131 *node_id_counter = node_id_counter.saturating_add(1);
132
133 push_rendered_line_prefix_with_base_depth(out, base_indent, depth);
136 let _ = write!(
137 out,
138 "{} execution_mode={}",
139 self.node_type.as_str(),
140 execution_mode_label(self.execution_mode)
141 );
142 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
143 let _ = write!(out, "node_id={node_id}");
144 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
145 let _ = write!(out, "layer={}", self.node_type.layer_label());
146 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
147 let _ = write!(
148 out,
149 "execution_mode_detail={}",
150 execution_mode_detail_label(self.execution_mode)
151 );
152 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
153 let _ = write!(
154 out,
155 "predicate_pushdown_mode={}",
156 predicate_pushdown_mode(self)
157 );
158 if let Some(fast_path_selected) = fast_path_selected(self) {
159 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
160 let _ = write!(out, "fast_path_selected={fast_path_selected}");
161 }
162 if let Some(fast_path_reason) = fast_path_reason(self) {
163 push_rendered_line_prefix_with_base_depth(out, base_indent, depth.saturating_add(1));
164 let _ = write!(out, "fast_path_reason={fast_path_reason}");
165 }
166
167 self.render_text_tree_verbose_node_fields(base_indent, depth.saturating_add(1), out);
169
170 for child in &self.children {
172 child.render_text_tree_verbose_into(
173 base_indent,
174 depth.saturating_add(1),
175 node_id_counter,
176 out,
177 );
178 }
179 }
180
181 fn render_text_tree_verbose_node_fields(
182 &self,
183 base_indent: &str,
184 field_depth: usize,
185 out: &mut String,
186 ) {
187 if let Some(access_strategy) = self.access_strategy.as_ref() {
188 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
189 out.push_str("access_strategy=");
190 out.push_str(explain_access_strategy_label(access_strategy).as_str());
191 }
192 if let Some(predicate_pushdown) = self.predicate_pushdown.as_ref() {
193 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
194 let _ = write!(out, "predicate_pushdown={predicate_pushdown}");
195 }
196 if let Some(filter_expr) = self.filter_expr.as_ref() {
197 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
198 let _ = write!(out, "filter_expr={filter_expr}");
199 }
200 if let Some(residual_filter_expr) = self.residual_filter_expr.as_ref() {
201 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
202 let _ = write!(out, "residual_filter_expr={residual_filter_expr}");
203 }
204 if let Some(residual_filter_predicate) = self.residual_filter_predicate.as_ref() {
205 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
206 let _ = write!(
207 out,
208 "residual_filter_predicate={residual_filter_predicate:?}"
209 );
210 }
211 if let Some(projection) = self.projection.as_ref() {
212 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
213 let _ = write!(out, "projection={projection}");
214 }
215 if let Some(ordering_source) = self.ordering_source {
216 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
217 let _ = write!(
218 out,
219 "ordering_source={}",
220 ordering_source_label(ordering_source)
221 );
222 }
223 if let Some(limit) = self.limit {
224 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
225 let _ = write!(out, "limit={limit}");
226 }
227 if let Some(cursor) = self.cursor {
228 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
229 let _ = write!(out, "cursor={cursor}");
230 }
231 if let Some(covering_scan) = self.covering_scan {
232 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
233 let _ = write!(out, "covering_scan={covering_scan}");
234 }
235 if let Some(rows_expected) = self.rows_expected {
236 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
237 let _ = write!(out, "rows_expected={rows_expected}");
238 }
239 if !self.node_properties.is_empty() {
240 push_rendered_line_prefix_with_base_depth(out, base_indent, field_depth);
241 out.push_str("node_properties:");
242
243 for (key, value) in self.node_properties.iter() {
246 push_rendered_line_prefix_with_base_depth(
247 out,
248 base_indent,
249 field_depth.saturating_add(1),
250 );
251 let _ = write!(out, "{key}={value:?}");
252 }
253 }
254 }
255}
256
257impl FinalizedQueryDiagnostics {
258 #[must_use]
260 pub(in crate::db) fn render_text_verbose(&self) -> String {
261 self.render_text_verbose_with_tree_indent("")
262 }
263
264 #[must_use]
267 pub(in crate::db) fn render_text_verbose_with_tree_indent(&self, tree_indent: &str) -> String {
268 let mut lines = vec![
269 self.execution()
270 .render_text_tree_verbose_with_indent(tree_indent),
271 ];
272 lines.extend(self.route_diagnostics.iter().cloned());
273 lines.extend(self.logical_diagnostics.iter().cloned());
274 if let Some(reuse) = self.reuse {
275 let artifact = match reuse.artifact_class() {
276 crate::db::TraceReuseArtifactClass::SharedPreparedQueryPlan => {
277 "shared_prepared_query_plan"
278 }
279 };
280 let outcome = if reuse.is_hit() { "hit" } else { "miss" };
281 lines.push(format!("diag.s.semantic_reuse_artifact={artifact}"));
282 lines.push(format!("diag.s.semantic_reuse={outcome}"));
283 }
284
285 lines.join("\n")
286 }
287}
288
289fn push_rendered_line_prefix(out: &mut String, depth: usize) {
290 if !out.is_empty() {
291 out.push('\n');
292 }
293
294 for _ in 0..depth {
295 out.push_str(" ");
296 }
297}
298
299fn push_rendered_line_prefix_with_base_depth(out: &mut String, base_indent: &str, depth: usize) {
300 if !out.is_empty() {
301 out.push('\n');
302 }
303 out.push_str(base_indent);
304
305 for _ in 0..depth {
306 out.push_str(" ");
307 }
308}
309
310fn write_node_properties(out: &mut String, node_properties: &ExplainPropertyMap) {
311 let mut first = true;
312 for (key, value) in node_properties.iter() {
313 if first {
314 first = false;
315 } else {
316 out.push(',');
317 }
318 let _ = write!(out, "{key}={value:?}");
319 }
320}