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 let _ = write!(out, "access_strategy={access_strategy:?}");
160 }
161 if let Some(predicate_pushdown) = self.predicate_pushdown.as_ref() {
162 push_rendered_line_prefix_with_indent(out, field_indent);
163 let _ = write!(out, "predicate_pushdown={predicate_pushdown}");
164 }
165 if let Some(residual_predicate) = self.residual_predicate.as_ref() {
166 push_rendered_line_prefix_with_indent(out, field_indent);
167 let _ = write!(out, "residual_predicate={residual_predicate:?}");
168 }
169 if let Some(projection) = self.projection.as_ref() {
170 push_rendered_line_prefix_with_indent(out, field_indent);
171 let _ = write!(out, "projection={projection}");
172 }
173 if let Some(ordering_source) = self.ordering_source {
174 push_rendered_line_prefix_with_indent(out, field_indent);
175 let _ = write!(
176 out,
177 "ordering_source={}",
178 ordering_source_label(ordering_source)
179 );
180 }
181 if let Some(limit) = self.limit {
182 push_rendered_line_prefix_with_indent(out, field_indent);
183 let _ = write!(out, "limit={limit}");
184 }
185 if let Some(cursor) = self.cursor {
186 push_rendered_line_prefix_with_indent(out, field_indent);
187 let _ = write!(out, "cursor={cursor}");
188 }
189 if let Some(covering_scan) = self.covering_scan {
190 push_rendered_line_prefix_with_indent(out, field_indent);
191 let _ = write!(out, "covering_scan={covering_scan}");
192 }
193 if let Some(rows_expected) = self.rows_expected {
194 push_rendered_line_prefix_with_indent(out, field_indent);
195 let _ = write!(out, "rows_expected={rows_expected}");
196 }
197 if !self.node_properties.is_empty() {
198 push_rendered_line_prefix_with_indent(out, field_indent);
199 out.push_str("node_properties=");
200 write_node_properties(out, &self.node_properties);
201 }
202 }
203}
204
205fn push_rendered_line_prefix(out: &mut String, depth: usize) {
206 if !out.is_empty() {
207 out.push('\n');
208 }
209
210 for _ in 0..depth {
211 out.push_str(" ");
212 }
213}
214
215fn push_rendered_line_prefix_with_indent(out: &mut String, indent: &str) {
216 if !out.is_empty() {
217 out.push('\n');
218 }
219 out.push_str(indent);
220}
221
222fn write_node_properties(out: &mut String, node_properties: &ExplainPropertyMap) {
223 let mut first = true;
224 for (key, value) in node_properties.iter() {
225 if first {
226 first = false;
227 } else {
228 out.push(',');
229 }
230 let _ = write!(out, "{key}={value:?}");
231 }
232}
233
234const fn next_node_id(node_id_counter: &mut u64) -> u64 {
235 let node_id = *node_id_counter;
236 *node_id_counter = node_id_counter.saturating_add(1);
237 node_id
238}