use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CompositeIndexType {
Hash,
Range,
}
#[allow(dead_code)]
#[derive(Debug, Serialize, Deserialize)]
pub struct CompositeGraphIndex {
label: String,
properties: Vec<String>,
index_type: CompositeIndexType,
hash_index: HashMap<u64, Vec<u64>>,
}
#[allow(dead_code)]
impl CompositeGraphIndex {
#[must_use]
pub fn new(
label: impl Into<String>,
properties: Vec<String>,
index_type: CompositeIndexType,
) -> Self {
Self {
label: label.into(),
properties,
index_type,
hash_index: HashMap::new(),
}
}
#[must_use]
pub fn label(&self) -> &str {
&self.label
}
#[must_use]
pub fn properties(&self) -> &[String] {
&self.properties
}
#[must_use]
pub fn index_type(&self) -> CompositeIndexType {
self.index_type
}
#[must_use]
pub fn covers(&self, label: &str, properties: &[&str]) -> bool {
if self.label != label {
return false;
}
properties
.iter()
.all(|p| self.properties.iter().any(|ip| ip == *p))
}
pub fn insert(&mut self, node_id: u64, values: &[Value]) {
if values.len() != self.properties.len() {
tracing::warn!(
"CompositeGraphIndex: value count ({}) != property count ({})",
values.len(),
self.properties.len()
);
return;
}
let hash = Self::hash_values(values);
self.hash_index.entry(hash).or_default().push(node_id);
}
pub fn remove(&mut self, node_id: u64, values: &[Value]) -> bool {
if values.len() != self.properties.len() {
return false;
}
let hash = Self::hash_values(values);
if let Some(nodes) = self.hash_index.get_mut(&hash) {
if let Some(pos) = nodes.iter().position(|&id| id == node_id) {
nodes.swap_remove(pos);
if nodes.is_empty() {
self.hash_index.remove(&hash);
}
return true;
}
}
false
}
#[must_use]
pub fn lookup(&self, values: &[Value]) -> &[u64] {
if values.len() != self.properties.len() {
return &[];
}
let hash = Self::hash_values(values);
self.hash_index.get(&hash).map_or(&[], Vec::as_slice)
}
#[must_use]
pub fn cardinality(&self) -> usize {
self.hash_index.len()
}
#[must_use]
pub fn node_count(&self) -> usize {
self.hash_index.values().map(Vec::len).sum()
}
pub fn clear(&mut self) {
self.hash_index.clear();
}
fn hash_values(values: &[Value]) -> u64 {
let mut hasher = DefaultHasher::new();
for value in values {
value.to_string().hash(&mut hasher);
}
hasher.finish()
}
#[must_use]
pub fn memory_usage(&self) -> usize {
let mut total = std::mem::size_of::<Self>();
total += self.label.len();
for prop in &self.properties {
total += prop.len();
}
for nodes in self.hash_index.values() {
total += 8 + std::mem::size_of::<Vec<u64>>() + nodes.len() * 8;
}
total
}
}
#[allow(dead_code)]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct CompositeIndexManager {
indexes: HashMap<String, CompositeGraphIndex>,
}
#[allow(dead_code)]
impl CompositeIndexManager {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn create_index(
&mut self,
name: impl Into<String>,
label: impl Into<String>,
properties: Vec<String>,
index_type: CompositeIndexType,
) -> bool {
let name = name.into();
if self.indexes.contains_key(&name) {
return false;
}
let index = CompositeGraphIndex::new(label, properties, index_type);
self.indexes.insert(name, index);
true
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&CompositeGraphIndex> {
self.indexes.get(name)
}
pub fn get_mut(&mut self, name: &str) -> Option<&mut CompositeGraphIndex> {
self.indexes.get_mut(name)
}
pub fn drop_index(&mut self, name: &str) -> bool {
self.indexes.remove(name).is_some()
}
#[must_use]
pub fn find_covering_indexes(&self, label: &str, properties: &[&str]) -> Vec<&str> {
self.indexes
.iter()
.filter(|(_, idx)| idx.covers(label, properties))
.map(|(name, _)| name.as_str())
.collect()
}
#[must_use]
pub fn list_indexes(&self) -> Vec<&str> {
self.indexes.keys().map(String::as_str).collect()
}
fn for_each_matching_index(
&mut self,
label: &str,
node_id: u64,
properties: &HashMap<String, Value>,
mut apply: impl FnMut(&mut CompositeGraphIndex, u64, &[Value]),
) {
for index in self.indexes.values_mut() {
if index.label() == label {
let values: Vec<Value> = index
.properties()
.iter()
.map(|p| properties.get(p).cloned().unwrap_or(Value::Null))
.collect();
apply(index, node_id, &values);
}
}
}
pub fn on_add_node(&mut self, label: &str, node_id: u64, properties: &HashMap<String, Value>) {
self.for_each_matching_index(label, node_id, properties, |idx, id, vals| {
idx.insert(id, vals);
});
}
pub fn on_remove_node(
&mut self,
label: &str,
node_id: u64,
properties: &HashMap<String, Value>,
) {
self.for_each_matching_index(label, node_id, properties, |idx, id, vals| {
idx.remove(id, vals);
});
}
}