use super::LpgStore;
use dashmap::DashMap;
use grafeo_common::types::{HashableValue, NodeId, PropertyKey, Value};
use grafeo_common::utils::hash::FxHashSet;
#[cfg(any(feature = "vector-index", feature = "text-index"))]
use parking_lot::RwLock;
#[cfg(any(feature = "vector-index", feature = "text-index"))]
use std::sync::Arc;
#[cfg(feature = "vector-index")]
use crate::index::vector::HnswIndex;
impl LpgStore {
pub fn create_property_index(&self, property: &str) {
let key = PropertyKey::new(property);
let mut indexes = self.property_indexes.write();
if indexes.contains_key(&key) {
return; }
let index: DashMap<HashableValue, FxHashSet<NodeId>> = DashMap::new();
for node_id in self.node_ids() {
if let Some(value) = self.node_properties.get(node_id, &key) {
let hv = HashableValue::new(value);
index.entry(hv).or_default().insert(node_id);
}
}
indexes.insert(key, index);
}
pub fn drop_property_index(&self, property: &str) -> bool {
let key = PropertyKey::new(property);
self.property_indexes.write().remove(&key).is_some()
}
#[must_use]
pub fn has_property_index(&self, property: &str) -> bool {
let key = PropertyKey::new(property);
self.property_indexes.read().contains_key(&key)
}
#[must_use]
pub fn property_index_keys(&self) -> Vec<String> {
self.property_indexes
.read()
.keys()
.map(|k| k.to_string())
.collect()
}
pub(super) fn update_property_index_on_set(
&self,
node_id: NodeId,
key: &PropertyKey,
new_value: &Value,
) {
let indexes = self.property_indexes.read();
if let Some(index) = indexes.get(key) {
if let Some(old_value) = self.node_properties.get(node_id, key) {
let old_hv = HashableValue::new(old_value);
if let Some(mut nodes) = index.get_mut(&old_hv) {
nodes.remove(&node_id);
if nodes.is_empty() {
drop(nodes);
index.remove(&old_hv);
}
}
}
let new_hv = HashableValue::new(new_value.clone());
index
.entry(new_hv)
.or_insert_with(FxHashSet::default)
.insert(node_id);
}
}
pub(super) fn update_property_index_on_remove(&self, node_id: NodeId, key: &PropertyKey) {
let indexes = self.property_indexes.read();
if let Some(index) = indexes.get(key) {
if let Some(old_value) = self.node_properties.get(node_id, key) {
let old_hv = HashableValue::new(old_value);
if let Some(mut nodes) = index.get_mut(&old_hv) {
nodes.remove(&node_id);
if nodes.is_empty() {
drop(nodes);
index.remove(&old_hv);
}
}
}
}
}
#[cfg(feature = "vector-index")]
pub fn add_vector_index(&self, label: &str, property: &str, index: Arc<HnswIndex>) {
let key = format!("{label}:{property}");
self.vector_indexes.write().insert(key, index);
}
#[cfg(feature = "vector-index")]
#[must_use]
pub fn get_vector_index(&self, label: &str, property: &str) -> Option<Arc<HnswIndex>> {
let key = format!("{label}:{property}");
self.vector_indexes.read().get(&key).cloned()
}
#[cfg(feature = "vector-index")]
pub fn remove_vector_index(&self, label: &str, property: &str) -> bool {
let key = format!("{label}:{property}");
self.vector_indexes.write().remove(&key).is_some()
}
#[cfg(feature = "vector-index")]
#[must_use]
pub fn vector_index_entries(&self) -> Vec<(String, Arc<HnswIndex>)> {
self.vector_indexes
.read()
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
#[cfg(feature = "text-index")]
pub fn add_text_index(
&self,
label: &str,
property: &str,
index: Arc<RwLock<crate::index::text::InvertedIndex>>,
) {
let key = format!("{label}:{property}");
self.text_indexes.write().insert(key, index);
}
#[cfg(feature = "text-index")]
#[must_use]
pub fn get_text_index(
&self,
label: &str,
property: &str,
) -> Option<Arc<RwLock<crate::index::text::InvertedIndex>>> {
let key = format!("{label}:{property}");
self.text_indexes.read().get(&key).cloned()
}
#[cfg(feature = "text-index")]
pub fn remove_text_index(&self, label: &str, property: &str) -> bool {
let key = format!("{label}:{property}");
self.text_indexes.write().remove(&key).is_some()
}
#[cfg(feature = "text-index")]
pub fn text_index_entries(
&self,
) -> Vec<(String, Arc<RwLock<crate::index::text::InvertedIndex>>)> {
self.text_indexes
.read()
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
#[cfg(feature = "text-index")]
pub(super) fn update_text_index_on_set(&self, id: NodeId, key: &str, value: &Value) {
let text_indexes = self.text_indexes.read();
if text_indexes.is_empty() {
return;
}
let id_to_label = self.id_to_label.read();
let node_labels = self.node_labels.read();
#[cfg(not(feature = "temporal"))]
let label_set = node_labels.get(&id);
#[cfg(feature = "temporal")]
let label_set = node_labels.get(&id).and_then(|log| log.latest());
if let Some(label_ids) = label_set {
for &label_id in label_ids {
if let Some(label_name) = id_to_label.get(label_id as usize) {
let index_key = format!("{label_name}:{key}");
if let Some(index) = text_indexes.get(&index_key) {
let mut idx = index.write();
idx.remove(id);
if let Value::String(text) = value {
idx.insert(id, text);
}
}
}
}
}
}
#[cfg(feature = "text-index")]
pub(super) fn update_text_index_on_remove(&self, id: NodeId, key: &str) {
let text_indexes = self.text_indexes.read();
if text_indexes.is_empty() {
return;
}
let id_to_label = self.id_to_label.read();
let node_labels = self.node_labels.read();
#[cfg(not(feature = "temporal"))]
let label_set = node_labels.get(&id);
#[cfg(feature = "temporal")]
let label_set = node_labels.get(&id).and_then(|log| log.latest());
if let Some(label_ids) = label_set {
for &label_id in label_ids {
if let Some(label_name) = id_to_label.get(label_id as usize) {
let index_key = format!("{label_name}:{key}");
if let Some(index) = text_indexes.get(&index_key) {
index.write().remove(id);
}
}
}
}
}
#[cfg(feature = "text-index")]
pub(super) fn remove_from_all_text_indexes(&self, id: NodeId) {
let text_indexes = self.text_indexes.read();
if text_indexes.is_empty() {
return;
}
for (_, index) in text_indexes.iter() {
index.write().remove(id);
}
}
}