use crate::{
db::{
access::AccessPlan,
query::{
explain::{ExplainAccessPath, ExplainExecutionNodeType, writer::JsonWriter},
plan::{
AccessPlanProjection, explain_access_kind_label, project_access_plan,
project_explain_access_path,
},
},
},
value::Value,
};
use std::fmt::Write;
struct ExplainAccessProjection;
impl<K> AccessPlanProjection<K> for ExplainAccessProjection
where
K: crate::traits::FieldValue,
{
type Output = ExplainAccessPath;
fn by_key(&mut self, key: &K) -> Self::Output {
ExplainAccessPath::ByKey {
key: key.to_value(),
}
}
fn by_keys(&mut self, keys: &[K]) -> Self::Output {
ExplainAccessPath::ByKeys {
keys: keys
.iter()
.map(crate::traits::FieldValue::to_value)
.collect(),
}
}
fn key_range(&mut self, start: &K, end: &K) -> Self::Output {
ExplainAccessPath::KeyRange {
start: start.to_value(),
end: end.to_value(),
}
}
fn index_prefix(
&mut self,
index_name: &'static str,
index_fields: &[&'static str],
prefix_len: usize,
values: &[Value],
) -> Self::Output {
ExplainAccessPath::IndexPrefix {
name: index_name,
fields: index_fields.to_vec(),
prefix_len,
values: values.to_vec(),
}
}
fn index_multi_lookup(
&mut self,
index_name: &'static str,
index_fields: &[&'static str],
values: &[Value],
) -> Self::Output {
ExplainAccessPath::IndexMultiLookup {
name: index_name,
fields: index_fields.to_vec(),
values: values.to_vec(),
}
}
fn index_range(
&mut self,
index_name: &'static str,
index_fields: &[&'static str],
prefix_len: usize,
prefix: &[Value],
lower: &std::ops::Bound<Value>,
upper: &std::ops::Bound<Value>,
) -> Self::Output {
ExplainAccessPath::IndexRange {
name: index_name,
fields: index_fields.to_vec(),
prefix_len,
prefix: prefix.to_vec(),
lower: lower.clone(),
upper: upper.clone(),
}
}
fn full_scan(&mut self) -> Self::Output {
ExplainAccessPath::FullScan
}
fn union(&mut self, children: Vec<Self::Output>) -> Self::Output {
ExplainAccessPath::Union(children)
}
fn intersection(&mut self, children: Vec<Self::Output>) -> Self::Output {
ExplainAccessPath::Intersection(children)
}
}
pub(in crate::db::query::explain) fn write_access_json(
access: &ExplainAccessPath,
out: &mut String,
) {
match access {
ExplainAccessPath::ByKey { key } => {
let mut object = JsonWriter::begin_object(out);
object.field_str("type", "ByKey");
object.field_value_debug("key", key);
object.finish();
}
ExplainAccessPath::ByKeys { keys } => {
let mut object = JsonWriter::begin_object(out);
object.field_str("type", "ByKeys");
object.field_debug_slice("keys", keys);
object.finish();
}
ExplainAccessPath::KeyRange { start, end } => {
let mut object = JsonWriter::begin_object(out);
object.field_str("type", "KeyRange");
object.field_value_debug("start", start);
object.field_value_debug("end", end);
object.finish();
}
ExplainAccessPath::IndexPrefix {
name,
fields,
prefix_len,
values,
} => {
let mut object = JsonWriter::begin_object(out);
object.field_str("type", "IndexPrefix");
object.field_str("name", name);
object.field_str_slice("fields", fields);
object.field_u64("prefix_len", *prefix_len as u64);
object.field_debug_slice("values", values);
object.finish();
}
ExplainAccessPath::IndexMultiLookup {
name,
fields,
values,
} => {
let mut object = JsonWriter::begin_object(out);
object.field_str("type", "IndexMultiLookup");
object.field_str("name", name);
object.field_str_slice("fields", fields);
object.field_debug_slice("values", values);
object.finish();
}
ExplainAccessPath::IndexRange {
name,
fields,
prefix_len,
prefix,
lower,
upper,
} => {
let mut object = JsonWriter::begin_object(out);
object.field_str("type", "IndexRange");
object.field_str("name", name);
object.field_str_slice("fields", fields);
object.field_u64("prefix_len", *prefix_len as u64);
object.field_debug_slice("prefix", prefix);
object.field_value_debug("lower", lower);
object.field_value_debug("upper", upper);
object.finish();
}
ExplainAccessPath::FullScan => {
let mut object = JsonWriter::begin_object(out);
object.field_str("type", "FullScan");
object.finish();
}
ExplainAccessPath::Union(children) => {
let mut object = JsonWriter::begin_object(out);
object.field_str("type", "Union");
object.field_with("children", |out| {
out.push('[');
for (index, child) in children.iter().enumerate() {
if index > 0 {
out.push(',');
}
write_access_json(child, out);
}
out.push(']');
});
object.finish();
}
ExplainAccessPath::Intersection(children) => {
let mut object = JsonWriter::begin_object(out);
object.field_str("type", "Intersection");
object.field_with("children", |out| {
out.push('[');
for (index, child) in children.iter().enumerate() {
if index > 0 {
out.push(',');
}
write_access_json(child, out);
}
out.push(']');
});
object.finish();
}
}
}
struct ExplainAccessStrategyLabelProjection;
impl AccessPlanProjection<Value> for ExplainAccessStrategyLabelProjection {
type Output = String;
fn by_key(&mut self, _key: &Value) -> Self::Output {
"ByKey".to_string()
}
fn by_keys(&mut self, _keys: &[Value]) -> Self::Output {
"ByKeys".to_string()
}
fn key_range(&mut self, _start: &Value, _end: &Value) -> Self::Output {
"KeyRange".to_string()
}
fn index_prefix(
&mut self,
index_name: &'static str,
_index_fields: &[&'static str],
_prefix_len: usize,
_values: &[Value],
) -> Self::Output {
let mut label = String::new();
let _ = write!(&mut label, "IndexPrefix({index_name})");
label
}
fn index_multi_lookup(
&mut self,
index_name: &'static str,
_index_fields: &[&'static str],
_values: &[Value],
) -> Self::Output {
let mut label = String::new();
let _ = write!(&mut label, "IndexMultiLookup({index_name})");
label
}
fn index_range(
&mut self,
index_name: &'static str,
_index_fields: &[&'static str],
_prefix_len: usize,
_prefix: &[Value],
_lower: &std::ops::Bound<Value>,
_upper: &std::ops::Bound<Value>,
) -> Self::Output {
let mut label = String::new();
let _ = write!(&mut label, "IndexRange({index_name})");
label
}
fn full_scan(&mut self) -> Self::Output {
"FullScan".to_string()
}
fn union(&mut self, children: Vec<Self::Output>) -> Self::Output {
let mut label = String::new();
let _ = write!(&mut label, "Union({})", children.len());
label
}
fn intersection(&mut self, children: Vec<Self::Output>) -> Self::Output {
let mut label = String::new();
let _ = write!(&mut label, "Intersection({})", children.len());
label
}
}
pub(in crate::db) fn explain_access_strategy_label(access: &ExplainAccessPath) -> String {
project_explain_access_path(access, &mut ExplainAccessStrategyLabelProjection)
}
pub(in crate::db) fn explain_access_execution_node_type(
access: &ExplainAccessPath,
) -> ExplainExecutionNodeType {
match explain_access_kind_label(access) {
"by_key" => ExplainExecutionNodeType::ByKeyLookup,
"by_keys" | "empty_access_contract" => ExplainExecutionNodeType::ByKeysLookup,
"key_range" => ExplainExecutionNodeType::PrimaryKeyRangeScan,
"index_prefix" => ExplainExecutionNodeType::IndexPrefixScan,
"index_multi_lookup" => ExplainExecutionNodeType::IndexMultiLookup,
"index_range" => ExplainExecutionNodeType::IndexRangeScan,
"full_scan" => ExplainExecutionNodeType::FullScan,
"union" => ExplainExecutionNodeType::Union,
"intersection" => ExplainExecutionNodeType::Intersection,
other => unreachable!("unexpected explain access kind label: {other}"),
}
}
pub(in crate::db) fn explain_access_plan<K>(access: &AccessPlan<K>) -> ExplainAccessPath
where
K: crate::traits::FieldValue,
{
project_access_plan(access, &mut ExplainAccessProjection)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::Value;
#[test]
fn explain_access_execution_node_type_uses_shared_access_classifier() {
assert_eq!(
explain_access_execution_node_type(&ExplainAccessPath::ByKey {
key: Value::Uint(1),
}),
ExplainExecutionNodeType::ByKeyLookup,
);
assert_eq!(
explain_access_execution_node_type(&ExplainAccessPath::Union(vec![
ExplainAccessPath::FullScan,
ExplainAccessPath::ByKeys {
keys: vec![Value::Uint(2)],
},
])),
ExplainExecutionNodeType::Union,
);
}
}