Skip to main content

icydb_core/db/query/explain/
json.rs

1//! Module: query::explain::json
2//! Responsibility: canonical JSON rendering helpers for execution explain descriptors.
3//! Does not own: execution decision derivation or text-tree rendering.
4//! Boundary: deterministic JSON field ordering for execution explain output.
5
6use crate::db::query::explain::{
7    ExplainExecutionNodeDescriptor,
8    access_projection::write_access_json,
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    writer::JsonWriter,
14};
15
16impl ExplainExecutionNodeDescriptor {
17    /// Render this execution subtree as canonical JSON.
18    #[must_use]
19    pub fn render_json_canonical(&self) -> String {
20        let mut out = String::new();
21        let mut node_id_counter = 0_u64;
22        write_execution_node_json(self, &mut node_id_counter, &mut out);
23        out
24    }
25}
26
27fn write_execution_node_json(
28    node: &ExplainExecutionNodeDescriptor,
29    node_id_counter: &mut u64,
30    out: &mut String,
31) {
32    let node_id = *node_id_counter;
33    *node_id_counter = node_id_counter.saturating_add(1);
34    let mut object = JsonWriter::begin_object(out);
35
36    object.field_u64("node_id", node_id);
37    object.field_str("node_type", node.node_type().as_str());
38    object.field_str("layer", node.node_type().layer_label());
39    object.field_str(
40        "execution_mode",
41        execution_mode_label(node.execution_mode()),
42    );
43    object.field_str(
44        "execution_mode_detail",
45        execution_mode_detail_label(node.execution_mode()),
46    );
47    object.field_with("access_strategy", |out| {
48        match node.access_strategy().as_ref() {
49            Some(access) => write_access_json(access, out),
50            None => out.push_str("null"),
51        }
52    });
53    object.field_str("predicate_pushdown_mode", predicate_pushdown_mode(node));
54    match node.predicate_pushdown() {
55        Some(predicate_pushdown) => object.field_str("predicate_pushdown", predicate_pushdown),
56        None => object.field_null("predicate_pushdown"),
57    }
58    match fast_path_selected(node) {
59        Some(selected) => object.field_bool("fast_path_selected", selected),
60        None => object.field_null("fast_path_selected"),
61    }
62    match fast_path_reason(node) {
63        Some(reason) => object.field_str("fast_path_reason", reason),
64        None => object.field_null("fast_path_reason"),
65    }
66    match node.residual_predicate() {
67        Some(residual_predicate) => {
68            object.field_value_debug("residual_predicate", residual_predicate);
69        }
70        None => object.field_null("residual_predicate"),
71    }
72    match node.projection() {
73        Some(projection) => object.field_str("projection", projection),
74        None => object.field_null("projection"),
75    }
76    match node.ordering_source() {
77        Some(ordering_source) => {
78            object.field_str("ordering_source", ordering_source_label(ordering_source));
79        }
80        None => object.field_null("ordering_source"),
81    }
82    match node.limit() {
83        Some(limit) => object.field_u64("limit", u64::from(limit)),
84        None => object.field_null("limit"),
85    }
86    match node.cursor() {
87        Some(cursor) => object.field_bool("cursor", cursor),
88        None => object.field_null("cursor"),
89    }
90    match node.covering_scan() {
91        Some(covering_scan) => object.field_bool("covering_scan", covering_scan),
92        None => object.field_null("covering_scan"),
93    }
94    match node.rows_expected() {
95        Some(rows_expected) => object.field_u64("rows_expected", rows_expected),
96        None => object.field_null("rows_expected"),
97    }
98    object.field_with("children", |out| {
99        out.push('[');
100        for (index, child) in node.children().iter().enumerate() {
101            if index > 0 {
102                out.push(',');
103            }
104            write_execution_node_json(child, node_id_counter, out);
105        }
106        out.push(']');
107    });
108    object.field_debug_map("node_properties", node.node_properties());
109
110    object.finish();
111}