use crate::{
db::Predicate,
model::index::{IndexKeyItem, IndexKeyItemsRef, IndexModel},
value::Value,
};
use std::ops::Bound;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum AccessPathKind {
ByKey,
ByKeys,
KeyRange,
IndexPrefix,
IndexMultiLookup,
IndexRange,
FullScan,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct SemanticIndexAccessContract {
ordinal: u16,
name: &'static str,
store_path: &'static str,
key_items: IndexKeyItemsRef,
unique: bool,
predicate_semantics: Option<fn() -> &'static Predicate>,
}
impl PartialEq for SemanticIndexAccessContract {
fn eq(&self, other: &Self) -> bool {
self.ordinal == other.ordinal
&& self.name == other.name
&& self.store_path == other.store_path
&& self.key_items == other.key_items
&& self.unique == other.unique
&& match (self.predicate_semantics, other.predicate_semantics) {
(Some(left), Some(right)) => std::ptr::fn_addr_eq(left, right),
(None, None) => true,
(Some(_), None) | (None, Some(_)) => false,
}
}
}
impl Eq for SemanticIndexAccessContract {}
impl SemanticIndexAccessContract {
#[must_use]
pub(in crate::db) const fn from_index(index: IndexModel) -> Self {
Self {
ordinal: index.ordinal(),
name: index.name(),
store_path: index.store(),
key_items: index.key_items(),
unique: index.is_unique(),
predicate_semantics: index.predicate_resolver(),
}
}
#[must_use]
pub(in crate::db) const fn ordinal(self) -> u16 {
self.ordinal
}
#[must_use]
pub(in crate::db) const fn name(self) -> &'static str {
self.name
}
#[must_use]
pub(in crate::db) const fn store_path(self) -> &'static str {
self.store_path
}
#[must_use]
pub(in crate::db) const fn key_items(self) -> IndexKeyItemsRef {
self.key_items
}
#[must_use]
pub(in crate::db) const fn key_arity(self) -> usize {
match self.key_items {
IndexKeyItemsRef::Fields(fields) => fields.len(),
IndexKeyItemsRef::Items(items) => items.len(),
}
}
#[must_use]
pub(in crate::db) const fn key_item_at(self, slot: usize) -> Option<IndexKeyItem> {
match self.key_items {
IndexKeyItemsRef::Fields(fields) => {
if slot < fields.len() {
Some(IndexKeyItem::Field(fields[slot]))
} else {
None
}
}
IndexKeyItemsRef::Items(items) => {
if slot < items.len() {
Some(items[slot])
} else {
None
}
}
}
}
#[cfg(test)]
#[must_use]
pub(in crate::db) fn fields(self) -> Vec<&'static str> {
match self.key_items {
IndexKeyItemsRef::Fields(fields) => fields.to_vec(),
IndexKeyItemsRef::Items(items) => items.iter().map(IndexKeyItem::field).collect(),
}
}
#[must_use]
pub(in crate::db) const fn is_unique(self) -> bool {
self.unique
}
#[must_use]
pub(in crate::db) const fn is_filtered(self) -> bool {
self.predicate_semantics.is_some()
}
#[must_use]
pub(in crate::db) const fn has_expression_key_items(self) -> bool {
match self.key_items {
IndexKeyItemsRef::Fields(_) => false,
IndexKeyItemsRef::Items(items) => {
let mut index = 0usize;
while index < items.len() {
if matches!(items[index], IndexKeyItem::Expression(_)) {
return true;
}
index = index.saturating_add(1);
}
false
}
}
}
#[must_use]
pub(in crate::db) fn predicate_semantics(self) -> Option<&'static Predicate> {
self.predicate_semantics.map(|resolver| resolver())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SemanticIndexRangeSpec {
index: SemanticIndexAccessContract,
field_slots: Vec<usize>,
prefix_values: Vec<Value>,
lower: Bound<Value>,
upper: Bound<Value>,
}
impl SemanticIndexRangeSpec {
#[must_use]
#[cfg(test)]
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: SemanticIndexAccessContract::from_index(index),
field_slots,
prefix_values,
lower,
upper,
}
}
#[must_use]
pub(crate) fn from_access_contract(
index: SemanticIndexAccessContract,
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.key_arity(),
"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) -> SemanticIndexAccessContract {
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: SemanticIndexAccessContract,
values: Vec<Value>,
},
IndexMultiLookup {
index: SemanticIndexAccessContract,
values: Vec<Value>,
},
IndexRange { spec: SemanticIndexRangeSpec },
FullScan,
}
impl<K> AccessPath<K> {
#[must_use]
pub(in crate::db) const fn kind(&self) -> AccessPathKind {
match self {
Self::ByKey(_) => AccessPathKind::ByKey,
Self::ByKeys(_) => AccessPathKind::ByKeys,
Self::KeyRange { .. } => AccessPathKind::KeyRange,
Self::IndexPrefix { .. } => AccessPathKind::IndexPrefix,
Self::IndexMultiLookup { .. } => AccessPathKind::IndexMultiLookup,
Self::IndexRange { .. } => AccessPathKind::IndexRange,
Self::FullScan => AccessPathKind::FullScan,
}
}
#[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(in crate::db) const fn as_index_prefix_contract(
&self,
) -> Option<(SemanticIndexAccessContract, &[Value])> {
match self {
Self::IndexPrefix { index, values } => Some((*index, values.as_slice())),
_ => None,
}
}
#[must_use]
pub(in crate::db) const fn as_index_multi_lookup_contract(
&self,
) -> Option<(SemanticIndexAccessContract, &[Value])> {
match self {
Self::IndexMultiLookup { index, values } => Some((*index, values.as_slice())),
_ => None,
}
}
#[must_use]
pub(crate) const fn as_index_range(&self) -> Option<&SemanticIndexRangeSpec> {
match self {
Self::IndexRange { spec } => Some(spec),
_ => None,
}
}
#[must_use]
pub(in crate::db) const fn selected_index_contract(
&self,
) -> Option<SemanticIndexAccessContract> {
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),
}
}
}