#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
mod advisor;
mod composite;
mod range;
pub(crate) use advisor::PredicateType;
#[allow(unused_imports)] pub use advisor::{IndexAdvisor, IndexSuggestion, PatternStats, QueryPattern, QueryPatternTracker};
#[allow(unused_imports)]
pub(crate) use composite::CompositeIndexType;
#[allow(unused_imports)] pub use composite::{CompositeGraphIndex, CompositeIndexManager};
#[allow(unused_imports)]
pub use range::{CompositeRangeIndex, EdgePropertyIndex, IndexIntersection, OrderedValue};
use super::helpers::{make_label_prop_key, safe_bitmap_id, PostcardPersistence};
use roaring::RoaringBitmap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
pub const PROPERTY_INDEX_VERSION: u32 = 1;
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PropertyIndex {
#[serde(default = "default_version")]
version: u32,
indexes: HashMap<(String, String), HashMap<String, RoaringBitmap>>,
}
fn default_version() -> u32 {
PROPERTY_INDEX_VERSION
}
impl PropertyIndex {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn create_index(&mut self, label: &str, property: &str) {
self.indexes
.entry(make_label_prop_key(label, property))
.or_default();
}
#[must_use]
pub fn has_index(&self, label: &str, property: &str) -> bool {
self.indexes
.contains_key(&make_label_prop_key(label, property))
}
pub fn insert(&mut self, label: &str, property: &str, value: &Value, node_id: u64) -> bool {
let Some(safe_id) = safe_bitmap_id(node_id) else {
tracing::warn!(
node_id = node_id,
label = label,
property = property,
"PropertyIndex: node_id exceeds u32::MAX ({}), cannot index. \
RoaringBitmap only supports u32 IDs.",
u32::MAX
);
return false;
};
let key = make_label_prop_key(label, property);
if let Some(value_map) = self.indexes.get_mut(&key) {
let value_key = value.to_string();
value_map.entry(value_key).or_default().insert(safe_id);
true
} else {
false
}
}
pub fn remove(&mut self, label: &str, property: &str, value: &Value, node_id: u64) -> bool {
let Some(safe_id) = safe_bitmap_id(node_id) else {
return false;
};
let key = make_label_prop_key(label, property);
if let Some(value_map) = self.indexes.get_mut(&key) {
let value_key = value.to_string();
if let Some(bitmap) = value_map.get_mut(&value_key) {
let removed = bitmap.remove(safe_id);
if bitmap.is_empty() {
value_map.remove(&value_key);
}
return removed;
}
}
false
}
#[must_use]
pub fn lookup(&self, label: &str, property: &str, value: &Value) -> Option<&RoaringBitmap> {
let key = make_label_prop_key(label, property);
self.indexes
.get(&key)
.and_then(|value_map| value_map.get(&value.to_string()))
}
#[must_use]
pub fn indexed_properties(&self) -> Vec<(String, String)> {
self.indexes.keys().cloned().collect()
}
#[must_use]
pub fn cardinality(&self, label: &str, property: &str) -> Option<usize> {
let key = make_label_prop_key(label, property);
self.indexes.get(&key).map(HashMap::len)
}
pub fn drop_index(&mut self, label: &str, property: &str) -> bool {
self.indexes
.remove(&make_label_prop_key(label, property))
.is_some()
}
pub fn clear(&mut self) {
self.indexes.clear();
}
#[must_use]
pub fn memory_usage(&self) -> usize {
let mut total = std::mem::size_of::<Self>();
for ((label, prop), value_map) in &self.indexes {
total += label.len() + prop.len();
for (value_key, bitmap) in value_map {
total += value_key.len();
total += bitmap.serialized_size();
}
}
total
}
pub fn on_add_node(&mut self, label: &str, node_id: u64, properties: &HashMap<String, Value>) {
for (prop_name, value) in properties {
if self.has_index(label, prop_name) {
self.insert(label, prop_name, value, node_id);
}
}
}
pub fn on_remove_node(
&mut self,
label: &str,
node_id: u64,
properties: &HashMap<String, Value>,
) {
for (prop_name, value) in properties {
if self.has_index(label, prop_name) {
self.remove(label, prop_name, value, node_id);
}
}
}
pub fn on_update_property(
&mut self,
label: &str,
node_id: u64,
property: &str,
old_value: &Value,
new_value: &Value,
) {
if self.has_index(label, property) {
self.remove(label, property, old_value, node_id);
self.insert(label, property, new_value, node_id);
}
}
pub fn index_node(&mut self, label: &str, node_id: u64, properties: &HashMap<String, Value>) {
self.on_add_node(label, node_id, properties);
}
}
impl PostcardPersistence for PropertyIndex {}
impl PropertyIndex {
pub fn to_bytes(&self) -> Result<Vec<u8>, postcard::Error> {
<Self as PostcardPersistence>::to_bytes(self)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, postcard::Error> {
<Self as PostcardPersistence>::from_bytes(bytes)
}
pub fn save_to_file(&self, path: &std::path::Path) -> std::io::Result<()> {
<Self as PostcardPersistence>::save_to_file(self, path)
}
pub fn load_from_file(path: &std::path::Path) -> std::io::Result<Self> {
<Self as PostcardPersistence>::load_from_file(path)
}
}