1use super::*;
2
3impl Graph {
4 pub fn nodes_by_label(&self, label: &str) -> Result<Vec<NodeId>, Error> {
10 let rtxn = self.storage.env.read_txn()?;
11 self.nodes_by_label_impl(&rtxn, label)
12 }
13
14 pub(super) fn nodes_by_label_impl(
15 &self,
16 rtxn: &heed::RoTxn,
17 label: &str,
18 ) -> Result<Vec<NodeId>, Error> {
19 let label_id = {
20 let key = format!("label:{label}");
21 match self.storage.meta.get(rtxn, &key)? {
22 Some(b) => {
23 let arr: [u8; 4] = b
24 .try_into()
25 .map_err(|_| Error::Corrupt("label id must be 4 bytes"))?;
26 u32::from_be_bytes(arr)
27 }
28 None => return Ok(vec![]),
29 }
30 };
31 let prefix = label_id.to_be_bytes();
32 let iter = self.storage.label_idx.prefix_iter(rtxn, &prefix)?;
33 let mut ids = Vec::new();
34 for result in iter {
35 let (key, _) = result?;
36 let id_bytes: [u8; 8] = key[4..]
37 .try_into()
38 .map_err(|_| Error::Corrupt("label_idx key has wrong length"))?;
39 ids.push(u64::from_be_bytes(id_bytes));
40 }
41 Ok(ids)
42 }
43
44 #[doc(hidden)]
48 pub fn label_filter(&self, nodes: &[NodeId], label: &str) -> Result<Vec<NodeId>, Error> {
49 let rtxn = self.storage.env.read_txn()?;
50 let label_id = match get_label(&self.storage, &rtxn, label)? {
51 Some(id) => id,
52 None => return Ok(vec![]),
53 };
54 let mut out = Vec::new();
55 for &n in nodes {
56 if self
57 .storage
58 .label_idx
59 .get(&rtxn, &composite_key(label_id, n))?
60 .is_some()
61 {
62 out.push(n);
63 }
64 }
65 Ok(out)
66 }
67
68 pub fn edges_by_type(&self, etype: &str) -> Result<Vec<EdgeId>, Error> {
70 let rtxn = self.storage.env.read_txn()?;
71 self.edges_by_type_impl(&rtxn, etype)
72 }
73
74 pub(super) fn edges_by_type_impl(
75 &self,
76 rtxn: &heed::RoTxn,
77 etype: &str,
78 ) -> Result<Vec<EdgeId>, Error> {
79 let type_id = {
80 let key = format!("type:{etype}");
81 match self.storage.meta.get(rtxn, &key)? {
82 Some(b) => {
83 let arr: [u8; 4] = b
84 .try_into()
85 .map_err(|_| Error::Corrupt("type id must be 4 bytes"))?;
86 u32::from_be_bytes(arr)
87 }
88 None => return Ok(vec![]),
89 }
90 };
91 let prefix = type_id.to_be_bytes();
92 let iter = self.storage.type_idx.prefix_iter(rtxn, &prefix)?;
93 let mut ids = Vec::new();
94 for result in iter {
95 let (key, _) = result?;
96 let id_bytes: [u8; 8] = key[4..]
97 .try_into()
98 .map_err(|_| Error::Corrupt("type_idx key has wrong length"))?;
99 ids.push(u64::from_be_bytes(id_bytes));
100 }
101 Ok(ids)
102 }
103
104 pub fn label_name(&self, id: LabelId) -> Result<Option<String>, Error> {
113 let rtxn = self.storage.env.read_txn()?;
114 self.label_name_impl(&rtxn, id)
115 }
116
117 pub(super) fn label_name_impl(
118 &self,
119 rtxn: &heed::RoTxn,
120 id: LabelId,
121 ) -> Result<Option<String>, Error> {
122 self.meta_reverse_lookup_impl(rtxn, "label:", id)
123 }
124
125 pub fn type_name(&self, id: TypeId) -> Result<Option<String>, Error> {
130 let rtxn = self.storage.env.read_txn()?;
131 self.type_name_impl(&rtxn, id)
132 }
133
134 pub(super) fn type_name_impl(
135 &self,
136 rtxn: &heed::RoTxn,
137 id: TypeId,
138 ) -> Result<Option<String>, Error> {
139 self.meta_reverse_lookup_impl(rtxn, "type:", id)
140 }
141
142 pub(super) fn prop_key_name_impl(
143 &self,
144 rtxn: &heed::RoTxn,
145 id: PropKeyId,
146 ) -> Result<Option<String>, Error> {
147 self.meta_reverse_lookup_impl(rtxn, "prop_key:", id)
148 }
149
150 pub(super) fn write_edge_index_entries(
156 &self,
157 wtxn: &mut heed::RwTxn,
158 edge_id: EdgeId,
159 type_id: TypeId,
160 etype: &str,
161 encoded_props: &[u8],
162 ) -> Result<(), Error> {
163 let active_indexes = self.get_active_edge_indexes(wtxn, type_id)?;
164 if active_indexes.is_empty() {
165 return Ok(());
166 }
167 let props_json: serde_json::Value = props::decode(encoded_props)?;
168 for (prop_key_id, flags) in active_indexes {
169 if let Some(prop_name) = self.prop_key_name_impl(wtxn, prop_key_id)? {
170 let prop_val = props_json.get(&prop_name);
171
172 if flags == 0x02
174 && (prop_val.is_none() || prop_val == Some(&serde_json::Value::Null))
175 {
176 return Err(Error::RequiredConstraintViolation(
177 etype.to_string(),
178 prop_name.to_string(),
179 ));
180 }
181
182 if let Some(val) = prop_val {
183 if val != &serde_json::Value::Null {
184 if let Some(encoded) = encode_property_value(val) {
185 if flags == 0x01 {
187 let mut prefix = Vec::with_capacity(4 + 4 + encoded.len());
188 prefix.extend_from_slice(&type_id.to_be_bytes());
189 prefix.extend_from_slice(&prop_key_id.to_be_bytes());
190 prefix.extend_from_slice(&encoded);
191
192 for entry in
193 self.storage.edge_prop_idx.prefix_iter(wtxn, &prefix)?
194 {
195 let (key, _) = entry?;
196 if key.len() >= 8 {
197 let mut edge_id_bytes = [0u8; 8];
198 edge_id_bytes.copy_from_slice(&key[key.len() - 8..]);
199 let found_edge_id = u64::from_be_bytes(edge_id_bytes);
200 if found_edge_id != edge_id {
201 return Err(Error::UniqueConstraintViolation(
202 etype.to_string(),
203 prop_name.to_string(),
204 val.to_string(),
205 ));
206 }
207 }
208 }
209 }
210
211 let idx_key =
213 edge_prop_index_key(type_id, prop_key_id, &encoded, edge_id);
214 self.storage.edge_prop_idx.put(wtxn, &idx_key, &())?;
215 }
216 }
217 }
218 }
219 }
220 Ok(())
221 }
222
223 pub(super) fn delete_edge_index_entries(
224 &self,
225 wtxn: &mut heed::RwTxn,
226 edge_id: EdgeId,
227 record: &EdgeRecord,
228 ) -> Result<(), Error> {
229 let active_indexes = self.get_active_edge_indexes(wtxn, record.edge_type)?;
230 if !active_indexes.is_empty() {
231 let props_json: serde_json::Value = props::decode(&record.props)?;
232 for (prop_key_id, _) in active_indexes {
233 if let Some(prop_name) = self.prop_key_name_impl(wtxn, prop_key_id)? {
234 if let Some(val) = props_json.get(&prop_name) {
235 if let Some(encoded) = encode_property_value(val) {
236 let idx_key = edge_prop_index_key(
237 record.edge_type,
238 prop_key_id,
239 &encoded,
240 edge_id,
241 );
242 self.storage.edge_prop_idx.delete(wtxn, &idx_key)?;
243 }
244 }
245 }
246 }
247 }
248 Ok(())
249 }
250
251 pub fn node_count_by_label(&self, label: &str) -> Result<u64, Error> {
253 let rtxn = self.storage.env.read_txn()?;
254 self.node_count_by_label_impl(&rtxn, label)
255 }
256
257 pub fn node_count_hint(&self) -> Result<u64, Error> {
262 let rtxn = self.storage.env.read_txn()?;
263 crate::storage::ids::node_high_water(&self.storage, &rtxn)
264 }
265
266 pub(super) fn node_count_by_label_impl(
267 &self,
268 rtxn: &heed::RoTxn,
269 label: &str,
270 ) -> Result<u64, Error> {
271 let meta_key = format!("label:{label}");
272 if let Some(b) = self.storage.meta.get(rtxn, &meta_key)? {
273 let arr: [u8; 4] = b
274 .try_into()
275 .map_err(|_| Error::Corrupt("label id must be 4 bytes"))?;
276 let label_id = u32::from_be_bytes(arr);
277 crate::storage::ids::get_label_count(&self.storage, rtxn, label_id)
278 } else {
279 Ok(0)
280 }
281 }
282
283 pub fn edge_count_by_type(&self, etype: &str) -> Result<u64, Error> {
285 let rtxn = self.storage.env.read_txn()?;
286 self.edge_count_by_type_impl(&rtxn, etype)
287 }
288
289 pub(super) fn edge_count_by_type_impl(
290 &self,
291 rtxn: &heed::RoTxn,
292 etype: &str,
293 ) -> Result<u64, Error> {
294 let meta_key = format!("type:{etype}");
295 if let Some(b) = self.storage.meta.get(rtxn, &meta_key)? {
296 let arr: [u8; 4] = b
297 .try_into()
298 .map_err(|_| Error::Corrupt("type id must be 4 bytes"))?;
299 let type_id = u32::from_be_bytes(arr);
300 crate::storage::ids::get_type_count(&self.storage, rtxn, type_id)
301 } else {
302 Ok(0)
303 }
304 }
305
306 pub(super) fn meta_reverse_lookup_impl(
307 &self,
308 rtxn: &heed::RoTxn,
309 prefix: &str,
310 id: u32,
311 ) -> Result<Option<String>, Error> {
312 for entry in self.storage.meta.iter(rtxn)? {
313 let (key, val) = entry?;
314 if let Some(name) = key.strip_prefix(prefix) {
315 if val.len() == 4 {
316 let stored = u32::from_be_bytes([val[0], val[1], val[2], val[3]]);
317 if stored == id {
318 return Ok(Some(name.to_owned()));
319 }
320 }
321 }
322 }
323 Ok(None)
324 }
325
326 pub(super) fn get_active_node_indexes(
327 &self,
328 rtxn: &heed::RoTxn,
329 label_id: LabelId,
330 ) -> Result<Vec<(PropKeyId, u8)>, Error> {
331 let prefix = format!("idx_meta:node:l:{label_id}:p:");
332 let mut active = Vec::new();
333 for entry in self.storage.meta.prefix_iter(rtxn, &prefix)? {
334 let (key, val) = entry?;
335 if let Some(prop_str) = key.strip_prefix(&prefix) {
336 let prop_key_id: PropKeyId = prop_str
337 .parse()
338 .map_err(|_| Error::Corrupt("prop key id in meta must be integer"))?;
339 let flags = val.first().copied().unwrap_or(0x00);
340 active.push((prop_key_id, flags));
341 }
342 }
343 Ok(active)
344 }
345
346 pub(super) fn get_active_edge_indexes(
347 &self,
348 rtxn: &heed::RoTxn,
349 type_id: TypeId,
350 ) -> Result<Vec<(PropKeyId, u8)>, Error> {
351 let prefix = format!("idx_meta:edge:t:{type_id}:p:");
352 let mut active = Vec::new();
353 for entry in self.storage.meta.prefix_iter(rtxn, &prefix)? {
354 let (key, val) = entry?;
355 if let Some(prop_str) = key.strip_prefix(&prefix) {
356 let prop_key_id: PropKeyId = prop_str
357 .parse()
358 .map_err(|_| Error::Corrupt("prop key id in meta must be integer"))?;
359 let flags = val.first().copied().unwrap_or(0x00);
360 active.push((prop_key_id, flags));
361 }
362 }
363 Ok(active)
364 }
365
366 pub fn create_node_property_index(&self, label: &str, property: &str) -> Result<(), Error> {
367 let _guard = self._write_lock.lock();
368 let mut wtxn = self.storage.env.write_txn()?;
369 self.create_node_index_impl(&mut wtxn, label, property, 0x00)?;
370 wtxn.commit()?;
371 Ok(())
372 }
373
374 pub fn create_node_unique_constraint(&self, label: &str, property: &str) -> Result<(), Error> {
375 let _guard = self._write_lock.lock();
376 let mut wtxn = self.storage.env.write_txn()?;
377 self.create_node_index_impl(&mut wtxn, label, property, 0x01)?;
378 wtxn.commit()?;
379 Ok(())
380 }
381
382 pub fn create_node_required_constraint(
383 &self,
384 label: &str,
385 property: &str,
386 ) -> Result<(), Error> {
387 let _guard = self._write_lock.lock();
388 let mut wtxn = self.storage.env.write_txn()?;
389 self.create_node_index_impl(&mut wtxn, label, property, 0x02)?;
390 wtxn.commit()?;
391 Ok(())
392 }
393
394 pub(super) fn create_node_index_impl(
395 &self,
396 wtxn: &mut heed::RwTxn,
397 label: &str,
398 property: &str,
399 flags: u8,
400 ) -> Result<(), Error> {
401 let label_id = get_or_create_label(&self.storage, wtxn, label)?;
402 let prop_key_id = get_or_create_prop_key(&self.storage, wtxn, property)?;
403 let meta_key = format!("idx_meta:node:l:{label_id}:p:{prop_key_id}");
404
405 if let Some(existing_val) = self.storage.meta.get(wtxn, &meta_key)? {
406 if !existing_val.is_empty() && existing_val[0] == flags {
407 return Ok(());
408 }
409 }
410
411 let node_ids = self.nodes_by_label_impl(wtxn, label)?;
412 let mut seen_values = ahash::AHashSet::new();
413
414 for node_id in &node_ids {
415 let record = self
416 .get_node_impl(wtxn, *node_id)?
417 .ok_or(Error::NodeNotFound(*node_id))?;
418 let props_json: serde_json::Value = props::decode(&record.props)?;
419 let prop_val = props_json.get(property);
420
421 if flags == 0x02 && (prop_val.is_none() || prop_val == Some(&serde_json::Value::Null)) {
422 return Err(Error::RequiredConstraintViolation(
423 label.to_string(),
424 property.to_string(),
425 ));
426 }
427
428 if let Some(val) = prop_val {
429 if flags == 0x01 && !seen_values.insert(val.clone()) {
430 return Err(Error::UniqueConstraintViolation(
431 label.to_string(),
432 property.to_string(),
433 val.to_string(),
434 ));
435 }
436 }
437 }
438
439 self.storage.meta.put(wtxn, &meta_key, &[flags])?;
440
441 for node_id in node_ids {
442 let record = self
443 .get_node_impl(wtxn, node_id)?
444 .ok_or(Error::NodeNotFound(node_id))?;
445 let props_json: serde_json::Value = props::decode(&record.props)?;
446 if let Some(val) = props_json.get(property) {
447 if let Some(encoded) = encode_property_value(val) {
448 let idx_key = node_prop_index_key(label_id, prop_key_id, &encoded, node_id);
449 self.storage.node_prop_idx.put(wtxn, &idx_key, &())?;
450 }
451 }
452 }
453
454 Ok(())
455 }
456
457 pub fn drop_node_property_index(&self, label: &str, property: &str) -> Result<(), Error> {
458 let _guard = self._write_lock.lock();
459 let mut wtxn = self.storage.env.write_txn()?;
460 self.drop_node_index_impl(&mut wtxn, label, property, 0x00)?;
461 wtxn.commit()?;
462 Ok(())
463 }
464
465 pub fn drop_node_unique_constraint(&self, label: &str, property: &str) -> Result<(), Error> {
466 let _guard = self._write_lock.lock();
467 let mut wtxn = self.storage.env.write_txn()?;
468 self.drop_node_index_impl(&mut wtxn, label, property, 0x01)?;
469 wtxn.commit()?;
470 Ok(())
471 }
472
473 pub fn drop_node_required_constraint(&self, label: &str, property: &str) -> Result<(), Error> {
474 let _guard = self._write_lock.lock();
475 let mut wtxn = self.storage.env.write_txn()?;
476 self.drop_node_index_impl(&mut wtxn, label, property, 0x02)?;
477 wtxn.commit()?;
478 Ok(())
479 }
480
481 pub(super) fn drop_node_index_impl(
482 &self,
483 wtxn: &mut heed::RwTxn,
484 label: &str,
485 property: &str,
486 flags: u8,
487 ) -> Result<(), Error> {
488 let label_id = get_or_create_label(&self.storage, wtxn, label)?;
489 let prop_key_id = get_or_create_prop_key(&self.storage, wtxn, property)?;
490 let meta_key = format!("idx_meta:node:l:{label_id}:p:{prop_key_id}");
491
492 if let Some(existing_val) = self.storage.meta.get(wtxn, &meta_key)? {
493 if !existing_val.is_empty() && existing_val[0] == flags {
494 self.storage.meta.delete(wtxn, &meta_key)?;
495
496 let mut prefix = Vec::with_capacity(8);
504 prefix.extend_from_slice(&label_id.to_be_bytes());
505 prefix.extend_from_slice(&prop_key_id.to_be_bytes());
506
507 let mut to_delete = Vec::new();
508 for entry in self.storage.node_prop_idx.prefix_iter(wtxn, &prefix)? {
509 let (key, _) = entry?;
510 if key.len() >= prefix.len() + 8 {
511 let encoded_val = &key[prefix.len()..key.len() - 8];
512 if encoded_val == [crate::graph::ENCODED_NULL].as_slice() {
513 to_delete.push(key.to_vec());
514 }
515 }
516 }
517
518 for key in to_delete {
519 self.storage.node_prop_idx.delete(wtxn, &key)?;
520 }
521 }
522 }
523
524 Ok(())
525 }
526
527 pub fn create_edge_property_index(&self, etype: &str, property: &str) -> Result<(), Error> {
528 let _guard = self._write_lock.lock();
529 let mut wtxn = self.storage.env.write_txn()?;
530 self.create_edge_index_impl(&mut wtxn, etype, property, 0x00)?;
531 wtxn.commit()?;
532 Ok(())
533 }
534
535 pub fn create_edge_unique_constraint(&self, etype: &str, property: &str) -> Result<(), Error> {
536 let _guard = self._write_lock.lock();
537 let mut wtxn = self.storage.env.write_txn()?;
538 self.create_edge_index_impl(&mut wtxn, etype, property, 0x01)?;
539 wtxn.commit()?;
540 Ok(())
541 }
542
543 pub fn create_edge_required_constraint(
544 &self,
545 etype: &str,
546 property: &str,
547 ) -> Result<(), Error> {
548 let _guard = self._write_lock.lock();
549 let mut wtxn = self.storage.env.write_txn()?;
550 self.create_edge_index_impl(&mut wtxn, etype, property, 0x02)?;
551 wtxn.commit()?;
552 Ok(())
553 }
554
555 pub(super) fn create_edge_index_impl(
556 &self,
557 wtxn: &mut heed::RwTxn,
558 etype: &str,
559 property: &str,
560 flags: u8,
561 ) -> Result<(), Error> {
562 let type_id = get_or_create_type(&self.storage, wtxn, etype)?;
563 let prop_key_id = get_or_create_prop_key(&self.storage, wtxn, property)?;
564 let meta_key = format!("idx_meta:edge:t:{type_id}:p:{prop_key_id}");
565
566 if let Some(existing_val) = self.storage.meta.get(wtxn, &meta_key)? {
567 if !existing_val.is_empty() && existing_val[0] == flags {
568 return Ok(());
569 }
570 }
571
572 let edge_ids = self.edges_by_type_impl(wtxn, etype)?;
573 let mut seen_values = ahash::AHashSet::new();
574
575 for edge_id in &edge_ids {
576 let record = self
577 .get_edge_impl(wtxn, *edge_id)?
578 .ok_or(Error::EdgeNotFound(*edge_id))?;
579 let props_json: serde_json::Value = props::decode(&record.props)?;
580 let prop_val = props_json.get(property);
581
582 if flags == 0x02 && (prop_val.is_none() || prop_val == Some(&serde_json::Value::Null)) {
583 return Err(Error::RequiredConstraintViolation(
584 etype.to_string(),
585 property.to_string(),
586 ));
587 }
588
589 if let Some(val) = prop_val {
590 if flags == 0x01 && !seen_values.insert(val.clone()) {
591 return Err(Error::UniqueConstraintViolation(
592 etype.to_string(),
593 property.to_string(),
594 val.to_string(),
595 ));
596 }
597 }
598 }
599
600 self.storage.meta.put(wtxn, &meta_key, &[flags])?;
601
602 for edge_id in edge_ids {
603 let record = self
604 .get_edge_impl(wtxn, edge_id)?
605 .ok_or(Error::EdgeNotFound(edge_id))?;
606 let props_json: serde_json::Value = props::decode(&record.props)?;
607 if let Some(val) = props_json.get(property) {
608 if let Some(encoded) = encode_property_value(val) {
609 let idx_key = edge_prop_index_key(type_id, prop_key_id, &encoded, edge_id);
610 self.storage.edge_prop_idx.put(wtxn, &idx_key, &())?;
611 }
612 }
613 }
614
615 Ok(())
616 }
617
618 pub fn drop_edge_property_index(&self, etype: &str, property: &str) -> Result<(), Error> {
619 let _guard = self._write_lock.lock();
620 let mut wtxn = self.storage.env.write_txn()?;
621 self.drop_edge_index_impl(&mut wtxn, etype, property, 0x00)?;
622 wtxn.commit()?;
623 Ok(())
624 }
625
626 pub fn drop_edge_unique_constraint(&self, etype: &str, property: &str) -> Result<(), Error> {
627 let _guard = self._write_lock.lock();
628 let mut wtxn = self.storage.env.write_txn()?;
629 self.drop_edge_index_impl(&mut wtxn, etype, property, 0x01)?;
630 wtxn.commit()?;
631 Ok(())
632 }
633
634 pub fn drop_edge_required_constraint(&self, etype: &str, property: &str) -> Result<(), Error> {
635 let _guard = self._write_lock.lock();
636 let mut wtxn = self.storage.env.write_txn()?;
637 self.drop_edge_index_impl(&mut wtxn, etype, property, 0x02)?;
638 wtxn.commit()?;
639 Ok(())
640 }
641
642 pub(super) fn drop_edge_index_impl(
643 &self,
644 wtxn: &mut heed::RwTxn,
645 etype: &str,
646 property: &str,
647 flags: u8,
648 ) -> Result<(), Error> {
649 let type_id = get_or_create_type(&self.storage, wtxn, etype)?;
650 let prop_key_id = get_or_create_prop_key(&self.storage, wtxn, property)?;
651 let meta_key = format!("idx_meta:edge:t:{type_id}:p:{prop_key_id}");
652
653 if let Some(existing_val) = self.storage.meta.get(wtxn, &meta_key)? {
654 if !existing_val.is_empty() && existing_val[0] == flags {
655 self.storage.meta.delete(wtxn, &meta_key)?;
656
657 let mut prefix = Vec::with_capacity(8);
658 prefix.extend_from_slice(&type_id.to_be_bytes());
659 prefix.extend_from_slice(&prop_key_id.to_be_bytes());
660
661 let mut to_delete = Vec::new();
662 for entry in self.storage.edge_prop_idx.prefix_iter(wtxn, &prefix)? {
663 let (key, _) = entry?;
664 to_delete.push(key.to_vec());
665 }
666
667 for key in to_delete {
668 self.storage.edge_prop_idx.delete(wtxn, &key)?;
669 }
670 }
671 }
672
673 Ok(())
674 }
675
676 pub fn nodes_by_property(
677 &self,
678 label: &str,
679 property: &str,
680 val: PropValue,
681 ) -> Result<Vec<NodeId>, Error> {
682 let rtxn = self.storage.env.read_txn()?;
683 self.nodes_by_property_impl(&rtxn, label, property, val)
684 }
685
686 pub(super) fn nodes_by_property_impl(
687 &self,
688 rtxn: &heed::RoTxn,
689 label: &str,
690 property: &str,
691 val: PropValue,
692 ) -> Result<Vec<NodeId>, Error> {
693 let val = val.into_json();
694 let label_key = format!("label:{label}");
695 let label_id = match self.storage.meta.get(rtxn, &label_key)? {
696 Some(b) => {
697 let arr: [u8; 4] = b
698 .try_into()
699 .map_err(|_| Error::Corrupt("label id must be 4 bytes"))?;
700 u32::from_be_bytes(arr)
701 }
702 None => return Ok(Vec::new()),
703 };
704
705 let prop_key = format!("prop_key:{property}");
706 let prop_key_id = match self.storage.meta.get(rtxn, &prop_key)? {
707 Some(b) => {
708 let arr: [u8; 4] = b
709 .try_into()
710 .map_err(|_| Error::Corrupt("prop key id must be 4 bytes"))?;
711 u32::from_be_bytes(arr)
712 }
713 None => return Ok(Vec::new()),
714 };
715
716 let encoded = match encode_property_value(&val) {
717 Some(e) => e,
718 None => return Ok(Vec::new()),
719 };
720
721 let mut prefix = Vec::with_capacity(4 + 4 + encoded.len());
722 prefix.extend_from_slice(&label_id.to_be_bytes());
723 prefix.extend_from_slice(&prop_key_id.to_be_bytes());
724 prefix.extend_from_slice(&encoded);
725
726 let mut result = Vec::new();
727 for entry in self.storage.node_prop_idx.prefix_iter(rtxn, &prefix)? {
728 let (key, _) = entry?;
729 if key.len() >= 8 {
730 let mut node_id_bytes = [0u8; 8];
731 node_id_bytes.copy_from_slice(&key[key.len() - 8..]);
732 result.push(u64::from_be_bytes(node_id_bytes));
733 }
734 }
735 Ok(result)
736 }
737
738 pub fn nodes_by_property_range(
739 &self,
740 label: &str,
741 property: &str,
742 min_val: Option<PropValue>,
743 min_inclusive: bool,
744 max_val: Option<PropValue>,
745 max_inclusive: bool,
746 ) -> Result<Vec<NodeId>, Error> {
747 let rtxn = self.storage.env.read_txn()?;
748 self.nodes_by_property_range_impl(
749 &rtxn,
750 label,
751 property,
752 min_val,
753 min_inclusive,
754 max_val,
755 max_inclusive,
756 )
757 }
758
759 #[allow(clippy::too_many_arguments)]
760 pub(super) fn nodes_by_property_range_impl(
761 &self,
762 rtxn: &heed::RoTxn,
763 label: &str,
764 property: &str,
765 min_val: Option<PropValue>,
766 min_inclusive: bool,
767 max_val: Option<PropValue>,
768 max_inclusive: bool,
769 ) -> Result<Vec<NodeId>, Error> {
770 let label_key = format!("label:{label}");
771 let label_id = match self.storage.meta.get(rtxn, &label_key)? {
772 Some(b) => {
773 let arr: [u8; 4] = b
774 .try_into()
775 .map_err(|_| Error::Corrupt("label id must be 4 bytes"))?;
776 u32::from_be_bytes(arr)
777 }
778 None => return Ok(Vec::new()),
779 };
780
781 let prop_key = format!("prop_key:{property}");
782 let prop_key_id = match self.storage.meta.get(rtxn, &prop_key)? {
783 Some(b) => {
784 let arr: [u8; 4] = b
785 .try_into()
786 .map_err(|_| Error::Corrupt("prop key id must be 4 bytes"))?;
787 u32::from_be_bytes(arr)
788 }
789 None => return Ok(Vec::new()),
790 };
791
792 let mut prefix = Vec::with_capacity(8);
793 prefix.extend_from_slice(&label_id.to_be_bytes());
794 prefix.extend_from_slice(&prop_key_id.to_be_bytes());
795
796 let min_encoded = min_val
797 .map(|v| v.into_json())
798 .as_ref()
799 .and_then(encode_property_value);
800 let max_encoded = max_val
801 .map(|v| v.into_json())
802 .as_ref()
803 .and_then(encode_property_value);
804
805 let mut result = Vec::new();
806 for entry in self.storage.node_prop_idx.prefix_iter(rtxn, &prefix)? {
807 let (key, _) = entry?;
808 if key.len() >= prefix.len() + 8 {
809 let val_bytes = &key[prefix.len()..key.len() - 8];
810
811 if let Some(ref min_enc) = min_encoded {
812 if min_inclusive {
813 if val_bytes < min_enc.as_slice() {
814 continue;
815 }
816 } else if val_bytes <= min_enc.as_slice() {
817 continue;
818 }
819 }
820 if let Some(ref max_enc) = max_encoded {
821 if max_inclusive {
822 if val_bytes > max_enc.as_slice() {
823 continue;
824 }
825 } else if val_bytes >= max_enc.as_slice() {
826 continue;
827 }
828 }
829
830 let mut node_id_bytes = [0u8; 8];
831 node_id_bytes.copy_from_slice(&key[key.len() - 8..]);
832 result.push(u64::from_be_bytes(node_id_bytes));
833 }
834 }
835 Ok(result)
836 }
837
838 pub fn has_node_property_index(&self, label: &str, property: &str) -> Result<bool, Error> {
839 let rtxn = self.storage.env.read_txn()?;
840 self.has_node_property_index_impl(&rtxn, label, property)
841 }
842
843 pub(super) fn has_node_property_index_impl(
844 &self,
845 rtxn: &heed::RoTxn,
846 label: &str,
847 property: &str,
848 ) -> Result<bool, Error> {
849 let label_key = format!("label:{label}");
850 let label_id = match self.storage.meta.get(rtxn, &label_key)? {
851 Some(b) => {
852 let arr: [u8; 4] = b
853 .try_into()
854 .map_err(|_| Error::Corrupt("label id must be 4 bytes"))?;
855 u32::from_be_bytes(arr)
856 }
857 None => return Ok(false),
858 };
859
860 let prop_key = format!("prop_key:{property}");
861 let prop_key_id = match self.storage.meta.get(rtxn, &prop_key)? {
862 Some(b) => {
863 let arr: [u8; 4] = b
864 .try_into()
865 .map_err(|_| Error::Corrupt("prop key id must be 4 bytes"))?;
866 u32::from_be_bytes(arr)
867 }
868 None => return Ok(false),
869 };
870
871 let mut prefix = Vec::with_capacity(8);
875 prefix.extend_from_slice(&label_id.to_be_bytes());
876 prefix.extend_from_slice(&prop_key_id.to_be_bytes());
877 let mut iter = self.storage.node_prop_idx.prefix_iter(rtxn, &prefix)?;
878 Ok(iter.next().is_some())
879 }
880
881 pub fn edges_by_property(
882 &self,
883 etype: &str,
884 property: &str,
885 val: PropValue,
886 ) -> Result<Vec<EdgeId>, Error> {
887 let rtxn = self.storage.env.read_txn()?;
888 self.edges_by_property_impl(&rtxn, etype, property, val)
889 }
890
891 pub(super) fn edges_by_property_impl(
892 &self,
893 rtxn: &heed::RoTxn,
894 etype: &str,
895 property: &str,
896 val: PropValue,
897 ) -> Result<Vec<EdgeId>, Error> {
898 let val = val.into_json();
899 let type_key = format!("type:{etype}");
900 let type_id = match self.storage.meta.get(rtxn, &type_key)? {
901 Some(b) => {
902 let arr: [u8; 4] = b
903 .try_into()
904 .map_err(|_| Error::Corrupt("type id must be 4 bytes"))?;
905 u32::from_be_bytes(arr)
906 }
907 None => return Ok(Vec::new()),
908 };
909
910 let prop_key = format!("prop_key:{property}");
911 let prop_key_id = match self.storage.meta.get(rtxn, &prop_key)? {
912 Some(b) => {
913 let arr: [u8; 4] = b
914 .try_into()
915 .map_err(|_| Error::Corrupt("prop key id must be 4 bytes"))?;
916 u32::from_be_bytes(arr)
917 }
918 None => return Ok(Vec::new()),
919 };
920
921 let encoded = match encode_property_value(&val) {
922 Some(e) => e,
923 None => return Ok(Vec::new()),
924 };
925
926 let mut prefix = Vec::with_capacity(4 + 4 + encoded.len());
927 prefix.extend_from_slice(&type_id.to_be_bytes());
928 prefix.extend_from_slice(&prop_key_id.to_be_bytes());
929 prefix.extend_from_slice(&encoded);
930
931 let mut result = Vec::new();
932 for entry in self.storage.edge_prop_idx.prefix_iter(rtxn, &prefix)? {
933 let (key, _) = entry?;
934 if key.len() >= 8 {
935 let mut edge_id_bytes = [0u8; 8];
936 edge_id_bytes.copy_from_slice(&key[key.len() - 8..]);
937 result.push(u64::from_be_bytes(edge_id_bytes));
938 }
939 }
940 Ok(result)
941 }
942
943 pub fn edges_by_property_range(
944 &self,
945 etype: &str,
946 property: &str,
947 min_val: Option<PropValue>,
948 max_val: Option<PropValue>,
949 ) -> Result<Vec<EdgeId>, Error> {
950 let rtxn = self.storage.env.read_txn()?;
951 self.edges_by_property_range_impl(&rtxn, etype, property, min_val, max_val)
952 }
953
954 pub(super) fn edges_by_property_range_impl(
955 &self,
956 rtxn: &heed::RoTxn,
957 etype: &str,
958 property: &str,
959 min_val: Option<PropValue>,
960 max_val: Option<PropValue>,
961 ) -> Result<Vec<EdgeId>, Error> {
962 let type_key = format!("type:{etype}");
963 let type_id = match self.storage.meta.get(rtxn, &type_key)? {
964 Some(b) => {
965 let arr: [u8; 4] = b
966 .try_into()
967 .map_err(|_| Error::Corrupt("type id must be 4 bytes"))?;
968 u32::from_be_bytes(arr)
969 }
970 None => return Ok(Vec::new()),
971 };
972
973 let prop_key = format!("prop_key:{property}");
974 let prop_key_id = match self.storage.meta.get(rtxn, &prop_key)? {
975 Some(b) => {
976 let arr: [u8; 4] = b
977 .try_into()
978 .map_err(|_| Error::Corrupt("prop key id must be 4 bytes"))?;
979 u32::from_be_bytes(arr)
980 }
981 None => return Ok(Vec::new()),
982 };
983
984 let mut prefix = Vec::with_capacity(8);
985 prefix.extend_from_slice(&type_id.to_be_bytes());
986 prefix.extend_from_slice(&prop_key_id.to_be_bytes());
987
988 let min_encoded = min_val
989 .map(|v| v.into_json())
990 .as_ref()
991 .and_then(encode_property_value);
992 let max_encoded = max_val
993 .map(|v| v.into_json())
994 .as_ref()
995 .and_then(encode_property_value);
996
997 let mut result = Vec::new();
998 for entry in self.storage.edge_prop_idx.prefix_iter(rtxn, &prefix)? {
999 let (key, _) = entry?;
1000 if key.len() >= prefix.len() + 8 {
1001 let val_bytes = &key[prefix.len()..key.len() - 8];
1002
1003 if let Some(ref min_enc) = min_encoded {
1004 if val_bytes < min_enc.as_slice() {
1005 continue;
1006 }
1007 }
1008 if let Some(ref max_enc) = max_encoded {
1009 if val_bytes > max_enc.as_slice() {
1010 continue;
1011 }
1012 }
1013
1014 let mut edge_id_bytes = [0u8; 8];
1015 edge_id_bytes.copy_from_slice(&key[key.len() - 8..]);
1016 result.push(u64::from_be_bytes(edge_id_bytes));
1017 }
1018 }
1019 Ok(result)
1020 }
1021
1022 pub fn list_node_indexes_and_constraints(&self) -> Result<Vec<(String, String, u8)>, Error> {
1023 let rtxn = self.storage.env.read_txn()?;
1024 let mut result = Vec::new();
1025 for entry in self.storage.meta.iter(&rtxn)? {
1026 let (key, val) = entry?;
1027 if let Some(rest) = key.strip_prefix("idx_meta:node:l:") {
1028 let parts: Vec<&str> = rest.split(":p:").collect();
1029 if parts.len() == 2 {
1030 if let (Ok(label_id), Ok(prop_key_id)) =
1031 (parts[0].parse::<u32>(), parts[1].parse::<u32>())
1032 {
1033 if let (Some(label_name), Some(prop_name)) = (
1034 self.label_name_impl(&rtxn, label_id)?,
1035 crate::storage::ids::get_prop_key_name(
1036 &self.storage,
1037 &rtxn,
1038 prop_key_id,
1039 )?,
1040 ) {
1041 let flags = val.first().copied().unwrap_or(0x00);
1042 result.push((label_name, prop_name, flags));
1043 }
1044 }
1045 }
1046 }
1047 }
1048 Ok(result)
1049 }
1050
1051 pub fn list_edge_indexes_and_constraints(&self) -> Result<Vec<(String, String, u8)>, Error> {
1052 let rtxn = self.storage.env.read_txn()?;
1053 let mut result = Vec::new();
1054 for entry in self.storage.meta.iter(&rtxn)? {
1055 let (key, val) = entry?;
1056 if let Some(rest) = key.strip_prefix("idx_meta:edge:t:") {
1057 let parts: Vec<&str> = rest.split(":p:").collect();
1058 if parts.len() == 2 {
1059 if let (Ok(type_id), Ok(prop_key_id)) =
1060 (parts[0].parse::<u32>(), parts[1].parse::<u32>())
1061 {
1062 if let (Some(type_name), Some(prop_name)) = (
1063 self.type_name_impl(&rtxn, type_id)?,
1064 crate::storage::ids::get_prop_key_name(
1065 &self.storage,
1066 &rtxn,
1067 prop_key_id,
1068 )?,
1069 ) {
1070 let flags = val.first().copied().unwrap_or(0x00);
1071 result.push((type_name, prop_name, flags));
1072 }
1073 }
1074 }
1075 }
1076 }
1077 Ok(result)
1078 }
1079}
1080
1081#[cfg(test)]
1082mod tests {
1083 use serde_json::json;
1084 use tempfile::TempDir;
1085
1086 use super::*;
1087
1088 fn open_tmp() -> (TempDir, Graph) {
1089 let dir = TempDir::new().unwrap();
1090 let g = Graph::open(dir.path(), 1).unwrap();
1091 (dir, g)
1092 }
1093
1094 #[test]
1097 fn drop_index_preserves_auto_index() {
1098 let (_dir, g) = open_tmp();
1099 let id = g.add_node("Person", &json!({"age": 30})).unwrap();
1100
1101 g.create_node_property_index("Person", "age").unwrap();
1102 g.drop_node_property_index("Person", "age").unwrap();
1103
1104 assert_eq!(
1105 g.nodes_by_property("Person", "age", PropValue::Int(30))
1106 .unwrap(),
1107 vec![id],
1108 "auto-index entries must survive dropping the explicit index"
1109 );
1110 }
1111
1112 #[test]
1115 fn drop_unique_constraint_preserves_lookups() {
1116 let (_dir, g) = open_tmp();
1117 let id = g.add_node("User", &json!({"email": "a@b.c"})).unwrap();
1118
1119 g.create_node_unique_constraint("User", "email").unwrap();
1120 g.drop_node_unique_constraint("User", "email").unwrap();
1121
1122 assert_eq!(
1123 g.nodes_by_property("User", "email", PropValue::Str("a@b.c".into()))
1124 .unwrap(),
1125 vec![id]
1126 );
1127
1128 let id2 = g.add_node("User", &json!({"email": "a@b.c"})).unwrap();
1131 let mut hits = g
1132 .nodes_by_property("User", "email", PropValue::Str("a@b.c".into()))
1133 .unwrap();
1134 hits.sort();
1135 let mut expected = vec![id, id2];
1136 expected.sort();
1137 assert_eq!(hits, expected);
1138 }
1139
1140 #[test]
1143 fn large_integer_property_no_false_match() {
1144 let (_dir, g) = open_tmp();
1145 let a = g
1146 .add_node("Item", &json!({"sid": 9_007_199_254_740_992_i64}))
1147 .unwrap();
1148 let b = g
1149 .add_node("Item", &json!({"sid": 9_007_199_254_740_993_i64}))
1150 .unwrap();
1151
1152 assert_eq!(
1153 g.nodes_by_property("Item", "sid", PropValue::Int(9_007_199_254_740_992))
1154 .unwrap(),
1155 vec![a]
1156 );
1157 assert_eq!(
1158 g.nodes_by_property("Item", "sid", PropValue::Int(9_007_199_254_740_993))
1159 .unwrap(),
1160 vec![b]
1161 );
1162 }
1163
1164 #[test]
1167 fn integer_property_matches_float_query() {
1168 let (_dir, g) = open_tmp();
1169 let id = g.add_node("Person", &json!({"age": 30})).unwrap();
1170 assert_eq!(
1171 g.nodes_by_property("Person", "age", PropValue::Float(30.0))
1172 .unwrap(),
1173 vec![id]
1174 );
1175 }
1176
1177 #[test]
1180 fn node_count_hint_is_high_water_mark() {
1181 let (_dir, g) = open_tmp();
1182 assert_eq!(g.node_count_hint().unwrap(), 0);
1183
1184 let a = g.add_node("N", &()).unwrap();
1185 g.add_node("N", &()).unwrap();
1186 assert_eq!(g.node_count_hint().unwrap(), 2);
1187
1188 g.delete_node(a).unwrap();
1189 assert_eq!(g.node_count_hint().unwrap(), 2);
1190 }
1191
1192 #[test]
1195 fn edge_property_index_lookup() {
1196 let (_dir, g) = open_tmp();
1197 let a = g.add_node("N", &()).unwrap();
1198 let b = g.add_node("N", &()).unwrap();
1199
1200 let e1 = g.add_edge(a, b, "ROAD", &json!({"cost": 5})).unwrap();
1202 g.create_edge_property_index("ROAD", "cost").unwrap();
1203
1204 let e2 = g.add_edge(b, a, "ROAD", &json!({"cost": 7})).unwrap();
1206
1207 assert_eq!(
1208 g.edges_by_property("ROAD", "cost", PropValue::Int(5))
1209 .unwrap(),
1210 vec![e1]
1211 );
1212 assert_eq!(
1213 g.edges_by_property("ROAD", "cost", PropValue::Int(7))
1214 .unwrap(),
1215 vec![e2]
1216 );
1217 assert_eq!(
1218 g.edges_by_property_range(
1219 "ROAD",
1220 "cost",
1221 Some(PropValue::Int(5)),
1222 Some(PropValue::Int(7)),
1223 )
1224 .unwrap(),
1225 vec![e1, e2]
1226 );
1227 }
1228
1229 #[test]
1230 fn drop_edge_property_index_removes_entries() {
1231 let (_dir, g) = open_tmp();
1232 let a = g.add_node("N", &()).unwrap();
1233 let b = g.add_node("N", &()).unwrap();
1234 g.create_edge_property_index("ROAD", "cost").unwrap();
1235 g.add_edge(a, b, "ROAD", &json!({"cost": 5})).unwrap();
1236
1237 g.drop_edge_property_index("ROAD", "cost").unwrap();
1238 assert_eq!(
1239 g.edges_by_property("ROAD", "cost", PropValue::Int(5))
1240 .unwrap(),
1241 Vec::<EdgeId>::new()
1242 );
1243 }
1244
1245 #[test]
1246 fn edge_unique_constraint_rejects_duplicate_insert() {
1247 let (_dir, g) = open_tmp();
1248 let a = g.add_node("N", &()).unwrap();
1249 let b = g.add_node("N", &()).unwrap();
1250 g.create_edge_unique_constraint("ROAD", "toll_id").unwrap();
1251
1252 g.add_edge(a, b, "ROAD", &json!({"toll_id": 1})).unwrap();
1253 let err = g
1254 .add_edge(b, a, "ROAD", &json!({"toll_id": 1}))
1255 .unwrap_err();
1256 assert!(matches!(err, Error::UniqueConstraintViolation(..)));
1257 }
1258
1259 #[test]
1260 fn edge_unique_constraint_rejects_existing_duplicates() {
1261 let (_dir, g) = open_tmp();
1262 let a = g.add_node("N", &()).unwrap();
1263 let b = g.add_node("N", &()).unwrap();
1264 g.add_edge(a, b, "ROAD", &json!({"toll_id": 1})).unwrap();
1265 g.add_edge(b, a, "ROAD", &json!({"toll_id": 1})).unwrap();
1266
1267 let err = g
1268 .create_edge_unique_constraint("ROAD", "toll_id")
1269 .unwrap_err();
1270 assert!(matches!(err, Error::UniqueConstraintViolation(..)));
1271 }
1272
1273 #[test]
1274 fn edge_required_constraint_rejects_missing_property() {
1275 let (_dir, g) = open_tmp();
1276 let a = g.add_node("N", &()).unwrap();
1277 let b = g.add_node("N", &()).unwrap();
1278 g.create_edge_required_constraint("ROAD", "cost").unwrap();
1279
1280 let err = g.add_edge(a, b, "ROAD", &json!({})).unwrap_err();
1281 assert!(matches!(err, Error::RequiredConstraintViolation(..)));
1282
1283 g.add_edge(a, b, "RAIL", &json!({})).unwrap();
1285 let err = g
1286 .create_edge_required_constraint("RAIL", "cost")
1287 .unwrap_err();
1288 assert!(matches!(err, Error::RequiredConstraintViolation(..)));
1289 }
1290
1291 #[test]
1294 fn update_edge_reindexes_edge_properties() {
1295 let (_dir, g) = open_tmp();
1296 let a = g.add_node("N", &()).unwrap();
1297 let b = g.add_node("N", &()).unwrap();
1298 g.create_edge_property_index("ROAD", "cost").unwrap();
1299 let eid = g.add_edge(a, b, "ROAD", &json!({"cost": 5})).unwrap();
1300
1301 g.update_edge(eid, &json!({"cost": 7})).unwrap();
1302
1303 assert_eq!(
1304 g.edges_by_property("ROAD", "cost", PropValue::Int(5))
1305 .unwrap(),
1306 Vec::<EdgeId>::new(),
1307 "stale index entry must be removed"
1308 );
1309 assert_eq!(
1310 g.edges_by_property("ROAD", "cost", PropValue::Int(7))
1311 .unwrap(),
1312 vec![eid],
1313 "new value must be indexed"
1314 );
1315 }
1316
1317 #[test]
1318 fn update_edge_enforces_unique_constraint() {
1319 let (_dir, g) = open_tmp();
1320 let a = g.add_node("N", &()).unwrap();
1321 let b = g.add_node("N", &()).unwrap();
1322 g.create_edge_unique_constraint("ROAD", "toll_id").unwrap();
1323 g.add_edge(a, b, "ROAD", &json!({"toll_id": 1})).unwrap();
1324 let e2 = g.add_edge(b, a, "ROAD", &json!({"toll_id": 2})).unwrap();
1325
1326 let err = g.update_edge(e2, &json!({"toll_id": 1})).unwrap_err();
1327 assert!(matches!(err, Error::UniqueConstraintViolation(..)));
1328
1329 g.update_edge(e2, &json!({"toll_id": 2})).unwrap();
1331 }
1332
1333 #[test]
1334 fn update_edge_enforces_required_constraint() {
1335 let (_dir, g) = open_tmp();
1336 let a = g.add_node("N", &()).unwrap();
1337 let b = g.add_node("N", &()).unwrap();
1338 g.create_edge_required_constraint("ROAD", "cost").unwrap();
1339 let eid = g.add_edge(a, b, "ROAD", &json!({"cost": 5})).unwrap();
1340
1341 let err = g.update_edge(eid, &json!({})).unwrap_err();
1342 assert!(matches!(err, Error::RequiredConstraintViolation(..)));
1343 }
1344}
1345
1346#[cfg(test)]
1347mod label_filter_tests {
1348 use serde_json::json;
1349 use tempfile::TempDir;
1350
1351 use crate::Graph;
1352
1353 fn open_tmp() -> (TempDir, Graph) {
1354 let dir = TempDir::new().unwrap();
1355 let g = Graph::open(dir.path(), 1).unwrap();
1356 (dir, g)
1357 }
1358
1359 #[test]
1360 fn label_filter_keeps_only_labeled_nodes() {
1361 let (_dir, g) = open_tmp();
1362 let a = g.add_node("Person", &json!({})).unwrap();
1363 let b = g.add_node("City", &json!({})).unwrap();
1364 let c = g.add_node_multi(&["City", "Person"], &json!({})).unwrap();
1365
1366 let filtered = g.label_filter(&[a, b, c], "Person").unwrap();
1367 assert_eq!(filtered.len(), 2);
1368 assert!(filtered.contains(&a));
1369 assert!(filtered.contains(&c));
1370 }
1371
1372 #[test]
1373 fn label_filter_unknown_label_is_empty() {
1374 let (_dir, g) = open_tmp();
1375 let a = g.add_node("Person", &json!({})).unwrap();
1376 assert!(g.label_filter(&[a], "Ghost").unwrap().is_empty());
1377 }
1378
1379 #[test]
1380 fn label_filter_sees_committed_writes_immediately() {
1381 let (_dir, g) = open_tmp();
1382 let a = g.add_node("Person", &json!({})).unwrap();
1383 g.add_label(a, "Admin").unwrap();
1384 assert_eq!(g.label_filter(&[a], "Admin").unwrap(), vec![a]);
1385 g.remove_label(a, "Admin").unwrap();
1386 assert!(g.label_filter(&[a], "Admin").unwrap().is_empty());
1387 }
1388}