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
695 let encoded = match encode_property_value(&val) {
702 Some(e) => e,
703 None => return self.scan_label_for_property_eq(rtxn, label, property, &val),
704 };
705
706 let label_key = format!("label:{label}");
707 let label_id = match self.storage.meta.get(rtxn, &label_key)? {
708 Some(b) => {
709 let arr: [u8; 4] = b
710 .try_into()
711 .map_err(|_| Error::Corrupt("label id must be 4 bytes"))?;
712 u32::from_be_bytes(arr)
713 }
714 None => return Ok(Vec::new()),
715 };
716
717 let prop_key = format!("prop_key:{property}");
718 let prop_key_id = match self.storage.meta.get(rtxn, &prop_key)? {
719 Some(b) => {
720 let arr: [u8; 4] = b
721 .try_into()
722 .map_err(|_| Error::Corrupt("prop key id must be 4 bytes"))?;
723 u32::from_be_bytes(arr)
724 }
725 None => return Ok(Vec::new()),
726 };
727
728 let mut prefix = Vec::with_capacity(4 + 4 + encoded.len());
729 prefix.extend_from_slice(&label_id.to_be_bytes());
730 prefix.extend_from_slice(&prop_key_id.to_be_bytes());
731 prefix.extend_from_slice(&encoded);
732
733 let mut result = Vec::new();
734 for entry in self.storage.node_prop_idx.prefix_iter(rtxn, &prefix)? {
735 let (key, _) = entry?;
736 if key.len() >= 8 {
737 let mut node_id_bytes = [0u8; 8];
738 node_id_bytes.copy_from_slice(&key[key.len() - 8..]);
739 result.push(u64::from_be_bytes(node_id_bytes));
740 }
741 }
742 Ok(result)
743 }
744
745 fn scan_label_for_property_eq(
749 &self,
750 rtxn: &heed::RoTxn,
751 label: &str,
752 property: &str,
753 val: &serde_json::Value,
754 ) -> Result<Vec<NodeId>, Error> {
755 let mut result = Vec::new();
756 for id in self.nodes_by_label_impl(rtxn, label)? {
757 if let Some(record) = self.get_node_impl(rtxn, id)? {
758 let props: serde_json::Value = props::decode(&record.props)?;
759 if props.get(property) == Some(val) {
760 result.push(id);
761 }
762 }
763 }
764 Ok(result)
765 }
766
767 pub fn nodes_by_property_range(
768 &self,
769 label: &str,
770 property: &str,
771 min_val: Option<PropValue>,
772 min_inclusive: bool,
773 max_val: Option<PropValue>,
774 max_inclusive: bool,
775 ) -> Result<Vec<NodeId>, Error> {
776 let rtxn = self.storage.env.read_txn()?;
777 self.nodes_by_property_range_impl(
778 &rtxn,
779 label,
780 property,
781 min_val,
782 min_inclusive,
783 max_val,
784 max_inclusive,
785 )
786 }
787
788 #[allow(clippy::too_many_arguments)]
789 pub(super) fn nodes_by_property_range_impl(
790 &self,
791 rtxn: &heed::RoTxn,
792 label: &str,
793 property: &str,
794 min_val: Option<PropValue>,
795 min_inclusive: bool,
796 max_val: Option<PropValue>,
797 max_inclusive: bool,
798 ) -> Result<Vec<NodeId>, Error> {
799 let label_key = format!("label:{label}");
800 let label_id = match self.storage.meta.get(rtxn, &label_key)? {
801 Some(b) => {
802 let arr: [u8; 4] = b
803 .try_into()
804 .map_err(|_| Error::Corrupt("label id must be 4 bytes"))?;
805 u32::from_be_bytes(arr)
806 }
807 None => return Ok(Vec::new()),
808 };
809
810 let prop_key = format!("prop_key:{property}");
811 let prop_key_id = match self.storage.meta.get(rtxn, &prop_key)? {
812 Some(b) => {
813 let arr: [u8; 4] = b
814 .try_into()
815 .map_err(|_| Error::Corrupt("prop key id must be 4 bytes"))?;
816 u32::from_be_bytes(arr)
817 }
818 None => return Ok(Vec::new()),
819 };
820
821 let mut prefix = Vec::with_capacity(8);
822 prefix.extend_from_slice(&label_id.to_be_bytes());
823 prefix.extend_from_slice(&prop_key_id.to_be_bytes());
824
825 let min_encoded = min_val
826 .map(|v| v.into_json())
827 .as_ref()
828 .and_then(encode_property_value);
829 let max_encoded = max_val
830 .map(|v| v.into_json())
831 .as_ref()
832 .and_then(encode_property_value);
833
834 let mut result = Vec::new();
835 for entry in self.storage.node_prop_idx.prefix_iter(rtxn, &prefix)? {
836 let (key, _) = entry?;
837 if key.len() >= prefix.len() + 8 {
838 let val_bytes = &key[prefix.len()..key.len() - 8];
839
840 if let Some(ref min_enc) = min_encoded {
841 if min_inclusive {
842 if val_bytes < min_enc.as_slice() {
843 continue;
844 }
845 } else if val_bytes <= min_enc.as_slice() {
846 continue;
847 }
848 }
849 if let Some(ref max_enc) = max_encoded {
850 if max_inclusive {
851 if val_bytes > max_enc.as_slice() {
852 continue;
853 }
854 } else if val_bytes >= max_enc.as_slice() {
855 continue;
856 }
857 }
858
859 let mut node_id_bytes = [0u8; 8];
860 node_id_bytes.copy_from_slice(&key[key.len() - 8..]);
861 result.push(u64::from_be_bytes(node_id_bytes));
862 }
863 }
864 Ok(result)
865 }
866
867 pub fn has_node_property_index(&self, label: &str, property: &str) -> Result<bool, Error> {
868 let rtxn = self.storage.env.read_txn()?;
869 self.has_node_property_index_impl(&rtxn, label, property)
870 }
871
872 pub(super) fn has_node_property_index_impl(
873 &self,
874 rtxn: &heed::RoTxn,
875 label: &str,
876 property: &str,
877 ) -> Result<bool, Error> {
878 let label_key = format!("label:{label}");
879 let label_id = match self.storage.meta.get(rtxn, &label_key)? {
880 Some(b) => {
881 let arr: [u8; 4] = b
882 .try_into()
883 .map_err(|_| Error::Corrupt("label id must be 4 bytes"))?;
884 u32::from_be_bytes(arr)
885 }
886 None => return Ok(false),
887 };
888
889 let prop_key = format!("prop_key:{property}");
890 let prop_key_id = match self.storage.meta.get(rtxn, &prop_key)? {
891 Some(b) => {
892 let arr: [u8; 4] = b
893 .try_into()
894 .map_err(|_| Error::Corrupt("prop key id must be 4 bytes"))?;
895 u32::from_be_bytes(arr)
896 }
897 None => return Ok(false),
898 };
899
900 let mut prefix = Vec::with_capacity(8);
904 prefix.extend_from_slice(&label_id.to_be_bytes());
905 prefix.extend_from_slice(&prop_key_id.to_be_bytes());
906 let mut iter = self.storage.node_prop_idx.prefix_iter(rtxn, &prefix)?;
907 Ok(iter.next().is_some())
908 }
909
910 pub fn edges_by_property(
911 &self,
912 etype: &str,
913 property: &str,
914 val: PropValue,
915 ) -> Result<Vec<EdgeId>, Error> {
916 let rtxn = self.storage.env.read_txn()?;
917 self.edges_by_property_impl(&rtxn, etype, property, val)
918 }
919
920 pub(super) fn edges_by_property_impl(
921 &self,
922 rtxn: &heed::RoTxn,
923 etype: &str,
924 property: &str,
925 val: PropValue,
926 ) -> Result<Vec<EdgeId>, Error> {
927 let val = val.into_json();
928
929 let encoded = match encode_property_value(&val) {
933 Some(e) => e,
934 None => return self.scan_type_for_property_eq(rtxn, etype, property, &val),
935 };
936
937 let type_key = format!("type:{etype}");
938 let type_id = match self.storage.meta.get(rtxn, &type_key)? {
939 Some(b) => {
940 let arr: [u8; 4] = b
941 .try_into()
942 .map_err(|_| Error::Corrupt("type id must be 4 bytes"))?;
943 u32::from_be_bytes(arr)
944 }
945 None => return Ok(Vec::new()),
946 };
947
948 let prop_key = format!("prop_key:{property}");
949 let prop_key_id = match self.storage.meta.get(rtxn, &prop_key)? {
950 Some(b) => {
951 let arr: [u8; 4] = b
952 .try_into()
953 .map_err(|_| Error::Corrupt("prop key id must be 4 bytes"))?;
954 u32::from_be_bytes(arr)
955 }
956 None => return Ok(Vec::new()),
957 };
958
959 let mut prefix = Vec::with_capacity(4 + 4 + encoded.len());
960 prefix.extend_from_slice(&type_id.to_be_bytes());
961 prefix.extend_from_slice(&prop_key_id.to_be_bytes());
962 prefix.extend_from_slice(&encoded);
963
964 let mut result = Vec::new();
965 for entry in self.storage.edge_prop_idx.prefix_iter(rtxn, &prefix)? {
966 let (key, _) = entry?;
967 if key.len() >= 8 {
968 let mut edge_id_bytes = [0u8; 8];
969 edge_id_bytes.copy_from_slice(&key[key.len() - 8..]);
970 result.push(u64::from_be_bytes(edge_id_bytes));
971 }
972 }
973 Ok(result)
974 }
975
976 fn scan_type_for_property_eq(
980 &self,
981 rtxn: &heed::RoTxn,
982 etype: &str,
983 property: &str,
984 val: &serde_json::Value,
985 ) -> Result<Vec<EdgeId>, Error> {
986 let mut result = Vec::new();
987 for id in self.edges_by_type_impl(rtxn, etype)? {
988 if let Some(record) = self.get_edge_impl(rtxn, id)? {
989 let props: serde_json::Value = props::decode(&record.props)?;
990 if props.get(property) == Some(val) {
991 result.push(id);
992 }
993 }
994 }
995 Ok(result)
996 }
997
998 pub fn edges_by_property_range(
999 &self,
1000 etype: &str,
1001 property: &str,
1002 min_val: Option<PropValue>,
1003 max_val: Option<PropValue>,
1004 ) -> Result<Vec<EdgeId>, Error> {
1005 let rtxn = self.storage.env.read_txn()?;
1006 self.edges_by_property_range_impl(&rtxn, etype, property, min_val, max_val)
1007 }
1008
1009 pub(super) fn edges_by_property_range_impl(
1010 &self,
1011 rtxn: &heed::RoTxn,
1012 etype: &str,
1013 property: &str,
1014 min_val: Option<PropValue>,
1015 max_val: Option<PropValue>,
1016 ) -> Result<Vec<EdgeId>, Error> {
1017 let type_key = format!("type:{etype}");
1018 let type_id = match self.storage.meta.get(rtxn, &type_key)? {
1019 Some(b) => {
1020 let arr: [u8; 4] = b
1021 .try_into()
1022 .map_err(|_| Error::Corrupt("type id must be 4 bytes"))?;
1023 u32::from_be_bytes(arr)
1024 }
1025 None => return Ok(Vec::new()),
1026 };
1027
1028 let prop_key = format!("prop_key:{property}");
1029 let prop_key_id = match self.storage.meta.get(rtxn, &prop_key)? {
1030 Some(b) => {
1031 let arr: [u8; 4] = b
1032 .try_into()
1033 .map_err(|_| Error::Corrupt("prop key id must be 4 bytes"))?;
1034 u32::from_be_bytes(arr)
1035 }
1036 None => return Ok(Vec::new()),
1037 };
1038
1039 let mut prefix = Vec::with_capacity(8);
1040 prefix.extend_from_slice(&type_id.to_be_bytes());
1041 prefix.extend_from_slice(&prop_key_id.to_be_bytes());
1042
1043 let min_encoded = min_val
1044 .map(|v| v.into_json())
1045 .as_ref()
1046 .and_then(encode_property_value);
1047 let max_encoded = max_val
1048 .map(|v| v.into_json())
1049 .as_ref()
1050 .and_then(encode_property_value);
1051
1052 let mut result = Vec::new();
1053 for entry in self.storage.edge_prop_idx.prefix_iter(rtxn, &prefix)? {
1054 let (key, _) = entry?;
1055 if key.len() >= prefix.len() + 8 {
1056 let val_bytes = &key[prefix.len()..key.len() - 8];
1057
1058 if let Some(ref min_enc) = min_encoded {
1059 if val_bytes < min_enc.as_slice() {
1060 continue;
1061 }
1062 }
1063 if let Some(ref max_enc) = max_encoded {
1064 if val_bytes > max_enc.as_slice() {
1065 continue;
1066 }
1067 }
1068
1069 let mut edge_id_bytes = [0u8; 8];
1070 edge_id_bytes.copy_from_slice(&key[key.len() - 8..]);
1071 result.push(u64::from_be_bytes(edge_id_bytes));
1072 }
1073 }
1074 Ok(result)
1075 }
1076
1077 pub fn list_node_indexes_and_constraints(&self) -> Result<Vec<(String, String, u8)>, Error> {
1078 let rtxn = self.storage.env.read_txn()?;
1079 let mut result = Vec::new();
1080 for entry in self.storage.meta.iter(&rtxn)? {
1081 let (key, val) = entry?;
1082 if let Some(rest) = key.strip_prefix("idx_meta:node:l:") {
1083 let parts: Vec<&str> = rest.split(":p:").collect();
1084 if parts.len() == 2 {
1085 if let (Ok(label_id), Ok(prop_key_id)) =
1086 (parts[0].parse::<u32>(), parts[1].parse::<u32>())
1087 {
1088 if let (Some(label_name), Some(prop_name)) = (
1089 self.label_name_impl(&rtxn, label_id)?,
1090 crate::storage::ids::get_prop_key_name(
1091 &self.storage,
1092 &rtxn,
1093 prop_key_id,
1094 )?,
1095 ) {
1096 let flags = val.first().copied().unwrap_or(0x00);
1097 result.push((label_name, prop_name, flags));
1098 }
1099 }
1100 }
1101 }
1102 }
1103 Ok(result)
1104 }
1105
1106 pub fn list_edge_indexes_and_constraints(&self) -> Result<Vec<(String, String, u8)>, Error> {
1107 let rtxn = self.storage.env.read_txn()?;
1108 let mut result = Vec::new();
1109 for entry in self.storage.meta.iter(&rtxn)? {
1110 let (key, val) = entry?;
1111 if let Some(rest) = key.strip_prefix("idx_meta:edge:t:") {
1112 let parts: Vec<&str> = rest.split(":p:").collect();
1113 if parts.len() == 2 {
1114 if let (Ok(type_id), Ok(prop_key_id)) =
1115 (parts[0].parse::<u32>(), parts[1].parse::<u32>())
1116 {
1117 if let (Some(type_name), Some(prop_name)) = (
1118 self.type_name_impl(&rtxn, type_id)?,
1119 crate::storage::ids::get_prop_key_name(
1120 &self.storage,
1121 &rtxn,
1122 prop_key_id,
1123 )?,
1124 ) {
1125 let flags = val.first().copied().unwrap_or(0x00);
1126 result.push((type_name, prop_name, flags));
1127 }
1128 }
1129 }
1130 }
1131 }
1132 Ok(result)
1133 }
1134}
1135
1136#[cfg(test)]
1137mod tests {
1138 use serde_json::json;
1139 use tempfile::TempDir;
1140
1141 use super::*;
1142
1143 fn open_tmp() -> (TempDir, Graph) {
1144 let dir = TempDir::new().unwrap();
1145 let g = Graph::open(dir.path(), 1).unwrap();
1146 (dir, g)
1147 }
1148
1149 #[test]
1152 fn drop_index_preserves_auto_index() {
1153 let (_dir, g) = open_tmp();
1154 let id = g.add_node("Person", &json!({"age": 30})).unwrap();
1155
1156 g.create_node_property_index("Person", "age").unwrap();
1157 g.drop_node_property_index("Person", "age").unwrap();
1158
1159 assert_eq!(
1160 g.nodes_by_property("Person", "age", PropValue::Int(30))
1161 .unwrap(),
1162 vec![id],
1163 "auto-index entries must survive dropping the explicit index"
1164 );
1165 }
1166
1167 #[test]
1171 fn nodes_by_property_finds_unindexed_long_string() {
1172 let (_dir, g) = open_tmp();
1173 let long = "word ".repeat(4000); let id = g
1175 .add_node("Post", &json!({ "body": long.clone() }))
1176 .unwrap();
1177 g.add_node("Post", &json!({ "body": "other ".repeat(4000) }))
1179 .unwrap();
1180
1181 assert_eq!(
1182 g.nodes_by_property("Post", "body", PropValue::Str(long))
1183 .unwrap(),
1184 vec![id],
1185 "equality lookup on an unindexed long string must scan and match"
1186 );
1187 }
1188
1189 #[test]
1192 fn drop_unique_constraint_preserves_lookups() {
1193 let (_dir, g) = open_tmp();
1194 let id = g.add_node("User", &json!({"email": "a@b.c"})).unwrap();
1195
1196 g.create_node_unique_constraint("User", "email").unwrap();
1197 g.drop_node_unique_constraint("User", "email").unwrap();
1198
1199 assert_eq!(
1200 g.nodes_by_property("User", "email", PropValue::Str("a@b.c".into()))
1201 .unwrap(),
1202 vec![id]
1203 );
1204
1205 let id2 = g.add_node("User", &json!({"email": "a@b.c"})).unwrap();
1208 let mut hits = g
1209 .nodes_by_property("User", "email", PropValue::Str("a@b.c".into()))
1210 .unwrap();
1211 hits.sort();
1212 let mut expected = vec![id, id2];
1213 expected.sort();
1214 assert_eq!(hits, expected);
1215 }
1216
1217 #[test]
1220 fn large_integer_property_no_false_match() {
1221 let (_dir, g) = open_tmp();
1222 let a = g
1223 .add_node("Item", &json!({"sid": 9_007_199_254_740_992_i64}))
1224 .unwrap();
1225 let b = g
1226 .add_node("Item", &json!({"sid": 9_007_199_254_740_993_i64}))
1227 .unwrap();
1228
1229 assert_eq!(
1230 g.nodes_by_property("Item", "sid", PropValue::Int(9_007_199_254_740_992))
1231 .unwrap(),
1232 vec![a]
1233 );
1234 assert_eq!(
1235 g.nodes_by_property("Item", "sid", PropValue::Int(9_007_199_254_740_993))
1236 .unwrap(),
1237 vec![b]
1238 );
1239 }
1240
1241 #[test]
1244 fn integer_property_matches_float_query() {
1245 let (_dir, g) = open_tmp();
1246 let id = g.add_node("Person", &json!({"age": 30})).unwrap();
1247 assert_eq!(
1248 g.nodes_by_property("Person", "age", PropValue::Float(30.0))
1249 .unwrap(),
1250 vec![id]
1251 );
1252 }
1253
1254 #[test]
1257 fn node_count_hint_is_high_water_mark() {
1258 let (_dir, g) = open_tmp();
1259 assert_eq!(g.node_count_hint().unwrap(), 0);
1260
1261 let a = g.add_node("N", &()).unwrap();
1262 g.add_node("N", &()).unwrap();
1263 assert_eq!(g.node_count_hint().unwrap(), 2);
1264
1265 g.delete_node(a).unwrap();
1266 assert_eq!(g.node_count_hint().unwrap(), 2);
1267 }
1268
1269 #[test]
1272 fn edge_property_index_lookup() {
1273 let (_dir, g) = open_tmp();
1274 let a = g.add_node("N", &()).unwrap();
1275 let b = g.add_node("N", &()).unwrap();
1276
1277 let e1 = g.add_edge(a, b, "ROAD", &json!({"cost": 5})).unwrap();
1279 g.create_edge_property_index("ROAD", "cost").unwrap();
1280
1281 let e2 = g.add_edge(b, a, "ROAD", &json!({"cost": 7})).unwrap();
1283
1284 assert_eq!(
1285 g.edges_by_property("ROAD", "cost", PropValue::Int(5))
1286 .unwrap(),
1287 vec![e1]
1288 );
1289 assert_eq!(
1290 g.edges_by_property("ROAD", "cost", PropValue::Int(7))
1291 .unwrap(),
1292 vec![e2]
1293 );
1294 assert_eq!(
1295 g.edges_by_property_range(
1296 "ROAD",
1297 "cost",
1298 Some(PropValue::Int(5)),
1299 Some(PropValue::Int(7)),
1300 )
1301 .unwrap(),
1302 vec![e1, e2]
1303 );
1304 }
1305
1306 #[test]
1307 fn drop_edge_property_index_removes_entries() {
1308 let (_dir, g) = open_tmp();
1309 let a = g.add_node("N", &()).unwrap();
1310 let b = g.add_node("N", &()).unwrap();
1311 g.create_edge_property_index("ROAD", "cost").unwrap();
1312 g.add_edge(a, b, "ROAD", &json!({"cost": 5})).unwrap();
1313
1314 g.drop_edge_property_index("ROAD", "cost").unwrap();
1315 assert_eq!(
1316 g.edges_by_property("ROAD", "cost", PropValue::Int(5))
1317 .unwrap(),
1318 Vec::<EdgeId>::new()
1319 );
1320 }
1321
1322 #[test]
1323 fn edge_unique_constraint_rejects_duplicate_insert() {
1324 let (_dir, g) = open_tmp();
1325 let a = g.add_node("N", &()).unwrap();
1326 let b = g.add_node("N", &()).unwrap();
1327 g.create_edge_unique_constraint("ROAD", "toll_id").unwrap();
1328
1329 g.add_edge(a, b, "ROAD", &json!({"toll_id": 1})).unwrap();
1330 let err = g
1331 .add_edge(b, a, "ROAD", &json!({"toll_id": 1}))
1332 .unwrap_err();
1333 assert!(matches!(err, Error::UniqueConstraintViolation(..)));
1334 }
1335
1336 #[test]
1337 fn edge_unique_constraint_rejects_existing_duplicates() {
1338 let (_dir, g) = open_tmp();
1339 let a = g.add_node("N", &()).unwrap();
1340 let b = g.add_node("N", &()).unwrap();
1341 g.add_edge(a, b, "ROAD", &json!({"toll_id": 1})).unwrap();
1342 g.add_edge(b, a, "ROAD", &json!({"toll_id": 1})).unwrap();
1343
1344 let err = g
1345 .create_edge_unique_constraint("ROAD", "toll_id")
1346 .unwrap_err();
1347 assert!(matches!(err, Error::UniqueConstraintViolation(..)));
1348 }
1349
1350 #[test]
1351 fn edge_required_constraint_rejects_missing_property() {
1352 let (_dir, g) = open_tmp();
1353 let a = g.add_node("N", &()).unwrap();
1354 let b = g.add_node("N", &()).unwrap();
1355 g.create_edge_required_constraint("ROAD", "cost").unwrap();
1356
1357 let err = g.add_edge(a, b, "ROAD", &json!({})).unwrap_err();
1358 assert!(matches!(err, Error::RequiredConstraintViolation(..)));
1359
1360 g.add_edge(a, b, "RAIL", &json!({})).unwrap();
1362 let err = g
1363 .create_edge_required_constraint("RAIL", "cost")
1364 .unwrap_err();
1365 assert!(matches!(err, Error::RequiredConstraintViolation(..)));
1366 }
1367
1368 #[test]
1371 fn update_edge_reindexes_edge_properties() {
1372 let (_dir, g) = open_tmp();
1373 let a = g.add_node("N", &()).unwrap();
1374 let b = g.add_node("N", &()).unwrap();
1375 g.create_edge_property_index("ROAD", "cost").unwrap();
1376 let eid = g.add_edge(a, b, "ROAD", &json!({"cost": 5})).unwrap();
1377
1378 g.update_edge(eid, &json!({"cost": 7})).unwrap();
1379
1380 assert_eq!(
1381 g.edges_by_property("ROAD", "cost", PropValue::Int(5))
1382 .unwrap(),
1383 Vec::<EdgeId>::new(),
1384 "stale index entry must be removed"
1385 );
1386 assert_eq!(
1387 g.edges_by_property("ROAD", "cost", PropValue::Int(7))
1388 .unwrap(),
1389 vec![eid],
1390 "new value must be indexed"
1391 );
1392 }
1393
1394 #[test]
1395 fn update_edge_enforces_unique_constraint() {
1396 let (_dir, g) = open_tmp();
1397 let a = g.add_node("N", &()).unwrap();
1398 let b = g.add_node("N", &()).unwrap();
1399 g.create_edge_unique_constraint("ROAD", "toll_id").unwrap();
1400 g.add_edge(a, b, "ROAD", &json!({"toll_id": 1})).unwrap();
1401 let e2 = g.add_edge(b, a, "ROAD", &json!({"toll_id": 2})).unwrap();
1402
1403 let err = g.update_edge(e2, &json!({"toll_id": 1})).unwrap_err();
1404 assert!(matches!(err, Error::UniqueConstraintViolation(..)));
1405
1406 g.update_edge(e2, &json!({"toll_id": 2})).unwrap();
1408 }
1409
1410 #[test]
1411 fn update_edge_enforces_required_constraint() {
1412 let (_dir, g) = open_tmp();
1413 let a = g.add_node("N", &()).unwrap();
1414 let b = g.add_node("N", &()).unwrap();
1415 g.create_edge_required_constraint("ROAD", "cost").unwrap();
1416 let eid = g.add_edge(a, b, "ROAD", &json!({"cost": 5})).unwrap();
1417
1418 let err = g.update_edge(eid, &json!({})).unwrap_err();
1419 assert!(matches!(err, Error::RequiredConstraintViolation(..)));
1420 }
1421}
1422
1423#[cfg(test)]
1424mod label_filter_tests {
1425 use serde_json::json;
1426 use tempfile::TempDir;
1427
1428 use crate::Graph;
1429
1430 fn open_tmp() -> (TempDir, Graph) {
1431 let dir = TempDir::new().unwrap();
1432 let g = Graph::open(dir.path(), 1).unwrap();
1433 (dir, g)
1434 }
1435
1436 #[test]
1437 fn label_filter_keeps_only_labeled_nodes() {
1438 let (_dir, g) = open_tmp();
1439 let a = g.add_node("Person", &json!({})).unwrap();
1440 let b = g.add_node("City", &json!({})).unwrap();
1441 let c = g.add_node_multi(&["City", "Person"], &json!({})).unwrap();
1442
1443 let filtered = g.label_filter(&[a, b, c], "Person").unwrap();
1444 assert_eq!(filtered.len(), 2);
1445 assert!(filtered.contains(&a));
1446 assert!(filtered.contains(&c));
1447 }
1448
1449 #[test]
1450 fn label_filter_unknown_label_is_empty() {
1451 let (_dir, g) = open_tmp();
1452 let a = g.add_node("Person", &json!({})).unwrap();
1453 assert!(g.label_filter(&[a], "Ghost").unwrap().is_empty());
1454 }
1455
1456 #[test]
1457 fn label_filter_sees_committed_writes_immediately() {
1458 let (_dir, g) = open_tmp();
1459 let a = g.add_node("Person", &json!({})).unwrap();
1460 g.add_label(a, "Admin").unwrap();
1461 assert_eq!(g.label_filter(&[a], "Admin").unwrap(), vec![a]);
1462 g.remove_label(a, "Admin").unwrap();
1463 assert!(g.label_filter(&[a], "Admin").unwrap().is_empty());
1464 }
1465}