use crate::error::Result;
use crate::field::manifest::FieldRoot;
use crate::field::observe::{ObserveRequest, ObserveStats, OpenedField, observe_opened};
use crate::field::plan::{ObservePlan, plan};
use crate::field::provenance::{Basis, json_escape};
use crate::field::{FieldId, FieldStore};
use crate::limits::Limits;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExplainPlan {
pub plan: ObservePlan,
pub json: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExplainActual {
pub stats: ObserveStats,
pub answer_basis: Basis,
pub exact: bool,
}
impl ExplainActual {
pub fn to_json(&self) -> String {
let s = &self.stats;
format!(
concat!(
"{{",
"\"index_nodes_read\":{},",
"\"seed_nodes_fetched\":{},",
"\"seed_nodes_materialized\":{},",
"\"descriptor_bytes_read\":{},",
"\"descriptor_read_mode\":\"{}\",",
"\"manifest_bytes_read\":{},",
"\"index_bytes_read\":{},",
"\"seed_bytes_read\":{},",
"\"bytes_read\":{},",
"\"bytes_returned\":{},",
"\"deepened\":{},",
"\"wall_micros\":{},",
"\"basis\":\"{}\",",
"\"exact\":{}",
"}}"
),
s.index_nodes_read,
s.seed_nodes_fetched,
s.seed_nodes_materialized,
s.descriptor_bytes_read,
s.descriptor_read_mode.name(),
s.manifest_bytes_read,
s.index_bytes_read,
s.seed_bytes_read,
s.bytes_read,
s.bytes_returned,
s.deepened,
s.wall_micros,
self.answer_basis.name(),
self.exact,
)
}
}
pub fn explain(
manifest: &FieldRoot,
store: &FieldStore,
req: &ObserveRequest,
) -> Result<ExplainPlan> {
let plan = plan(manifest, store, req)?;
let json = plan_json(req, &plan);
Ok(ExplainPlan { plan, json })
}
pub fn explain_analyze(
store: &mut FieldStore,
id: &FieldId,
req: &ObserveRequest,
limits: Limits,
) -> Result<(ExplainPlan, ExplainActual)> {
let started = std::time::Instant::now();
let opened = OpenedField::open(store, id, req, limits)?;
let planned = plan(opened.manifest(), store, req)?;
let json = plan_json(req, &planned);
let (answer, mut stats, _) = observe_opened(store, &opened, req, limits)?;
stats.wall_micros = started.elapsed().as_micros().min(u128::from(u64::MAX)) as u64;
let actual = ExplainActual {
stats,
answer_basis: answer.basis,
exact: answer.exact,
};
Ok((
ExplainPlan {
plan: planned,
json,
},
actual,
))
}
fn plan_json(req: &ObserveRequest, plan: &ObservePlan) -> String {
format!(
concat!(
"{{",
"\"selector\":\"{}\",",
"\"representation\":\"{}\",",
"\"shape\":\"{}\",",
"\"index_reads\":{},",
"\"required_nodes\":{},",
"\"will_materialize\":{},",
"\"will_not_materialize\":{}",
"}}"
),
json_escape(&req.selector.canonical()),
json_escape(req.representation.name()),
plan.shape.name(),
plan.index_reads,
plan.required_nodes,
string_array(&plan.will_materialize),
string_array(&plan.will_not_materialize),
)
}
fn string_array(items: &[String]) -> String {
let body = items
.iter()
.map(|s| format!("\"{}\"", json_escape(s)))
.collect::<Vec<_>>()
.join(",");
format!("[{body}]")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn actual_json_has_exactly_the_fifteen_keys() {
let actual = ExplainActual {
stats: ObserveStats::default(),
answer_basis: Basis::DirectlyObserved,
exact: true,
};
assert_eq!(
actual.to_json(),
"{\"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}"
);
}
#[test]
fn string_array_escapes() {
assert_eq!(
string_array(&["a".into(), "b\"c".into()]),
"[\"a\",\"b\\\"c\"]"
);
assert_eq!(string_array(&[]), "[]");
}
}