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