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