Skip to main content

jetro_core/introspect/
render.rs

1use super::report::QueryInspection;
2use std::fmt::Write;
3
4impl QueryInspection {
5    /// Renders the inspection as a compact tree for humans.
6    pub fn format_tree(&self) -> String {
7        let mut out = String::new();
8        let _ = writeln!(out, "query:");
9        let _ = writeln!(out, "  {}", self.query);
10        let _ = writeln!(out, "context:");
11        let _ = writeln!(out, "  {:?} / {:?}", self.context, self.level);
12        let _ = writeln!(out, "summary:");
13        let _ = writeln!(out, "  root: {}", self.summary.root);
14        let _ = writeln!(
15            out,
16            "  selected executor: {}",
17            self.summary
18                .selected_executor
19                .map(|backend| format!("{backend:?}"))
20                .unwrap_or_else(|| "unknown".to_string())
21        );
22        let _ = writeln!(
23            out,
24            "  materializes root: {}",
25            self.summary.materializes_root
26        );
27        let _ = writeln!(
28            out,
29            "  contains vm fallback: {}",
30            self.summary.contains_vm_fallback
31        );
32        let _ = writeln!(out, "  byte native: {}", self.summary.can_run_byte_native);
33
34        if let Some(logical) = &self.logical {
35            let _ = writeln!(out, "logical:");
36            let _ = writeln!(out, "  ast root: {}", logical.ast_root);
37            let _ = writeln!(out, "  root shape: {}", logical.root_shape);
38            for note in &logical.notes {
39                let _ = writeln!(out, "  note: {note}");
40            }
41        }
42
43        if let Some(physical) = &self.physical {
44            let _ = writeln!(out, "physical:");
45            let _ = writeln!(out, "  root: {}", physical.root);
46            for node in &physical.nodes {
47                let _ = writeln!(out, "  node {}: {}", node.id, node.kind);
48                if !node.children.is_empty() {
49                    let _ = writeln!(out, "    children: {:?}", node.children);
50                }
51                if let Some(detail) = &node.detail {
52                    let _ = writeln!(out, "    detail: {detail}");
53                }
54                let _ = writeln!(
55                    out,
56                    "    facts: byte_native={}, avoid_root_materialization={}, vm_fallback={}",
57                    node.facts.can_run_byte_native,
58                    node.facts.can_avoid_root_materialization,
59                    node.facts.contains_vm_fallback
60                );
61                if !node.backends.is_empty() {
62                    let backends = node
63                        .backends
64                        .iter()
65                        .map(|backend| format!("{:?}:{:?}", backend.backend, backend.status))
66                        .collect::<Vec<_>>()
67                        .join(", ");
68                    let _ = writeln!(out, "    backends: {backends}");
69                }
70            }
71        }
72
73        if let Some(pipeline) = &self.pipeline {
74            let _ = writeln!(out, "pipeline:");
75            let _ = writeln!(out, "  source: {}", pipeline.source);
76            let _ = writeln!(out, "  demand: {}", pipeline.source_demand);
77            let _ = writeln!(out, "  sink: {}", pipeline.sink);
78            if let Some(path) = &pipeline.execution_path {
79                let _ = writeln!(out, "  execution path: {path}");
80            }
81            if let Some(boundary) = &pipeline.fallback_boundary {
82                let _ = writeln!(out, "  fallback boundary: {boundary}");
83            }
84            for stage in &pipeline.stages {
85                let _ = writeln!(out, "  stage {}: {}", stage.index, stage.kind);
86                if let Some(detail) = &stage.detail {
87                    let _ = writeln!(out, "    {detail}");
88                }
89            }
90        }
91
92        if let Some(ndjson) = &self.ndjson {
93            let _ = writeln!(out, "ndjson:");
94            let _ = writeln!(out, "  route: {}", ndjson.route_kind);
95            let _ = writeln!(out, "  source: {}", ndjson.source);
96            if let Some(reason) = &ndjson.fallback_reason {
97                let _ = writeln!(out, "  fallback: {reason}");
98            }
99            if let Some(path) = &ndjson.writer_path {
100                let _ = writeln!(out, "  writer path: {path}");
101            }
102            if let Some(rows) = &ndjson.rows {
103                let _ = writeln!(out, "  rows:");
104                let _ = writeln!(out, "    plan: {}", rows.plan_kind);
105                let _ = writeln!(out, "    source: {}", rows.source);
106                let _ = writeln!(out, "    direction: {}", rows.direction);
107                let _ = writeln!(out, "    demand: {}", rows.demand);
108                if let Some(strategy) = &rows.file_strategy {
109                    let _ = writeln!(out, "    file strategy: {strategy}");
110                }
111                let _ = writeln!(out, "    sink: {}", rows.sink);
112                for stage in &rows.stages {
113                    let _ = writeln!(out, "    stage {}: {}", stage.index, stage.kind);
114                    if let Some(detail) = &stage.detail {
115                        let _ = writeln!(out, "      {detail}");
116                    }
117                }
118            }
119            for plan in &ndjson.direct_plans {
120                let _ = writeln!(out, "  direct: {}", plan.kind);
121                if let Some(detail) = &plan.detail {
122                    let _ = writeln!(out, "    {detail}");
123                }
124            }
125        }
126
127        if !self.warnings.is_empty() {
128            let _ = writeln!(out, "warnings:");
129            for warning in &self.warnings {
130                let _ = writeln!(out, "  {warning}");
131            }
132        }
133        out
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use crate::introspect::{
140        BackendKind, InspectContext, InspectLevel, InspectionSummary, QueryInspection,
141    };
142
143    #[test]
144    fn tree_renderer_includes_key_sections() {
145        let report = QueryInspection {
146            query: "$.a".to_string(),
147            context: InspectContext::Bytes,
148            level: InspectLevel::Plan,
149            summary: InspectionSummary {
150                root: "node:0".to_string(),
151                selected_executor: Some(BackendKind::TapePath),
152                materializes_root: false,
153                contains_vm_fallback: false,
154                can_run_byte_native: true,
155            },
156            logical: None,
157            physical: None,
158            pipeline: None,
159            ndjson: None,
160            warnings: Vec::new(),
161        };
162
163        let text = report.format_tree();
164
165        assert!(text.contains("query:"));
166        assert!(text.contains("summary:"));
167        assert!(text.contains("TapePath"));
168    }
169}