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 = next_node_id(node_id_counter);
33    let mut object = JsonWriter::begin_object(out);
34
35    object.field_u64("node_id", node_id);
36    object.field_str("node_type", node.node_type().as_str());
37    object.field_str("layer", node.node_type().layer_label());
38    object.field_str(
39        "execution_mode",
40        execution_mode_label(node.execution_mode()),
41    );
42    object.field_str(
43        "execution_mode_detail",
44        execution_mode_detail_label(node.execution_mode()),
45    );
46    object.field_with("access_strategy", |out| {
47        match node.access_strategy().as_ref() {
48            Some(access) => write_access_json(access, out),
49            None => out.push_str("null"),
50        }
51    });
52    object.field_str("predicate_pushdown_mode", predicate_pushdown_mode(node));
53    match node.predicate_pushdown() {
54        Some(predicate_pushdown) => object.field_str("predicate_pushdown", predicate_pushdown),
55        None => object.field_null("predicate_pushdown"),
56    }
57    match fast_path_selected(node) {
58        Some(selected) => object.field_bool("fast_path_selected", selected),
59        None => object.field_null("fast_path_selected"),
60    }
61    match fast_path_reason(node) {
62        Some(reason) => object.field_str("fast_path_reason", reason),
63        None => object.field_null("fast_path_reason"),
64    }
65    match node.residual_predicate() {
66        Some(residual_predicate) => {
67            object.field_value_debug("residual_predicate", residual_predicate);
68        }
69        None => object.field_null("residual_predicate"),
70    }
71    match node.projection() {
72        Some(projection) => object.field_str("projection", projection),
73        None => object.field_null("projection"),
74    }
75    match node.ordering_source() {
76        Some(ordering_source) => {
77            object.field_str("ordering_source", ordering_source_label(ordering_source));
78        }
79        None => object.field_null("ordering_source"),
80    }
81    match node.limit() {
82        Some(limit) => object.field_u64("limit", u64::from(limit)),
83        None => object.field_null("limit"),
84    }
85    match node.cursor() {
86        Some(cursor) => object.field_bool("cursor", cursor),
87        None => object.field_null("cursor"),
88    }
89    match node.covering_scan() {
90        Some(covering_scan) => object.field_bool("covering_scan", covering_scan),
91        None => object.field_null("covering_scan"),
92    }
93    match node.rows_expected() {
94        Some(rows_expected) => object.field_u64("rows_expected", rows_expected),
95        None => object.field_null("rows_expected"),
96    }
97    object.field_with("children", |out| {
98        out.push('[');
99        for (index, child) in node.children().iter().enumerate() {
100            if index > 0 {
101                out.push(',');
102            }
103            write_execution_node_json(child, node_id_counter, out);
104        }
105        out.push(']');
106    });
107    object.field_debug_map("node_properties", node.node_properties());
108
109    object.finish();
110}
111
112const fn next_node_id(node_id_counter: &mut u64) -> u64 {
113    let node_id = *node_id_counter;
114    *node_id_counter = node_id_counter.saturating_add(1);
115    node_id
116}