icydb_core/db/query/explain/
json.rs1use crate::db::query::{
7 admission::QueryAdmissionSummary,
8 explain::{
9 ExplainExecutionNodeDescriptor, FinalizedQueryDiagnostics,
10 access_projection::write_access_json,
11 execution::{execution_mode_label, ordering_source_label},
12 nodes::{
13 execution_mode_detail_label, fast_path_reason, fast_path_selected,
14 predicate_pushdown_mode,
15 },
16 writer::JsonWriter,
17 },
18};
19
20impl ExplainExecutionNodeDescriptor {
21 #[must_use]
23 pub fn render_json_canonical(&self) -> String {
24 let mut out = String::new();
25 let mut node_id_counter = 0_u64;
26 write_execution_node_json(self, &mut node_id_counter, &mut out);
27 out
28 }
29}
30
31impl FinalizedQueryDiagnostics {
32 #[must_use]
34 pub(in crate::db) fn render_json_canonical(&self) -> String {
35 let mut out = String::new();
36 let mut object = JsonWriter::begin_object(&mut out);
37 object.field_with("admission", |out| match self.admission() {
38 Some(admission) => write_admission_json(admission, out),
39 None => out.push_str("null"),
40 });
41 object.field_with("execution", |out| {
42 let mut node_id_counter = 0_u64;
43 write_execution_node_json(self.execution(), &mut node_id_counter, out);
44 });
45 object.field_str_slice("route_diagnostics", &self.route_diagnostics);
46 object.field_str_slice("logical_diagnostics", &self.logical_diagnostics);
47 object.field_with("reuse", |out| {
48 let Some(reuse) = self.reuse else {
49 out.push_str("null");
50 return;
51 };
52 let mut reuse_object = JsonWriter::begin_object(out);
53 reuse_object.field_str("artifact", "shared_prepared_query_plan");
54 reuse_object.field_str("outcome", if reuse.is_hit() { "hit" } else { "miss" });
55 reuse_object.finish();
56 });
57 object.finish();
58
59 out
60 }
61}
62
63fn write_admission_json(admission: &QueryAdmissionSummary, out: &mut String) {
64 let mut object = JsonWriter::begin_object(out);
65 object.field_str("lane", admission.lane().as_str());
66 object.field_str("decision", admission.decision().as_str());
67 match admission.rejection() {
68 Some(rejection) => object.field_str("reason", rejection.as_str()),
69 None => object.field_null("reason"),
70 }
71 object.field_str("plan_shape", admission.plan_shape().as_str());
72 object.field_str("selected_access", admission.selected_access().as_str());
73 match admission.selected_index() {
74 Some(selected_index) => object.field_str("selected_index", selected_index),
75 None => object.field_null("selected_index"),
76 }
77 write_optional_u32(&mut object, "limit", admission.limit());
78 write_optional_u32(&mut object, "offset", admission.offset());
79 write_optional_u64(&mut object, "scan_bound", admission.scan_bound());
80 object.field_str("scan_bound_kind", admission.scan_bound_kind().as_str());
81 write_optional_u32(
82 &mut object,
83 "returned_row_bound",
84 admission.returned_row_bound(),
85 );
86 object.field_str(
87 "returned_row_bound_kind",
88 admission.returned_row_bound_kind().as_str(),
89 );
90 write_optional_u32(
91 &mut object,
92 "primary_key_input_terms",
93 admission.primary_key_input_terms(),
94 );
95 write_optional_u32(
96 &mut object,
97 "primary_key_input_payload_bytes",
98 admission.primary_key_input_payload_bytes(),
99 );
100 object.field_str("residual_filter", admission.residual_filter().as_str());
101 object.field_str("ordering", admission.ordering().as_str());
102 object.field_with("materialization", |out| {
103 let materialization = admission.materialization();
104 let mut materialization_object = JsonWriter::begin_object(out);
105 materialization_object.field_bool("materialized_sort", materialization.materialized_sort());
106 write_optional_u32(
107 &mut materialization_object,
108 "materialized_rows",
109 materialization.materialized_rows(),
110 );
111 materialization_object
112 .field_str("row_bound_kind", materialization.row_bound_kind().as_str());
113 materialization_object.finish();
114 });
115 object.field_with("grouped", |out| {
116 let Some(grouped) = admission.grouped() else {
117 out.push_str("null");
118 return;
119 };
120 let mut grouped_object = JsonWriter::begin_object(out);
121 grouped_object.field_u64("group_field_count", u64::from(grouped.group_field_count()));
122 grouped_object.field_u64("aggregate_count", u64::from(grouped.aggregate_count()));
123 grouped_object.field_u64(
124 "distinct_aggregate_count",
125 u64::from(grouped.distinct_aggregate_count()),
126 );
127 grouped_object.field_u64("max_groups", grouped.max_groups());
128 grouped_object.field_u64("max_group_bytes", grouped.max_group_bytes());
129 grouped_object.field_bool("having_filter", grouped.has_having_filter());
130 grouped_object.finish();
131 });
132 object.finish();
133}
134
135fn write_optional_u32(object: &mut JsonWriter<'_>, key: &str, value: Option<u32>) {
136 match value {
137 Some(value) => object.field_u64(key, u64::from(value)),
138 None => object.field_null(key),
139 }
140}
141
142fn write_optional_u64(object: &mut JsonWriter<'_>, key: &str, value: Option<u64>) {
143 match value {
144 Some(value) => object.field_u64(key, value),
145 None => object.field_null(key),
146 }
147}
148
149fn write_execution_node_json(
150 node: &ExplainExecutionNodeDescriptor,
151 node_id_counter: &mut u64,
152 out: &mut String,
153) {
154 let node_id = *node_id_counter;
155 *node_id_counter = node_id_counter.saturating_add(1);
156 let mut object = JsonWriter::begin_object(out);
157
158 object.field_u64("node_id", node_id);
159 object.field_str("node_type", node.node_type().as_str());
160 object.field_str("layer", node.node_type().layer_label());
161 object.field_str(
162 "execution_mode",
163 execution_mode_label(node.execution_mode()),
164 );
165 object.field_str(
166 "execution_mode_detail",
167 execution_mode_detail_label(node.execution_mode()),
168 );
169 object.field_with("access_strategy", |out| {
170 match node.access_strategy().as_ref() {
171 Some(access) => write_access_json(access, out),
172 None => out.push_str("null"),
173 }
174 });
175 object.field_str("predicate_pushdown_mode", predicate_pushdown_mode(node));
176 match node.predicate_pushdown() {
177 Some(predicate_pushdown) => object.field_str("predicate_pushdown", predicate_pushdown),
178 None => object.field_null("predicate_pushdown"),
179 }
180 match node.filter_expr() {
181 Some(filter_expr) => object.field_str("filter_expr", filter_expr),
182 None => object.field_null("filter_expr"),
183 }
184 match node.residual_filter_expr() {
185 Some(residual_filter_expr) => {
186 object.field_str("residual_filter_expr", residual_filter_expr);
187 }
188 None => object.field_null("residual_filter_expr"),
189 }
190 match fast_path_selected(node) {
191 Some(selected) => object.field_bool("fast_path_selected", selected),
192 None => object.field_null("fast_path_selected"),
193 }
194 match fast_path_reason(node) {
195 Some(reason) => object.field_str("fast_path_reason", reason),
196 None => object.field_null("fast_path_reason"),
197 }
198 match node.residual_filter_predicate() {
199 Some(residual_filter_predicate) => {
200 object.field_value_debug("residual_filter_predicate", residual_filter_predicate);
201 }
202 None => object.field_null("residual_filter_predicate"),
203 }
204 match node.projection() {
205 Some(projection) => object.field_str("projection", projection),
206 None => object.field_null("projection"),
207 }
208 match node.ordering_source() {
209 Some(ordering_source) => {
210 object.field_str("ordering_source", ordering_source_label(ordering_source));
211 }
212 None => object.field_null("ordering_source"),
213 }
214 match node.limit() {
215 Some(limit) => object.field_u64("limit", u64::from(limit)),
216 None => object.field_null("limit"),
217 }
218 match node.cursor() {
219 Some(cursor) => object.field_bool("cursor", cursor),
220 None => object.field_null("cursor"),
221 }
222 match node.covering_scan() {
223 Some(covering_scan) => object.field_bool("covering_scan", covering_scan),
224 None => object.field_null("covering_scan"),
225 }
226 match node.rows_expected() {
227 Some(rows_expected) => object.field_u64("rows_expected", rows_expected),
228 None => object.field_null("rows_expected"),
229 }
230 object.field_with("children", |out| {
231 out.push('[');
232 for (index, child) in node.children().iter().enumerate() {
233 if index > 0 {
234 out.push(',');
235 }
236 write_execution_node_json(child, node_id_counter, out);
237 }
238 out.push(']');
239 });
240 object.field_debug_map("node_properties", node.node_properties());
241
242 object.finish();
243}