1use crate::error::Result;
23use crate::field::manifest::FieldRoot;
24use crate::field::observe::{ObserveRequest, ObserveStats, OpenedField, observe_opened};
25use crate::field::plan::{ObservePlan, plan};
26use crate::field::provenance::{Basis, json_escape};
27use crate::field::{FieldId, FieldStore};
28use crate::limits::Limits;
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct ExplainPlan {
33 pub plan: ObservePlan,
35 pub json: String,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct ExplainActual {
42 pub stats: ObserveStats,
44 pub answer_basis: Basis,
46 pub exact: bool,
48}
49
50impl ExplainActual {
51 pub fn to_json(&self) -> String {
53 let s = &self.stats;
54 format!(
55 concat!(
56 "{{",
57 "\"index_nodes_read\":{},",
58 "\"seed_nodes_fetched\":{},",
59 "\"seed_nodes_materialized\":{},",
60 "\"descriptor_bytes_read\":{},",
61 "\"descriptor_read_mode\":\"{}\",",
62 "\"manifest_bytes_read\":{},",
63 "\"index_bytes_read\":{},",
64 "\"seed_bytes_read\":{},",
65 "\"bytes_read\":{},",
66 "\"bytes_returned\":{},",
67 "\"deepened\":{},",
68 "\"wall_micros\":{},",
69 "\"basis\":\"{}\",",
70 "\"exact\":{}",
71 "}}"
72 ),
73 s.index_nodes_read,
74 s.seed_nodes_fetched,
75 s.seed_nodes_materialized,
76 s.descriptor_bytes_read,
77 s.descriptor_read_mode.name(),
78 s.manifest_bytes_read,
79 s.index_bytes_read,
80 s.seed_bytes_read,
81 s.bytes_read,
82 s.bytes_returned,
83 s.deepened,
84 s.wall_micros,
85 self.answer_basis.name(),
86 self.exact,
87 )
88 }
89}
90
91pub fn explain(
93 manifest: &FieldRoot,
94 store: &FieldStore,
95 req: &ObserveRequest,
96) -> Result<ExplainPlan> {
97 let plan = plan(manifest, store, req)?;
98 let json = plan_json(req, &plan);
99 Ok(ExplainPlan { plan, json })
100}
101
102pub fn explain_analyze(
109 store: &mut FieldStore,
110 id: &FieldId,
111 req: &ObserveRequest,
112 limits: Limits,
113) -> Result<(ExplainPlan, ExplainActual)> {
114 let started = std::time::Instant::now();
115 let opened = OpenedField::open(store, id, req, limits)?;
116 let planned = plan(opened.manifest(), store, req)?;
117 let json = plan_json(req, &planned);
118 let (answer, mut stats, _) = observe_opened(store, &opened, req, limits)?;
119 stats.wall_micros = started.elapsed().as_micros().min(u128::from(u64::MAX)) as u64;
120 let actual = ExplainActual {
121 stats,
122 answer_basis: answer.basis,
123 exact: answer.exact,
124 };
125 Ok((
126 ExplainPlan {
127 plan: planned,
128 json,
129 },
130 actual,
131 ))
132}
133
134fn plan_json(req: &ObserveRequest, plan: &ObservePlan) -> String {
135 format!(
136 concat!(
137 "{{",
138 "\"selector\":\"{}\",",
139 "\"representation\":\"{}\",",
140 "\"shape\":\"{}\",",
141 "\"index_reads\":{},",
142 "\"required_nodes\":{},",
143 "\"will_materialize\":{},",
144 "\"will_not_materialize\":{}",
145 "}}"
146 ),
147 json_escape(&req.selector.canonical()),
148 json_escape(req.representation.name()),
149 plan.shape.name(),
150 plan.index_reads,
151 plan.required_nodes,
152 string_array(&plan.will_materialize),
153 string_array(&plan.will_not_materialize),
154 )
155}
156
157fn string_array(items: &[String]) -> String {
158 let body = items
159 .iter()
160 .map(|s| format!("\"{}\"", json_escape(s)))
161 .collect::<Vec<_>>()
162 .join(",");
163 format!("[{body}]")
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn actual_json_has_exactly_the_fifteen_keys() {
172 let actual = ExplainActual {
173 stats: ObserveStats::default(),
174 answer_basis: Basis::DirectlyObserved,
175 exact: true,
176 };
177 assert_eq!(
178 actual.to_json(),
179 "{\"index_nodes_read\":0,\"seed_nodes_fetched\":0,\"seed_nodes_materialized\":0,\"descriptor_bytes_read\":0,\"descriptor_read_mode\":\"full\",\"manifest_bytes_read\":0,\"index_bytes_read\":0,\"seed_bytes_read\":0,\"bytes_read\":0,\"bytes_returned\":0,\"deepened\":false,\"wall_micros\":0,\"basis\":\"directly-observed\",\"exact\":true}"
180 );
181 }
182
183 #[test]
184 fn string_array_escapes() {
185 assert_eq!(
186 string_array(&["a".into(), "b\"c".into()]),
187 "[\"a\",\"b\\\"c\"]"
188 );
189 assert_eq!(string_array(&[]), "[]");
190 }
191}