use crate::{model::index::IndexModel, traits::FieldValue, value::Value};
use std::ops::Bound;
pub(crate) type IndexRangePathRef<'a> = (
&'a IndexModel,
&'a [Value],
&'a Bound<Value>,
&'a Bound<Value>,
);
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SemanticIndexRangeSpec {
index: IndexModel,
field_slots: Vec<usize>,
prefix_values: Vec<Value>,
lower: Bound<Value>,
upper: Bound<Value>,
}
impl SemanticIndexRangeSpec {
#[must_use]
pub(crate) fn new(
index: IndexModel,
field_slots: Vec<usize>,
prefix_values: Vec<Value>,
lower: Bound<Value>,
upper: Bound<Value>,
) -> Self {
debug_assert!(
!field_slots.is_empty(),
"semantic index-range field slots must include the range slot",
);
debug_assert_eq!(
field_slots.len(),
prefix_values.len().saturating_add(1),
"semantic index-range slots must include one slot per prefix field plus range slot",
);
debug_assert!(
prefix_values.len() < index.fields().len(),
"semantic index-range prefix must be shorter than index arity",
);
Self {
index,
field_slots,
prefix_values,
lower,
upper,
}
}
#[cfg(test)]
#[must_use]
pub(crate) fn from_prefix_and_bounds(
index: IndexModel,
prefix_values: Vec<Value>,
lower: Bound<Value>,
upper: Bound<Value>,
) -> Self {
let slot_count = prefix_values.len().saturating_add(1);
let field_slots = (0..slot_count).collect();
Self::new(index, field_slots, prefix_values, lower, upper)
}
#[must_use]
pub(crate) const fn index(&self) -> &IndexModel {
&self.index
}
#[must_use]
pub(crate) const fn field_slots(&self) -> &[usize] {
self.field_slots.as_slice()
}
#[must_use]
pub(crate) const fn prefix_values(&self) -> &[Value] {
self.prefix_values.as_slice()
}
#[must_use]
pub(crate) const fn lower(&self) -> &Bound<Value> {
&self.lower
}
#[must_use]
pub(crate) const fn upper(&self) -> &Bound<Value> {
&self.upper
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum AccessPath<K> {
ByKey(K),
ByKeys(Vec<K>),
KeyRange { start: K, end: K },
IndexPrefix {
index: IndexModel,
values: Vec<Value>,
},
IndexMultiLookup {
index: IndexModel,
values: Vec<Value>,
},
IndexRange { spec: SemanticIndexRangeSpec },
FullScan,
}
impl<K> AccessPath<K> {
#[cfg(test)]
#[must_use]
pub(crate) fn index_range(
index: IndexModel,
prefix_values: Vec<Value>,
lower: Bound<Value>,
upper: Bound<Value>,
) -> Self {
Self::IndexRange {
spec: SemanticIndexRangeSpec::from_prefix_and_bounds(
index,
prefix_values,
lower,
upper,
),
}
}
#[must_use]
pub(crate) const fn is_full_scan(&self) -> bool {
matches!(self, Self::FullScan)
}
#[must_use]
pub(crate) const fn is_by_key(&self) -> bool {
matches!(self, Self::ByKey(_))
}
#[must_use]
pub(crate) const fn is_index_multi_lookup(&self) -> bool {
matches!(self, Self::IndexMultiLookup { .. })
}
#[must_use]
pub(crate) const fn as_by_key(&self) -> Option<&K> {
match self {
Self::ByKey(key) => Some(key),
Self::ByKeys(_)
| Self::KeyRange { .. }
| Self::IndexPrefix { .. }
| Self::IndexMultiLookup { .. }
| Self::IndexRange { .. }
| Self::FullScan => None,
}
}
#[must_use]
pub(crate) const fn as_by_keys(&self) -> Option<&[K]> {
match self {
Self::ByKeys(keys) => Some(keys.as_slice()),
Self::ByKey(_)
| Self::KeyRange { .. }
| Self::IndexPrefix { .. }
| Self::IndexMultiLookup { .. }
| Self::IndexRange { .. }
| Self::FullScan => None,
}
}
#[must_use]
pub(crate) const fn as_index_prefix(&self) -> Option<(&IndexModel, &[Value])> {
match self {
Self::IndexPrefix { index, values } => Some((index, values.as_slice())),
_ => None,
}
}
#[must_use]
pub(crate) const fn as_index_range(&self) -> Option<IndexRangePathRef<'_>> {
match self {
Self::IndexRange { spec } => Some((
spec.index(),
spec.prefix_values(),
spec.lower(),
spec.upper(),
)),
_ => None,
}
}
#[must_use]
pub(crate) const fn selected_index_model(&self) -> Option<&IndexModel> {
match self {
Self::IndexPrefix { index, .. } | Self::IndexMultiLookup { index, .. } => Some(index),
Self::IndexRange { spec } => Some(spec.index()),
Self::ByKey(_) | Self::ByKeys(_) | Self::KeyRange { .. } | Self::FullScan => None,
}
}
#[must_use]
pub(crate) const fn as_key_range(&self) -> Option<(&K, &K)> {
match self {
Self::KeyRange { start, end } => Some((start, end)),
Self::ByKey(_)
| Self::ByKeys(_)
| Self::IndexPrefix { .. }
| Self::IndexMultiLookup { .. }
| Self::IndexRange { .. }
| Self::FullScan => None,
}
}
#[must_use]
pub(crate) const fn is_primary_store_authoritative_scan(&self) -> bool {
matches!(self, Self::KeyRange { .. } | Self::FullScan)
}
#[must_use]
pub(crate) const fn is_primary_key_lookup(&self) -> bool {
matches!(self, Self::ByKey(_) | Self::ByKeys(_))
}
pub(crate) fn map_keys<T, E, F>(self, mut map_key: F) -> Result<AccessPath<T>, E>
where
F: FnMut(K) -> Result<T, E>,
{
match self {
Self::ByKey(key) => Ok(AccessPath::ByKey(map_key(key)?)),
Self::ByKeys(keys) => {
let mut mapped = Vec::with_capacity(keys.len());
for key in keys {
mapped.push(map_key(key)?);
}
Ok(AccessPath::ByKeys(mapped))
}
Self::KeyRange { start, end } => Ok(AccessPath::KeyRange {
start: map_key(start)?,
end: map_key(end)?,
}),
Self::IndexPrefix { index, values } => Ok(AccessPath::IndexPrefix { index, values }),
Self::IndexMultiLookup { index, values } => {
Ok(AccessPath::IndexMultiLookup { index, values })
}
Self::IndexRange { spec } => Ok(AccessPath::IndexRange { spec }),
Self::FullScan => Ok(AccessPath::FullScan),
}
}
}
impl<K> AccessPath<K> where K: FieldValue {}
impl AccessPath<Value> {
pub(in crate::db) fn bind_runtime_values(self, replacements: &[(Value, Value)]) -> Self {
match self {
Self::ByKey(key) => Self::ByKey(bind_runtime_value(key, replacements)),
Self::ByKeys(keys) => Self::ByKeys(
keys.into_iter()
.map(|key| bind_runtime_value(key, replacements))
.collect(),
),
Self::KeyRange { start, end } => Self::KeyRange {
start: bind_runtime_value(start, replacements),
end: bind_runtime_value(end, replacements),
},
Self::IndexPrefix { index, values } => Self::IndexPrefix {
index,
values: values
.into_iter()
.map(|value| bind_runtime_value(value, replacements))
.collect(),
},
Self::IndexMultiLookup { index, values } => Self::IndexMultiLookup {
index,
values: values
.into_iter()
.map(|value| bind_runtime_value(value, replacements))
.collect(),
},
Self::IndexRange { spec } => Self::IndexRange {
spec: spec.bind_runtime_values(replacements),
},
Self::FullScan => Self::FullScan,
}
}
}
impl SemanticIndexRangeSpec {
pub(in crate::db) fn bind_runtime_values(self, replacements: &[(Value, Value)]) -> Self {
Self::new(
self.index,
self.field_slots,
self.prefix_values
.into_iter()
.map(|value| bind_runtime_value(value, replacements))
.collect(),
bind_runtime_bound(self.lower, replacements),
bind_runtime_bound(self.upper, replacements),
)
}
}
fn bind_runtime_bound(bound: Bound<Value>, replacements: &[(Value, Value)]) -> Bound<Value> {
match bound {
Bound::Unbounded => Bound::Unbounded,
Bound::Included(value) => Bound::Included(bind_runtime_value(value, replacements)),
Bound::Excluded(value) => Bound::Excluded(bind_runtime_value(value, replacements)),
}
}
fn bind_runtime_value(value: Value, replacements: &[(Value, Value)]) -> Value {
replacements
.iter()
.find(|(template, _)| *template == value)
.map_or(value, |(_, bound)| bound.clone())
}