pub mod edge_index;
use std::collections::BTreeMap;
use std::collections::HashMap;
use cypherlite_core::{NodeId, PropertyValue};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct PropertyValueKey(pub PropertyValue);
impl PartialEq for PropertyValueKey {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == std::cmp::Ordering::Equal
}
}
impl Eq for PropertyValueKey {}
impl PropertyValueKey {
fn discriminant(&self) -> u8 {
self.0.type_tag()
}
}
impl PartialOrd for PropertyValueKey {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PropertyValueKey {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering;
let d1 = self.discriminant();
let d2 = other.discriminant();
if d1 != d2 {
return d1.cmp(&d2);
}
match (&self.0, &other.0) {
(PropertyValue::Null, PropertyValue::Null) => Ordering::Equal,
(PropertyValue::Bool(a), PropertyValue::Bool(b)) => a.cmp(b),
(PropertyValue::Int64(a), PropertyValue::Int64(b)) => a.cmp(b),
(PropertyValue::Float64(a), PropertyValue::Float64(b)) => {
total_cmp_f64(*a, *b)
}
(PropertyValue::String(a), PropertyValue::String(b)) => a.cmp(b),
(PropertyValue::Bytes(a), PropertyValue::Bytes(b)) => a.cmp(b),
(PropertyValue::Array(a), PropertyValue::Array(b)) => {
let len_ord = a.len().cmp(&b.len());
if len_ord != Ordering::Equal {
return len_ord;
}
for (av, bv) in a.iter().zip(b.iter()) {
let kv_a = PropertyValueKey(av.clone());
let kv_b = PropertyValueKey(bv.clone());
let c = kv_a.cmp(&kv_b);
if c != Ordering::Equal {
return c;
}
}
Ordering::Equal
}
(PropertyValue::DateTime(a), PropertyValue::DateTime(b)) => a.cmp(b),
_ => Ordering::Equal, }
}
}
fn total_cmp_f64(a: f64, b: f64) -> std::cmp::Ordering {
a.total_cmp(&b)
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IndexDefinition {
pub name: String,
pub label_id: u32,
pub prop_key_id: u32,
}
#[derive(Debug, Clone, Default)]
pub struct PropertyIndex {
tree: BTreeMap<PropertyValueKey, Vec<NodeId>>,
}
impl PropertyIndex {
pub fn new() -> Self {
Self {
tree: BTreeMap::new(),
}
}
pub fn insert(&mut self, value: &PropertyValue, node_id: NodeId) {
let key = PropertyValueKey(value.clone());
self.tree.entry(key).or_default().push(node_id);
}
pub fn remove(&mut self, value: &PropertyValue, node_id: NodeId) {
let key = PropertyValueKey(value.clone());
if let Some(ids) = self.tree.get_mut(&key) {
ids.retain(|id| *id != node_id);
if ids.is_empty() {
self.tree.remove(&key);
}
}
}
pub fn lookup(&self, value: &PropertyValue) -> Vec<NodeId> {
let key = PropertyValueKey(value.clone());
self.tree.get(&key).cloned().unwrap_or_default()
}
pub fn range(&self, min: &PropertyValue, max: &PropertyValue) -> Vec<NodeId> {
let min_key = PropertyValueKey(min.clone());
let max_key = PropertyValueKey(max.clone());
let mut result = Vec::new();
for (_key, ids) in self.tree.range(min_key..=max_key) {
result.extend(ids);
}
result
}
}
#[derive(Debug, Clone, Default)]
pub struct IndexManager {
indexes: HashMap<String, (IndexDefinition, PropertyIndex)>,
}
impl IndexManager {
pub fn new() -> Self {
Self {
indexes: HashMap::new(),
}
}
pub fn create_index(
&mut self,
name: String,
label_id: u32,
prop_key_id: u32,
) -> cypherlite_core::Result<()> {
if self.indexes.contains_key(&name) {
return Err(cypherlite_core::CypherLiteError::ConstraintViolation(
format!("index '{}' already exists", name),
));
}
let def = IndexDefinition {
name: name.clone(),
label_id,
prop_key_id,
};
self.indexes.insert(name, (def, PropertyIndex::new()));
Ok(())
}
pub fn drop_index(&mut self, name: &str) -> cypherlite_core::Result<()> {
if self.indexes.remove(name).is_none() {
return Err(cypherlite_core::CypherLiteError::ConstraintViolation(
format!("index '{}' does not exist", name),
));
}
Ok(())
}
pub fn find_index(&self, label_id: u32, prop_key_id: u32) -> Option<&PropertyIndex> {
self.indexes
.values()
.find(|(def, _)| def.label_id == label_id && def.prop_key_id == prop_key_id)
.map(|(_, idx)| idx)
}
pub fn find_index_mut(
&mut self,
label_id: u32,
prop_key_id: u32,
) -> Option<&mut PropertyIndex> {
self.indexes
.values_mut()
.find(|(def, _)| def.label_id == label_id && def.prop_key_id == prop_key_id)
.map(|(_, idx)| idx)
}
pub fn definitions(&self) -> Vec<&IndexDefinition> {
self.indexes.values().map(|(def, _)| def).collect()
}
pub fn get_index_mut(&mut self, name: &str) -> Option<&mut PropertyIndex> {
self.indexes.get_mut(name).map(|(_, idx)| idx)
}
pub fn iter(&self) -> impl Iterator<Item = (&IndexDefinition, &PropertyIndex)> {
self.indexes.values().map(|(def, idx)| (def, idx))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&IndexDefinition, &mut PropertyIndex)> {
self.indexes
.values_mut()
.map(|(def, idx)| (def as &IndexDefinition, idx))
}
}
#[cfg(test)]
mod tests {
use super::*;
use cypherlite_core::PropertyValue;
#[test]
fn test_index_definition_creation() {
let def = IndexDefinition {
name: "idx_person_name".to_string(),
label_id: 0,
prop_key_id: 1,
};
assert_eq!(def.name, "idx_person_name");
assert_eq!(def.label_id, 0);
assert_eq!(def.prop_key_id, 1);
}
#[test]
fn test_index_definition_serde_roundtrip() {
let def = IndexDefinition {
name: "idx_test".to_string(),
label_id: 5,
prop_key_id: 10,
};
let bytes = bincode::serialize(&def).expect("serialize");
let loaded: IndexDefinition = bincode::deserialize(&bytes).expect("deserialize");
assert_eq!(def, loaded);
}
#[test]
fn test_index_manager_empty() {
let mgr = IndexManager::new();
assert!(mgr.definitions().is_empty());
}
#[test]
fn test_property_value_key_ord_null() {
let a = PropertyValueKey(PropertyValue::Null);
let b = PropertyValueKey(PropertyValue::Null);
assert_eq!(a.cmp(&b), std::cmp::Ordering::Equal);
}
#[test]
fn test_property_value_key_ord_int64() {
let a = PropertyValueKey(PropertyValue::Int64(1));
let b = PropertyValueKey(PropertyValue::Int64(2));
assert!(a < b);
}
#[test]
fn test_property_value_key_ord_float64_nan() {
let a = PropertyValueKey(PropertyValue::Float64(f64::NAN));
let b = PropertyValueKey(PropertyValue::Float64(f64::NAN));
assert_eq!(a.cmp(&b), std::cmp::Ordering::Equal);
}
#[test]
fn test_property_value_key_ord_cross_type() {
let null_k = PropertyValueKey(PropertyValue::Null);
let bool_k = PropertyValueKey(PropertyValue::Bool(true));
let int_k = PropertyValueKey(PropertyValue::Int64(0));
let float_k = PropertyValueKey(PropertyValue::Float64(0.0));
let str_k = PropertyValueKey(PropertyValue::String("a".into()));
assert!(null_k < bool_k);
assert!(bool_k < int_k);
assert!(int_k < float_k);
assert!(float_k < str_k);
}
#[test]
fn test_property_value_key_ord_string() {
let a = PropertyValueKey(PropertyValue::String("alpha".into()));
let b = PropertyValueKey(PropertyValue::String("beta".into()));
assert!(a < b);
}
#[test]
fn test_property_index_insert_and_lookup() {
let mut idx = PropertyIndex::new();
let val = PropertyValue::String("Alice".into());
idx.insert(&val, NodeId(1));
idx.insert(&val, NodeId(2));
let result = idx.lookup(&val);
assert_eq!(result, vec![NodeId(1), NodeId(2)]);
}
#[test]
fn test_property_index_lookup_empty() {
let idx = PropertyIndex::new();
let result = idx.lookup(&PropertyValue::Int64(42));
assert!(result.is_empty());
}
#[test]
fn test_property_index_remove() {
let mut idx = PropertyIndex::new();
let val = PropertyValue::Int64(100);
idx.insert(&val, NodeId(1));
idx.insert(&val, NodeId(2));
idx.remove(&val, NodeId(1));
let result = idx.lookup(&val);
assert_eq!(result, vec![NodeId(2)]);
}
#[test]
fn test_property_index_remove_last_cleans_entry() {
let mut idx = PropertyIndex::new();
let val = PropertyValue::Int64(100);
idx.insert(&val, NodeId(1));
idx.remove(&val, NodeId(1));
let result = idx.lookup(&val);
assert!(result.is_empty());
assert!(idx.tree.is_empty());
}
#[test]
fn test_property_index_range_query() {
let mut idx = PropertyIndex::new();
for i in 1..=10 {
idx.insert(&PropertyValue::Int64(i), NodeId(i as u64));
}
let result = idx.range(&PropertyValue::Int64(3), &PropertyValue::Int64(7));
let mut ids: Vec<u64> = result.iter().map(|n| n.0).collect();
ids.sort();
assert_eq!(ids, vec![3, 4, 5, 6, 7]);
}
#[test]
fn test_property_index_range_empty() {
let mut idx = PropertyIndex::new();
idx.insert(&PropertyValue::Int64(1), NodeId(1));
idx.insert(&PropertyValue::Int64(10), NodeId(10));
let result = idx.range(&PropertyValue::Int64(5), &PropertyValue::Int64(8));
assert!(result.is_empty());
}
#[test]
fn test_property_index_range_single_value() {
let mut idx = PropertyIndex::new();
idx.insert(&PropertyValue::Int64(5), NodeId(1));
idx.insert(&PropertyValue::Int64(5), NodeId(2));
let result = idx.range(&PropertyValue::Int64(5), &PropertyValue::Int64(5));
assert_eq!(result.len(), 2);
}
#[test]
fn test_index_manager_create_index() {
let mut mgr = IndexManager::new();
mgr.create_index("idx_person_name".to_string(), 0, 1)
.expect("create");
assert_eq!(mgr.definitions().len(), 1);
assert_eq!(mgr.definitions()[0].name, "idx_person_name");
}
#[test]
fn test_index_manager_create_duplicate_fails() {
let mut mgr = IndexManager::new();
mgr.create_index("idx_test".to_string(), 0, 1)
.expect("create");
let result = mgr.create_index("idx_test".to_string(), 0, 1);
assert!(result.is_err());
}
#[test]
fn test_index_manager_drop_index() {
let mut mgr = IndexManager::new();
mgr.create_index("idx_test".to_string(), 0, 1)
.expect("create");
mgr.drop_index("idx_test").expect("drop");
assert!(mgr.definitions().is_empty());
}
#[test]
fn test_index_manager_drop_nonexistent_fails() {
let mut mgr = IndexManager::new();
let result = mgr.drop_index("nonexistent");
assert!(result.is_err());
}
#[test]
fn test_index_manager_find_index() {
let mut mgr = IndexManager::new();
mgr.create_index("idx_test".to_string(), 0, 1)
.expect("create");
let idx = mgr.find_index_mut(0, 1).expect("should find");
idx.insert(&PropertyValue::String("Alice".into()), NodeId(1));
let idx_ref = mgr.find_index(0, 1).expect("should find");
let result = idx_ref.lookup(&PropertyValue::String("Alice".into()));
assert_eq!(result, vec![NodeId(1)]);
}
#[test]
fn test_index_manager_find_index_not_found() {
let mgr = IndexManager::new();
assert!(mgr.find_index(99, 99).is_none());
}
#[test]
fn test_property_value_key_ord_datetime() {
let a = PropertyValueKey(PropertyValue::DateTime(1_000));
let b = PropertyValueKey(PropertyValue::DateTime(2_000));
assert!(a < b);
assert_eq!(
PropertyValueKey(PropertyValue::DateTime(1_000))
.cmp(&PropertyValueKey(PropertyValue::DateTime(1_000))),
std::cmp::Ordering::Equal
);
}
#[test]
fn test_property_value_key_datetime_after_array() {
let array_k = PropertyValueKey(PropertyValue::Array(vec![]));
let dt_k = PropertyValueKey(PropertyValue::DateTime(0));
assert!(array_k < dt_k);
}
#[test]
fn test_property_index_datetime_range_query() {
let mut idx = PropertyIndex::new();
for i in 1..=5 {
idx.insert(&PropertyValue::DateTime(i * 1_000_000), NodeId(i as u64));
}
let result = idx.range(
&PropertyValue::DateTime(2_000_000),
&PropertyValue::DateTime(4_000_000),
);
let mut ids: Vec<u64> = result.iter().map(|n| n.0).collect();
ids.sort();
assert_eq!(ids, vec![2, 3, 4]);
}
}