grafeo_core/graph/lpg/store/
index.rs1use super::LpgStore;
4use dashmap::DashMap;
5use grafeo_common::types::{HashableValue, NodeId, PropertyKey, Value};
6use grafeo_common::utils::hash::FxHashSet;
7#[cfg(any(feature = "vector-index", feature = "text-index"))]
8use parking_lot::RwLock;
9#[cfg(any(feature = "vector-index", feature = "text-index"))]
10use std::sync::Arc;
11
12#[cfg(feature = "vector-index")]
13use crate::index::vector::HnswIndex;
14
15impl LpgStore {
16 pub fn create_property_index(&self, property: &str) {
42 let key = PropertyKey::new(property);
43
44 let mut indexes = self.property_indexes.write();
45 if indexes.contains_key(&key) {
46 return; }
48
49 let index: DashMap<HashableValue, FxHashSet<NodeId>> = DashMap::new();
51
52 for node_id in self.node_ids() {
54 if let Some(value) = self.node_properties.get(node_id, &key) {
55 let hv = HashableValue::new(value);
56 index.entry(hv).or_default().insert(node_id);
57 }
58 }
59
60 indexes.insert(key, index);
61 }
62
63 pub fn drop_property_index(&self, property: &str) -> bool {
67 let key = PropertyKey::new(property);
68 self.property_indexes.write().remove(&key).is_some()
69 }
70
71 #[must_use]
73 pub fn has_property_index(&self, property: &str) -> bool {
74 let key = PropertyKey::new(property);
75 self.property_indexes.read().contains_key(&key)
76 }
77
78 #[must_use]
80 pub fn property_index_keys(&self) -> Vec<String> {
81 self.property_indexes
82 .read()
83 .keys()
84 .map(|k| k.to_string())
85 .collect()
86 }
87
88 pub(super) fn update_property_index_on_set(
90 &self,
91 node_id: NodeId,
92 key: &PropertyKey,
93 new_value: &Value,
94 ) {
95 let indexes = self.property_indexes.read();
96 if let Some(index) = indexes.get(key) {
97 if let Some(old_value) = self.node_properties.get(node_id, key) {
99 let old_hv = HashableValue::new(old_value);
100 if let Some(mut nodes) = index.get_mut(&old_hv) {
101 nodes.remove(&node_id);
102 if nodes.is_empty() {
103 drop(nodes);
104 index.remove(&old_hv);
105 }
106 }
107 }
108
109 let new_hv = HashableValue::new(new_value.clone());
111 index
112 .entry(new_hv)
113 .or_insert_with(FxHashSet::default)
114 .insert(node_id);
115 }
116 }
117
118 pub(super) fn update_property_index_on_remove(&self, node_id: NodeId, key: &PropertyKey) {
120 let indexes = self.property_indexes.read();
121 if let Some(index) = indexes.get(key) {
122 if let Some(old_value) = self.node_properties.get(node_id, key) {
124 let old_hv = HashableValue::new(old_value);
125 if let Some(mut nodes) = index.get_mut(&old_hv) {
126 nodes.remove(&node_id);
127 if nodes.is_empty() {
128 drop(nodes);
129 index.remove(&old_hv);
130 }
131 }
132 }
133 }
134 }
135
136 #[cfg(feature = "vector-index")]
138 pub fn add_vector_index(&self, label: &str, property: &str, index: Arc<HnswIndex>) {
139 let key = format!("{label}:{property}");
140 self.vector_indexes.write().insert(key, index);
141 }
142
143 #[cfg(feature = "vector-index")]
145 #[must_use]
146 pub fn get_vector_index(&self, label: &str, property: &str) -> Option<Arc<HnswIndex>> {
147 let key = format!("{label}:{property}");
148 self.vector_indexes.read().get(&key).cloned()
149 }
150
151 #[cfg(feature = "vector-index")]
155 pub fn remove_vector_index(&self, label: &str, property: &str) -> bool {
156 let key = format!("{label}:{property}");
157 self.vector_indexes.write().remove(&key).is_some()
158 }
159
160 #[cfg(feature = "vector-index")]
164 #[must_use]
165 pub fn vector_index_entries(&self) -> Vec<(String, Arc<HnswIndex>)> {
166 self.vector_indexes
167 .read()
168 .iter()
169 .map(|(k, v)| (k.clone(), v.clone()))
170 .collect()
171 }
172
173 #[cfg(feature = "text-index")]
175 pub fn add_text_index(
176 &self,
177 label: &str,
178 property: &str,
179 index: Arc<RwLock<crate::index::text::InvertedIndex>>,
180 ) {
181 let key = format!("{label}:{property}");
182 self.text_indexes.write().insert(key, index);
183 }
184
185 #[cfg(feature = "text-index")]
187 #[must_use]
188 pub fn get_text_index(
189 &self,
190 label: &str,
191 property: &str,
192 ) -> Option<Arc<RwLock<crate::index::text::InvertedIndex>>> {
193 let key = format!("{label}:{property}");
194 self.text_indexes.read().get(&key).cloned()
195 }
196
197 #[cfg(feature = "text-index")]
201 pub fn remove_text_index(&self, label: &str, property: &str) -> bool {
202 let key = format!("{label}:{property}");
203 self.text_indexes.write().remove(&key).is_some()
204 }
205
206 #[cfg(feature = "text-index")]
210 pub fn text_index_entries(
211 &self,
212 ) -> Vec<(String, Arc<RwLock<crate::index::text::InvertedIndex>>)> {
213 self.text_indexes
214 .read()
215 .iter()
216 .map(|(k, v)| (k.clone(), v.clone()))
217 .collect()
218 }
219
220 #[cfg(feature = "text-index")]
225 pub(super) fn update_text_index_on_set(&self, id: NodeId, key: &str, value: &Value) {
226 let text_indexes = self.text_indexes.read();
227 if text_indexes.is_empty() {
228 return;
229 }
230 let id_to_label = self.id_to_label.read();
231 let node_labels = self.node_labels.read();
232 #[cfg(not(feature = "temporal"))]
233 let label_set = node_labels.get(&id);
234 #[cfg(feature = "temporal")]
235 let label_set = node_labels.get(&id).and_then(|log| log.latest());
236 if let Some(label_ids) = label_set {
237 for &label_id in label_ids {
238 if let Some(label_name) = id_to_label.get(label_id as usize) {
239 let index_key = format!("{label_name}:{key}");
240 if let Some(index) = text_indexes.get(&index_key) {
241 let mut idx = index.write();
242 idx.remove(id);
244 if let Value::String(text) = value {
245 idx.insert(id, text);
246 }
247 }
248 }
249 }
250 }
251 }
252
253 #[cfg(feature = "text-index")]
255 pub(super) fn update_text_index_on_remove(&self, id: NodeId, key: &str) {
256 let text_indexes = self.text_indexes.read();
257 if text_indexes.is_empty() {
258 return;
259 }
260 let id_to_label = self.id_to_label.read();
261 let node_labels = self.node_labels.read();
262 #[cfg(not(feature = "temporal"))]
263 let label_set = node_labels.get(&id);
264 #[cfg(feature = "temporal")]
265 let label_set = node_labels.get(&id).and_then(|log| log.latest());
266 if let Some(label_ids) = label_set {
267 for &label_id in label_ids {
268 if let Some(label_name) = id_to_label.get(label_id as usize) {
269 let index_key = format!("{label_name}:{key}");
270 if let Some(index) = text_indexes.get(&index_key) {
271 index.write().remove(id);
272 }
273 }
274 }
275 }
276 }
277
278 #[cfg(feature = "text-index")]
280 pub(super) fn remove_from_all_text_indexes(&self, id: NodeId) {
281 let text_indexes = self.text_indexes.read();
282 if text_indexes.is_empty() {
283 return;
284 }
285 for (_, index) in text_indexes.iter() {
286 index.write().remove(id);
287 }
288 }
289}