use std::sync::Arc;
use selene_core::{DbString, Value};
use selene_graph::{SeleneGraph, TypedIndexKind};
use crate::plan::optimize::{
CompositeIndexHandle, IndexCatalog, IndexHandle, IndexKind, IndexTarget, TypedIndexLookup,
};
#[derive(Clone, Debug)]
pub struct LiveIndexCatalog {
snapshot: Arc<SeleneGraph>,
}
impl LiveIndexCatalog {
#[must_use]
pub const fn new(snapshot: Arc<SeleneGraph>) -> Self {
Self { snapshot }
}
}
impl IndexCatalog for LiveIndexCatalog {
fn typed_index(
&self,
target: IndexTarget,
label: DbString,
property: DbString,
) -> Option<TypedIndexLookup> {
let kind = match target {
IndexTarget::Node => self.snapshot.property_index_for(&label, &property)?.kind(),
IndexTarget::Edge => self
.snapshot
.edge_property_index_for(&label, &property)?
.kind(),
};
Some(TypedIndexLookup::new(
IndexHandle::new(0),
index_kind_from(kind),
))
}
fn label_index(&self, target: IndexTarget, _label: DbString) -> Option<IndexHandle> {
match target {
IndexTarget::Node | IndexTarget::Edge => Some(IndexHandle::new(0)),
}
}
fn composite_index(
&self,
target: IndexTarget,
label: DbString,
properties: &[DbString],
) -> Option<CompositeIndexHandle> {
if target != IndexTarget::Node {
return None;
}
let mut canonical = properties.to_vec();
canonical.sort_unstable();
let entry = self
.snapshot
.composite_property_index_entry_for(&label, &canonical)?;
let kinds = entry.kinds();
let component_kinds: Vec<(DbString, IndexKind)> = entry
.declared_properties
.iter()
.zip(kinds.iter())
.map(|(property, kind)| (property.clone(), index_kind_from(*kind)))
.collect();
Some(CompositeIndexHandle::new(
IndexHandle::new(0),
component_kinds,
))
}
fn total_rows(&self, target: IndexTarget) -> Option<u64> {
Some(match target {
IndexTarget::Node => self.snapshot.node_count() as u64,
IndexTarget::Edge => self.snapshot.edge_count() as u64,
})
}
fn label_cardinality(&self, target: IndexTarget, label: DbString) -> Option<u64> {
match target {
IndexTarget::Node => Some(
self.snapshot
.nodes_with_label(&label)
.map_or(0, |bm| bm.len()),
),
IndexTarget::Edge => Some(
self.snapshot
.edges_with_label(&label)
.map_or(0, |bm| bm.len()),
),
}
}
fn equality_cardinality(
&self,
target: IndexTarget,
label: DbString,
property: DbString,
value: &Value,
) -> Option<u64> {
match target {
IndexTarget::Node => self
.snapshot
.nodes_with_property_eq(&label, &property, value)
.map(|cow| cow.len()),
IndexTarget::Edge => self
.snapshot
.edges_with_property_eq(&label, &property, value)
.map(|cow| cow.len()),
}
}
fn range_cardinality(
&self,
target: IndexTarget,
label: DbString,
property: DbString,
range: (std::ops::Bound<Value>, std::ops::Bound<Value>),
) -> Option<u64> {
match target {
IndexTarget::Node => self
.snapshot
.nodes_with_property_range(&label, &property, range)
.map(|bm| bm.len()),
IndexTarget::Edge => self
.snapshot
.edges_with_property_range(&label, &property, range)
.map(|bm| bm.len()),
}
}
fn typed_avg_bucket(
&self,
target: IndexTarget,
label: DbString,
property: DbString,
) -> Option<u64> {
let index = match target {
IndexTarget::Node => self.snapshot.property_index_for(&label, &property)?,
IndexTarget::Edge => self.snapshot.edge_property_index_for(&label, &property)?,
};
Some(avg_bucket(index.cardinality(), index.distinct_keys()))
}
fn composite_cardinality(
&self,
target: IndexTarget,
label: DbString,
properties: &[DbString],
keys: &[Value],
) -> Option<u64> {
if target != IndexTarget::Node {
return None;
}
let mut canonical = properties.to_vec();
canonical.sort_unstable();
let index = self
.snapshot
.composite_property_index_for(&label, &canonical)?;
let mut order: Vec<usize> = (0..properties.len()).collect();
order.sort_by_key(|&i| properties[i].clone());
if keys.len() != properties.len() {
return None;
}
let ordered: Vec<&Value> = order.iter().map(|&i| &keys[i]).collect();
match index.key_from_values(&ordered) {
Ok(key) => Some(index.lookup_key(&key).map_or(0, |bm| bm.len())),
Err(_) => None,
}
}
fn composite_avg_bucket(
&self,
target: IndexTarget,
label: DbString,
properties: &[DbString],
) -> Option<u64> {
if target != IndexTarget::Node {
return None;
}
let mut canonical = properties.to_vec();
canonical.sort_unstable();
let index = self
.snapshot
.composite_property_index_for(&label, &canonical)?;
Some(avg_bucket(index.cardinality(), index.distinct_keys()))
}
}
fn avg_bucket(cardinality: u64, distinct_keys: u64) -> u64 {
if distinct_keys == 0 {
return 0;
}
cardinality.div_ceil(distinct_keys)
}
fn index_kind_from(kind: TypedIndexKind) -> IndexKind {
match kind {
TypedIndexKind::Bool => IndexKind::Boolean,
TypedIndexKind::I64 => IndexKind::Integer,
TypedIndexKind::U64 => IndexKind::UnsignedInteger,
TypedIndexKind::I128 => IndexKind::Integer128,
TypedIndexKind::U128 => IndexKind::UnsignedInteger128,
TypedIndexKind::Decimal => IndexKind::Decimal,
TypedIndexKind::F32 => IndexKind::Float32,
TypedIndexKind::F64 => IndexKind::Float,
TypedIndexKind::String => IndexKind::String,
TypedIndexKind::Date => IndexKind::Date,
TypedIndexKind::LocalDateTime => IndexKind::LocalDateTime,
TypedIndexKind::ZonedDateTime => IndexKind::ZonedDateTime,
TypedIndexKind::LocalTime => IndexKind::LocalTime,
TypedIndexKind::ZonedTime => IndexKind::ZonedTime,
TypedIndexKind::Duration => IndexKind::Duration,
TypedIndexKind::Uuid => IndexKind::Uuid,
}
}