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