use crate::{
db::{
access::{AccessPath, AccessPlan},
query::explain::ExplainAccessPath,
},
value::Value,
};
use std::ops::Bound;
pub(crate) trait AccessPlanProjection<K> {
type Output;
fn by_key(&mut self, key: &K) -> Self::Output;
fn by_keys(&mut self, keys: &[K]) -> Self::Output;
fn key_range(&mut self, start: &K, end: &K) -> Self::Output;
fn index_prefix(
&mut self,
index_name: &'static str,
index_fields: &[&'static str],
prefix_len: usize,
values: &[Value],
) -> Self::Output;
fn index_multi_lookup(
&mut self,
index_name: &'static str,
index_fields: &[&'static str],
values: &[Value],
) -> Self::Output;
fn index_range(
&mut self,
index_name: &'static str,
index_fields: &[&'static str],
prefix_len: usize,
prefix: &[Value],
lower: &Bound<Value>,
upper: &Bound<Value>,
) -> Self::Output;
fn full_scan(&mut self) -> Self::Output;
fn union(&mut self, children: Vec<Self::Output>) -> Self::Output;
fn intersection(&mut self, children: Vec<Self::Output>) -> Self::Output;
}
pub(crate) fn project_access_plan<K, P>(plan: &AccessPlan<K>, projection: &mut P) -> P::Output
where
P: AccessPlanProjection<K>,
{
plan.project(projection)
}
impl<K> AccessPlan<K> {
fn project<P>(&self, projection: &mut P) -> P::Output
where
P: AccessPlanProjection<K>,
{
match self {
Self::Path(path) => path.project(projection),
Self::Union(children) => {
let child_projections =
project_projection_children(children.iter(), projection, Self::project);
projection.union(child_projections)
}
Self::Intersection(children) => {
let child_projections =
project_projection_children(children.iter(), projection, Self::project);
projection.intersection(child_projections)
}
}
}
}
impl<K> AccessPath<K> {
fn project<P>(&self, projection: &mut P) -> P::Output
where
P: AccessPlanProjection<K>,
{
match self {
Self::ByKey(key) => projection.by_key(key),
Self::ByKeys(keys) => projection.by_keys(keys),
Self::KeyRange { start, end } => projection.key_range(start, end),
Self::IndexPrefix { index, values } => {
projection.index_prefix(index.name(), index.fields(), values.len(), values)
}
Self::IndexMultiLookup { index, values } => {
projection.index_multi_lookup(index.name(), index.fields(), values)
}
Self::IndexRange { spec } => projection.index_range(
spec.index().name(),
spec.index().fields(),
spec.prefix_values().len(),
spec.prefix_values(),
spec.lower(),
spec.upper(),
),
Self::FullScan => projection.full_scan(),
}
}
}
pub(crate) fn project_explain_access_path<P>(
access: &ExplainAccessPath,
projection: &mut P,
) -> P::Output
where
P: AccessPlanProjection<Value>,
{
match access {
ExplainAccessPath::ByKey { key } => projection.by_key(key),
ExplainAccessPath::ByKeys { keys } => projection.by_keys(keys),
ExplainAccessPath::KeyRange { start, end } => projection.key_range(start, end),
ExplainAccessPath::IndexPrefix {
name,
fields,
prefix_len,
values,
} => projection.index_prefix(name, fields, *prefix_len, values),
ExplainAccessPath::IndexMultiLookup {
name,
fields,
values,
} => projection.index_multi_lookup(name, fields, values),
ExplainAccessPath::IndexRange {
name,
fields,
prefix_len,
prefix,
lower,
upper,
} => projection.index_range(name, fields, *prefix_len, prefix, lower, upper),
ExplainAccessPath::FullScan => projection.full_scan(),
ExplainAccessPath::Union(children) => {
let child_projections = project_projection_children(
children.iter(),
projection,
project_explain_access_path,
);
projection.union(child_projections)
}
ExplainAccessPath::Intersection(children) => {
let child_projections = project_projection_children(
children.iter(),
projection,
project_explain_access_path,
);
projection.intersection(child_projections)
}
}
}
fn project_projection_children<'a, T: 'a, P, I, F, O>(
children: I,
projection: &mut P,
project_child: F,
) -> Vec<O>
where
I: Iterator<Item = &'a T>,
F: Fn(&'a T, &mut P) -> O,
{
children
.map(|child| project_child(child, projection))
.collect()
}
#[cfg(test)]
mod access_projection_tests {
use super::*;
use crate::{model::index::IndexModel, value::Value};
const TEST_INDEX_FIELDS: [&str; 2] = ["group", "rank"];
const TEST_INDEX: IndexModel = IndexModel::generated(
"tests::group_rank",
"tests::store",
&TEST_INDEX_FIELDS,
false,
);
#[derive(Default)]
struct AccessPlanEventProjection {
events: Vec<&'static str>,
union_child_counts: Vec<usize>,
intersection_child_counts: Vec<usize>,
seen_index: Option<(&'static str, usize, usize, usize)>,
}
impl AccessPlanProjection<u64> for AccessPlanEventProjection {
type Output = ();
fn by_key(&mut self, _key: &u64) -> Self::Output {
self.events.push("by_key");
}
fn by_keys(&mut self, keys: &[u64]) -> Self::Output {
self.events.push("by_keys");
assert_eq!(keys, [2, 3].as_slice());
}
fn key_range(&mut self, start: &u64, end: &u64) -> Self::Output {
self.events.push("key_range");
assert_eq!((*start, *end), (4, 9));
}
fn index_prefix(
&mut self,
index_name: &'static str,
index_fields: &[&'static str],
prefix_len: usize,
values: &[Value],
) -> Self::Output {
self.events.push("index_prefix");
self.seen_index = Some((index_name, index_fields.len(), prefix_len, values.len()));
}
fn index_multi_lookup(
&mut self,
index_name: &'static str,
index_fields: &[&'static str],
values: &[Value],
) -> Self::Output {
self.events.push("index_multi_lookup");
self.seen_index = Some((index_name, index_fields.len(), 1, values.len()));
}
fn index_range(
&mut self,
index_name: &'static str,
index_fields: &[&'static str],
prefix_len: usize,
prefix: &[Value],
lower: &Bound<Value>,
upper: &Bound<Value>,
) -> Self::Output {
self.events.push("index_range");
self.seen_index = Some((index_name, index_fields.len(), prefix_len, prefix.len()));
assert_eq!(lower, &Bound::Included(Value::Uint(8)));
assert_eq!(upper, &Bound::Excluded(Value::Uint(12)));
}
fn full_scan(&mut self) -> Self::Output {
self.events.push("full_scan");
}
fn union(&mut self, children: Vec<Self::Output>) -> Self::Output {
self.events.push("union");
self.union_child_counts.push(children.len());
}
fn intersection(&mut self, children: Vec<Self::Output>) -> Self::Output {
self.events.push("intersection");
self.intersection_child_counts.push(children.len());
}
}
#[test]
fn project_access_plan_walks_canonical_access_variants() {
let plan: AccessPlan<u64> = AccessPlan::Union(vec![
AccessPlan::path(AccessPath::ByKey(1)),
AccessPlan::path(AccessPath::ByKeys(vec![2, 3])),
AccessPlan::path(AccessPath::KeyRange { start: 4, end: 9 }),
AccessPlan::path(AccessPath::IndexPrefix {
index: TEST_INDEX,
values: vec![Value::Uint(7)],
}),
AccessPlan::path(AccessPath::IndexMultiLookup {
index: TEST_INDEX,
values: vec![Value::Uint(7), Value::Uint(9)],
}),
AccessPlan::path(AccessPath::index_range(
TEST_INDEX,
vec![Value::Uint(7)],
Bound::Included(Value::Uint(8)),
Bound::Excluded(Value::Uint(12)),
)),
AccessPlan::Intersection(vec![
AccessPlan::path(AccessPath::FullScan),
AccessPlan::path(AccessPath::ByKey(11)),
]),
]);
let mut projection = AccessPlanEventProjection::default();
project_access_plan(&plan, &mut projection);
assert_eq!(projection.union_child_counts, vec![7]);
assert_eq!(projection.intersection_child_counts, vec![2]);
assert_eq!(projection.seen_index, Some((TEST_INDEX.name(), 2, 1, 1)));
assert!(
projection.events.contains(&"by_key"),
"projection must visit by-key variants"
);
assert!(
projection.events.contains(&"by_keys"),
"projection must visit by-keys variants"
);
assert!(
projection.events.contains(&"key_range"),
"projection must visit key-range variants"
);
assert!(
projection.events.contains(&"index_prefix"),
"projection must visit index-prefix variants"
);
assert!(
projection.events.contains(&"index_range"),
"projection must visit index-range variants"
);
assert!(
projection.events.contains(&"index_multi_lookup"),
"projection must visit index-multi-lookup variants",
);
assert!(
projection.events.contains(&"full_scan"),
"projection must visit full-scan variants"
);
}
#[derive(Default)]
struct ExplainAccessEventProjection {
events: Vec<&'static str>,
union_child_counts: Vec<usize>,
intersection_child_counts: Vec<usize>,
seen_index: Option<(&'static str, usize, usize, usize)>,
}
impl AccessPlanProjection<Value> for ExplainAccessEventProjection {
type Output = ();
fn by_key(&mut self, key: &Value) -> Self::Output {
self.events.push("by_key");
assert_eq!(key, &Value::Uint(10));
}
fn by_keys(&mut self, keys: &[Value]) -> Self::Output {
self.events.push("by_keys");
assert_eq!(keys, [Value::Uint(20), Value::Uint(30)].as_slice());
}
fn key_range(&mut self, start: &Value, end: &Value) -> Self::Output {
self.events.push("key_range");
assert_eq!((start, end), (&Value::Uint(40), &Value::Uint(90)));
}
fn index_prefix(
&mut self,
index_name: &'static str,
index_fields: &[&'static str],
prefix_len: usize,
values: &[Value],
) -> Self::Output {
self.events.push("index_prefix");
self.seen_index = Some((index_name, index_fields.len(), prefix_len, values.len()));
}
fn index_multi_lookup(
&mut self,
index_name: &'static str,
index_fields: &[&'static str],
values: &[Value],
) -> Self::Output {
self.events.push("index_multi_lookup");
self.seen_index = Some((index_name, index_fields.len(), 1, values.len()));
}
fn index_range(
&mut self,
index_name: &'static str,
index_fields: &[&'static str],
prefix_len: usize,
prefix: &[Value],
lower: &Bound<Value>,
upper: &Bound<Value>,
) -> Self::Output {
self.events.push("index_range");
self.seen_index = Some((index_name, index_fields.len(), prefix_len, prefix.len()));
assert_eq!(lower, &Bound::Included(Value::Uint(8)));
assert_eq!(upper, &Bound::Excluded(Value::Uint(12)));
}
fn full_scan(&mut self) -> Self::Output {
self.events.push("full_scan");
}
fn union(&mut self, children: Vec<Self::Output>) -> Self::Output {
self.events.push("union");
self.union_child_counts.push(children.len());
}
fn intersection(&mut self, children: Vec<Self::Output>) -> Self::Output {
self.events.push("intersection");
self.intersection_child_counts.push(children.len());
}
}
#[test]
fn project_explain_access_path_walks_canonical_access_variants() {
let access = ExplainAccessPath::Union(vec![
ExplainAccessPath::ByKey {
key: Value::Uint(10),
},
ExplainAccessPath::ByKeys {
keys: vec![Value::Uint(20), Value::Uint(30)],
},
ExplainAccessPath::KeyRange {
start: Value::Uint(40),
end: Value::Uint(90),
},
ExplainAccessPath::IndexPrefix {
name: TEST_INDEX.name(),
fields: vec!["group", "rank"],
prefix_len: 1,
values: vec![Value::Uint(7)],
},
ExplainAccessPath::IndexMultiLookup {
name: TEST_INDEX.name(),
fields: vec!["group", "rank"],
values: vec![Value::Uint(7), Value::Uint(9)],
},
ExplainAccessPath::IndexRange {
name: TEST_INDEX.name(),
fields: vec!["group", "rank"],
prefix_len: 1,
prefix: vec![Value::Uint(7)],
lower: Bound::Included(Value::Uint(8)),
upper: Bound::Excluded(Value::Uint(12)),
},
ExplainAccessPath::Intersection(vec![
ExplainAccessPath::FullScan,
ExplainAccessPath::ByKey {
key: Value::Uint(10),
},
]),
]);
let mut projection = ExplainAccessEventProjection::default();
project_explain_access_path(&access, &mut projection);
assert_eq!(projection.union_child_counts, vec![7]);
assert_eq!(projection.intersection_child_counts, vec![2]);
assert_eq!(projection.seen_index, Some((TEST_INDEX.name(), 2, 1, 1)));
assert!(
projection.events.contains(&"by_key"),
"projection must visit by-key variants"
);
assert!(
projection.events.contains(&"by_keys"),
"projection must visit by-keys variants"
);
assert!(
projection.events.contains(&"key_range"),
"projection must visit key-range variants"
);
assert!(
projection.events.contains(&"index_prefix"),
"projection must visit index-prefix variants"
);
assert!(
projection.events.contains(&"index_range"),
"projection must visit index-range variants"
);
assert!(
projection.events.contains(&"index_multi_lookup"),
"projection must visit index-multi-lookup variants",
);
assert!(
projection.events.contains(&"full_scan"),
"projection must visit full-scan variants"
);
}
}