1use super::*;
2
3impl ReadTxn<'_> {
4 pub fn get_node(&self, id: NodeId) -> Result<Option<NodeRecord>, Error> {
5 self.graph.get_node_impl(&self.rtxn, id)
6 }
7
8 pub fn get_edge(&self, id: EdgeId) -> Result<Option<EdgeRecord>, Error> {
9 self.graph.get_edge_impl(&self.rtxn, id)
10 }
11
12 pub fn out_neighbors(&self, node: NodeId) -> Result<Vec<NeighborEntry>, Error> {
13 self.graph.out_neighbors_impl(&self.rtxn, node)
14 }
15
16 pub fn in_neighbors(&self, node: NodeId) -> Result<Vec<NeighborEntry>, Error> {
17 self.graph.in_neighbors_impl(&self.rtxn, node)
18 }
19
20 pub fn nodes_by_label(&self, label: &str) -> Result<Vec<NodeId>, Error> {
21 self.graph.nodes_by_label_impl(&self.rtxn, label)
22 }
23
24 pub fn edges_by_type(&self, etype: &str) -> Result<Vec<EdgeId>, Error> {
25 self.graph.edges_by_type_impl(&self.rtxn, etype)
26 }
27
28 pub fn label_name(&self, id: LabelId) -> Result<Option<String>, Error> {
29 self.graph.label_name_impl(&self.rtxn, id)
30 }
31
32 pub fn type_name(&self, id: TypeId) -> Result<Option<String>, Error> {
33 self.graph.type_name_impl(&self.rtxn, id)
34 }
35
36 pub fn node_count_by_label(&self, label: &str) -> Result<u64, Error> {
37 self.graph.node_count_by_label_impl(&self.rtxn, label)
38 }
39
40 pub fn edge_count_by_type(&self, etype: &str) -> Result<u64, Error> {
41 self.graph.edge_count_by_type_impl(&self.rtxn, etype)
42 }
43
44 pub fn all_nodes(&self) -> Result<Vec<NodeId>, Error> {
45 self.graph.all_nodes_impl(&self.rtxn)
46 }
47
48 #[doc(hidden)]
49 pub fn vector_bytes(&self) -> Result<Vec<(NodeId, Vec<u8>)>, Error> {
50 self.graph.vector_bytes_impl(&self.rtxn)
51 }
52
53 #[doc(hidden)]
54 pub fn get_vector_bytes(&self, n: NodeId) -> Result<Option<Vec<u8>>, Error> {
55 self.graph.get_vector_bytes_impl(&self.rtxn, n)
56 }
57
58 pub fn has_node_property_index(&self, label: &str, property: &str) -> Result<bool, Error> {
59 self.graph
60 .has_node_property_index_impl(&self.rtxn, label, property)
61 }
62
63 pub fn nodes_by_property(
64 &self,
65 label: &str,
66 property: &str,
67 val: PropValue,
68 ) -> Result<Vec<NodeId>, Error> {
69 self.graph
70 .nodes_by_property_impl(&self.rtxn, label, property, val)
71 }
72
73 pub fn nodes_by_property_range(
74 &self,
75 label: &str,
76 property: &str,
77 min_val: Option<PropValue>,
78 min_inclusive: bool,
79 max_val: Option<PropValue>,
80 max_inclusive: bool,
81 ) -> Result<Vec<NodeId>, Error> {
82 self.graph.nodes_by_property_range_impl(
83 &self.rtxn,
84 label,
85 property,
86 min_val,
87 min_inclusive,
88 max_val,
89 max_inclusive,
90 )
91 }
92
93 pub fn edges_by_property(
94 &self,
95 etype: &str,
96 property: &str,
97 val: PropValue,
98 ) -> Result<Vec<EdgeId>, Error> {
99 self.graph
100 .edges_by_property_impl(&self.rtxn, etype, property, val)
101 }
102
103 pub fn edges_by_property_range(
104 &self,
105 etype: &str,
106 property: &str,
107 min_val: Option<PropValue>,
108 max_val: Option<PropValue>,
109 ) -> Result<Vec<EdgeId>, Error> {
110 self.graph
111 .edges_by_property_range_impl(&self.rtxn, etype, property, min_val, max_val)
112 }
113
114 #[doc(hidden)]
115 pub fn has_node_text_index(&self, label: &str, property: &str) -> Result<bool, Error> {
116 self.graph
117 .has_node_text_index_impl(&self.rtxn, label, property)
118 }
119
120 #[doc(hidden)]
121 pub fn fts_stats(&self, label: &str, property: &str) -> Result<Option<(u64, u64)>, Error> {
122 self.graph.fts_stats_impl(&self.rtxn, label, property)
123 }
124
125 #[doc(hidden)]
126 pub fn fts_doc_len(
127 &self,
128 label: &str,
129 property: &str,
130 node_id: NodeId,
131 ) -> Result<Option<u32>, Error> {
132 self.graph
133 .fts_doc_len_impl(&self.rtxn, label, property, node_id)
134 }
135
136 #[doc(hidden)]
137 pub fn fts_postings(
138 &self,
139 label: &str,
140 property: &str,
141 term: &str,
142 ) -> Result<Vec<(NodeId, u32)>, Error> {
143 self.graph
144 .fts_postings_impl(&self.rtxn, label, property, term)
145 }
146
147 #[doc(hidden)]
148 pub fn active_text_indexes(&self) -> Result<Vec<(String, String, Language)>, Error> {
149 self.graph.active_text_indexes_impl(&self.rtxn)
150 }
151}
152
153impl WriteTxn<'_> {
154 pub fn get_node(&self, id: NodeId) -> Result<Option<NodeRecord>, Error> {
155 self.graph.get_node_impl(&self.wtxn, id)
156 }
157
158 pub fn get_edge(&self, id: EdgeId) -> Result<Option<EdgeRecord>, Error> {
159 self.graph.get_edge_impl(&self.wtxn, id)
160 }
161
162 pub fn out_neighbors(&self, node: NodeId) -> Result<Vec<NeighborEntry>, Error> {
163 self.graph.out_neighbors_impl(&self.wtxn, node)
164 }
165
166 pub fn in_neighbors(&self, node: NodeId) -> Result<Vec<NeighborEntry>, Error> {
167 self.graph.in_neighbors_impl(&self.wtxn, node)
168 }
169
170 pub fn nodes_by_label(&self, label: &str) -> Result<Vec<NodeId>, Error> {
171 self.graph.nodes_by_label_impl(&self.wtxn, label)
172 }
173
174 pub fn edges_by_type(&self, etype: &str) -> Result<Vec<EdgeId>, Error> {
175 self.graph.edges_by_type_impl(&self.wtxn, etype)
176 }
177
178 pub fn label_name(&self, id: LabelId) -> Result<Option<String>, Error> {
179 self.graph.label_name_impl(&self.wtxn, id)
180 }
181
182 pub fn type_name(&self, id: TypeId) -> Result<Option<String>, Error> {
183 self.graph.type_name_impl(&self.wtxn, id)
184 }
185
186 pub fn node_count_by_label(&self, label: &str) -> Result<u64, Error> {
187 self.graph.node_count_by_label_impl(&self.wtxn, label)
188 }
189
190 pub fn edge_count_by_type(&self, etype: &str) -> Result<u64, Error> {
191 self.graph.edge_count_by_type_impl(&self.wtxn, etype)
192 }
193
194 pub fn all_nodes(&self) -> Result<Vec<NodeId>, Error> {
195 self.graph.all_nodes_impl(&self.wtxn)
196 }
197
198 #[doc(hidden)]
199 pub fn vector_bytes(&self) -> Result<Vec<(NodeId, Vec<u8>)>, Error> {
200 self.graph.vector_bytes_impl(&self.wtxn)
201 }
202
203 #[doc(hidden)]
204 pub fn get_vector_bytes(&self, n: NodeId) -> Result<Option<Vec<u8>>, Error> {
205 self.graph.get_vector_bytes_impl(&self.wtxn, n)
206 }
207
208 pub fn has_node_property_index(&self, label: &str, property: &str) -> Result<bool, Error> {
209 self.graph
210 .has_node_property_index_impl(&self.wtxn, label, property)
211 }
212
213 pub fn nodes_by_property(
214 &self,
215 label: &str,
216 property: &str,
217 val: PropValue,
218 ) -> Result<Vec<NodeId>, Error> {
219 self.graph
220 .nodes_by_property_impl(&self.wtxn, label, property, val)
221 }
222
223 pub fn nodes_by_property_range(
224 &self,
225 label: &str,
226 property: &str,
227 min_val: Option<PropValue>,
228 min_inclusive: bool,
229 max_val: Option<PropValue>,
230 max_inclusive: bool,
231 ) -> Result<Vec<NodeId>, Error> {
232 self.graph.nodes_by_property_range_impl(
233 &self.wtxn,
234 label,
235 property,
236 min_val,
237 min_inclusive,
238 max_val,
239 max_inclusive,
240 )
241 }
242
243 pub fn edges_by_property(
244 &self,
245 etype: &str,
246 property: &str,
247 val: PropValue,
248 ) -> Result<Vec<EdgeId>, Error> {
249 self.graph
250 .edges_by_property_impl(&self.wtxn, etype, property, val)
251 }
252
253 pub fn edges_by_property_range(
254 &self,
255 etype: &str,
256 property: &str,
257 min_val: Option<PropValue>,
258 max_val: Option<PropValue>,
259 ) -> Result<Vec<EdgeId>, Error> {
260 self.graph
261 .edges_by_property_range_impl(&self.wtxn, etype, property, min_val, max_val)
262 }
263
264 pub fn add_node(&mut self, label: &str, props: &impl Serialize) -> Result<NodeId, Error> {
265 let node_id = self.graph.add_node_impl(&mut self.wtxn, &[label], props)?;
266 self.mutations_count += 1;
267 self.delta.added_nodes.push(node_id);
268 Ok(node_id)
269 }
270
271 pub fn add_node_multi(
273 &mut self,
274 labels: &[&str],
275 props: &impl Serialize,
276 ) -> Result<NodeId, Error> {
277 let node_id = self.graph.add_node_impl(&mut self.wtxn, labels, props)?;
278 self.mutations_count += 1;
279 self.delta.added_nodes.push(node_id);
280 Ok(node_id)
281 }
282
283 pub fn update_node(&mut self, id: NodeId, props: &impl Serialize) -> Result<(), Error> {
284 self.graph.update_node_impl(&mut self.wtxn, id, props)?;
285 self.mutations_count += 1;
286 self.delta.updated_nodes.push(id);
287 Ok(())
288 }
289
290 pub fn add_label(&mut self, id: NodeId, label: &str) -> Result<(), Error> {
292 self.graph.add_label_impl(&mut self.wtxn, id, label)?;
293 self.mutations_count += 1;
294 Ok(())
295 }
296
297 pub fn remove_label(&mut self, id: NodeId, label: &str) -> Result<(), Error> {
299 self.graph.remove_label_impl(&mut self.wtxn, id, label)?;
300 self.mutations_count += 1;
301 Ok(())
302 }
303
304 pub fn delete_node(&mut self, id: NodeId) -> Result<(), Error> {
305 self.graph.delete_node_impl(&mut self.wtxn, id)?;
306 self.mutations_count += 1;
307 self.delta.force_full = true;
310 Ok(())
311 }
312
313 pub fn delete_edge(&mut self, id: EdgeId) -> Result<(), Error> {
314 if let Some((src, dst)) = self.graph.delete_edge_impl(&mut self.wtxn, id)? {
315 self.delta.removed_edges.push((src, dst));
316 }
317 self.mutations_count += 1;
318 Ok(())
319 }
320
321 pub fn add_edge(
322 &mut self,
323 src: NodeId,
324 dst: NodeId,
325 etype: &str,
326 props: &impl Serialize,
327 ) -> Result<EdgeId, Error> {
328 let edge_id = self
329 .graph
330 .add_edge_impl(&mut self.wtxn, src, dst, etype, props)?;
331 self.mutations_count += 1;
332 self.delta.added_edges.push((src, dst));
333 Ok(edge_id)
334 }
335
336 #[doc(hidden)]
337 pub fn put_vector_bytes(&mut self, n: NodeId, bytes: &[u8]) -> Result<(), Error> {
338 self.graph.put_vector_bytes_impl(&mut self.wtxn, n, bytes)?;
339 self.mutations_count += 1;
340 Ok(())
341 }
342
343 #[doc(hidden)]
345 pub fn delete_vector_bytes(&mut self, n: NodeId) -> Result<(), Error> {
346 self.graph.delete_vector_bytes_impl(&mut self.wtxn, n)?;
347 self.mutations_count += 1;
348 Ok(())
349 }
350
351 #[doc(hidden)]
352 pub fn create_node_text_index(&mut self, label: &str, property: &str) -> Result<(), Error> {
353 self.graph.create_node_text_index_impl(
354 &mut self.wtxn,
355 label,
356 property,
357 Language::English,
358 )?;
359 self.mutations_count += 1;
360 Ok(())
361 }
362
363 #[doc(hidden)]
364 pub fn drop_node_text_index(&mut self, label: &str, property: &str) -> Result<(), Error> {
365 self.graph
366 .drop_node_text_index_impl(&mut self.wtxn, label, property)?;
367 self.mutations_count += 1;
368 Ok(())
369 }
370
371 #[doc(hidden)]
372 pub fn has_node_text_index(&self, label: &str, property: &str) -> Result<bool, Error> {
373 let rtxn: &heed::RoTxn = &self.wtxn;
374 self.graph.has_node_text_index_impl(rtxn, label, property)
375 }
376
377 #[doc(hidden)]
378 pub fn fts_stats(&self, label: &str, property: &str) -> Result<Option<(u64, u64)>, Error> {
379 let rtxn: &heed::RoTxn = &self.wtxn;
380 self.graph.fts_stats_impl(rtxn, label, property)
381 }
382
383 #[doc(hidden)]
384 pub fn fts_doc_len(
385 &self,
386 label: &str,
387 property: &str,
388 node_id: NodeId,
389 ) -> Result<Option<u32>, Error> {
390 let rtxn: &heed::RoTxn = &self.wtxn;
391 self.graph.fts_doc_len_impl(rtxn, label, property, node_id)
392 }
393
394 #[doc(hidden)]
395 pub fn fts_postings(
396 &self,
397 label: &str,
398 property: &str,
399 term: &str,
400 ) -> Result<Vec<(NodeId, u32)>, Error> {
401 let rtxn: &heed::RoTxn = &self.wtxn;
402 self.graph.fts_postings_impl(rtxn, label, property, term)
403 }
404
405 #[doc(hidden)]
406 pub fn create_node_text_index_with_language(
407 &mut self,
408 label: &str,
409 property: &str,
410 lang: Language,
411 ) -> Result<(), Error> {
412 self.graph
413 .create_node_text_index_impl(&mut self.wtxn, label, property, lang)?;
414 self.mutations_count += 1;
415 Ok(())
416 }
417
418 #[doc(hidden)]
419 pub fn active_text_indexes(&self) -> Result<Vec<(String, String, Language)>, Error> {
420 let rtxn: &heed::RoTxn = &self.wtxn;
421 self.graph.active_text_indexes_impl(rtxn)
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use serde_json::json;
428 use tempfile::TempDir;
429
430 use super::*;
431
432 fn open_tmp() -> (TempDir, Graph) {
433 let dir = TempDir::new().unwrap();
434 let g = Graph::open(dir.path(), 1).unwrap();
435 (dir, g)
436 }
437
438 #[test]
439 fn test_transaction_read_only() {
440 let (_dir, g) = open_tmp();
441 let a = g.add_node("Person", &json!({"name": "Alice"})).unwrap();
442 let b = g.add_node("Person", &json!({"name": "Bob"})).unwrap();
443
444 g.view(|txn| {
445 let node_a = txn.get_node(a).unwrap().unwrap();
446 let props_a: serde_json::Value = rmp_serde::from_slice(&node_a.props).unwrap();
447 assert_eq!(props_a["name"], "Alice");
448
449 let node_b = txn.get_node(b).unwrap().unwrap();
450 let props_b: serde_json::Value = rmp_serde::from_slice(&node_b.props).unwrap();
451 assert_eq!(props_b["name"], "Bob");
452
453 let nodes = txn.all_nodes().unwrap();
454 assert_eq!(nodes.len(), 2);
455
456 Ok(())
457 })
458 .unwrap();
459 }
460
461 #[test]
462 fn test_transaction_write_commit() {
463 let (_dir, g) = open_tmp();
464
465 let (a, b) = g
466 .update(|txn| {
467 let a = txn.add_node("Person", &json!({"name": "Alice"})).unwrap();
468 let b = txn.add_node("Person", &json!({"name": "Bob"})).unwrap();
469 txn.add_edge(a, b, "KNOWS", &json!({"since": 2020}))
470 .unwrap();
471 Ok((a, b))
472 })
473 .unwrap();
474
475 let node_a = g.get_node(a).unwrap().unwrap();
477 let props_a: serde_json::Value = rmp_serde::from_slice(&node_a.props).unwrap();
478 assert_eq!(props_a["name"], "Alice");
479
480 let neighbors = g.out_neighbors(a).unwrap();
481 assert_eq!(neighbors.len(), 1);
482 assert_eq!(neighbors[0].node, b);
483 }
484
485 #[test]
486 fn test_transaction_write_rollback() {
487 let (_dir, g) = open_tmp();
488
489 let res: Result<(), Error> = g.update(|txn| {
490 txn.add_node("Person", &json!({"name": "Alice"})).unwrap();
491 Err(Error::Corrupt("simulated failure"))
493 });
494
495 assert!(res.is_err());
496
497 let nodes = g.all_nodes().unwrap();
499 assert_eq!(nodes.len(), 0);
500 }
501
502 #[test]
508 fn graphblas_multi_source_empty_seeds_returns_empty() {
509 let (_dir, g) = open_tmp();
510 g.add_node("N", &json!({})).unwrap();
511 g.rebuild_csr().unwrap();
512 let result = g.bfs_multi_source_graphblas(&[], 2, None).unwrap();
513 assert!(result.is_empty());
514 }
515
516 #[test]
517 fn graphblas_multi_source_hops_zero_returns_only_seeds() {
518 let (_dir, g) = open_tmp();
519 let a = g.add_node("N", &json!({})).unwrap();
520 let b = g.add_node("N", &json!({})).unwrap();
521 let c = g.add_node("N", &json!({})).unwrap();
522 g.add_edge(a, c, "E", &json!({})).unwrap();
523 g.rebuild_csr().unwrap();
524
525 let mut result = g.bfs_multi_source_graphblas(&[a, b], 0, None).unwrap();
526 result.sort_unstable();
527 assert_eq!(result, vec![a, b]);
528 assert!(!result.contains(&c));
529 }
530
531 #[test]
532 fn graphblas_multi_source_expands_to_correct_depth() {
533 let (_dir, g) = open_tmp();
534 let a = g.add_node("N", &json!({})).unwrap();
536 let b = g.add_node("N", &json!({})).unwrap();
537 let c = g.add_node("N", &json!({})).unwrap();
538 let d = g.add_node("N", &json!({})).unwrap();
539 g.add_edge(a, b, "E", &json!({})).unwrap();
540 g.add_edge(b, c, "E", &json!({})).unwrap();
541 g.add_edge(c, d, "E", &json!({})).unwrap();
542 g.rebuild_csr().unwrap();
543
544 let r1 = g.bfs_multi_source_graphblas(&[a], 1, None).unwrap();
545 assert!(r1.contains(&a));
546 assert!(r1.contains(&b));
547 assert!(!r1.contains(&c));
548 assert!(!r1.contains(&d));
549
550 let r2 = g.bfs_multi_source_graphblas(&[a], 2, None).unwrap();
551 assert!(r2.contains(&a));
552 assert!(r2.contains(&b));
553 assert!(r2.contains(&c));
554 assert!(!r2.contains(&d));
555 }
556
557 #[test]
558 fn graphblas_multi_source_max_nodes_cap_respected() {
559 let (_dir, g) = open_tmp();
560 let a = g.add_node("N", &json!({})).unwrap();
562 let b = g.add_node("N", &json!({})).unwrap();
563 let c = g.add_node("N", &json!({})).unwrap();
564 let d = g.add_node("N", &json!({})).unwrap();
565 let e = g.add_node("N", &json!({})).unwrap();
566 g.add_edge(a, b, "E", &json!({})).unwrap();
567 g.add_edge(a, c, "E", &json!({})).unwrap();
568 g.add_edge(a, d, "E", &json!({})).unwrap();
569 g.add_edge(b, e, "E", &json!({})).unwrap();
570 g.rebuild_csr().unwrap();
571
572 let result = g.bfs_multi_source_graphblas(&[a], 2, Some(3)).unwrap();
573 assert!(
574 result.len() <= 3,
575 "expected at most 3 nodes, got {}",
576 result.len()
577 );
578 }
579
580 #[test]
581 fn graphblas_multi_source_two_seeds_union_disconnected_components() {
582 let (_dir, g) = open_tmp();
583 let a = g.add_node("N", &json!({})).unwrap();
585 let b = g.add_node("N", &json!({})).unwrap();
586 let c = g.add_node("N", &json!({})).unwrap();
587 let d = g.add_node("N", &json!({})).unwrap();
588 g.add_edge(a, b, "E", &json!({})).unwrap();
589 g.add_edge(c, d, "E", &json!({})).unwrap();
590 g.rebuild_csr().unwrap();
591
592 let result = g.bfs_multi_source_graphblas(&[a, c], 1, None).unwrap();
593 assert!(result.contains(&a));
594 assert!(result.contains(&b));
595 assert!(result.contains(&c));
596 assert!(result.contains(&d));
597 }
598
599 #[test]
600 fn graphblas_multi_source_deduplicates_shared_neighbors() {
601 let (_dir, g) = open_tmp();
602 let a = g.add_node("N", &json!({})).unwrap();
604 let b = g.add_node("N", &json!({})).unwrap();
605 let c = g.add_node("N", &json!({})).unwrap();
606 g.add_edge(a, c, "E", &json!({})).unwrap();
607 g.add_edge(b, c, "E", &json!({})).unwrap();
608 g.rebuild_csr().unwrap();
609
610 let result = g.bfs_multi_source_graphblas(&[a, b], 1, None).unwrap();
611 let count_c = result.iter().filter(|&&n| n == c).count();
612 assert_eq!(count_c, 1);
613 assert_eq!(result.len(), 3); }
615
616 #[test]
617 fn graphblas_multi_source_handles_newly_added_seeds_via_dynamic_materialization() {
618 let (_dir, g) = open_tmp();
619 let a = g.add_node("N", &json!({})).unwrap();
622 let c = g.add_node("N", &json!({})).unwrap();
623 g.add_edge(a, c, "E", &json!({})).unwrap();
624 g.rebuild_csr().unwrap();
625
626 let b = g.add_node("N", &json!({})).unwrap();
628 let d = g.add_node("N", &json!({})).unwrap();
629 g.add_edge(b, d, "E", &json!({})).unwrap();
630
631 let result = g.bfs_multi_source_graphblas(&[a, b], 1, None).unwrap();
633 assert!(result.contains(&a), "seed a must be present");
634 assert!(result.contains(&b), "seed b must be present");
635 assert!(result.contains(&c), "c reachable from a");
636 assert!(result.contains(&d), "d reachable from b");
637 }
638
639 #[test]
642 fn label_count_increments_on_add_node() {
643 let (_dir, g) = open_tmp();
644 assert_eq!(g.node_count_by_label("Person").unwrap(), 0);
645 g.add_node("Person", &json!({})).unwrap();
646 assert_eq!(g.node_count_by_label("Person").unwrap(), 1);
647 g.add_node("Person", &json!({})).unwrap();
648 assert_eq!(g.node_count_by_label("Person").unwrap(), 2);
649 assert_eq!(g.node_count_by_label("Company").unwrap(), 0);
651 }
652
653 #[test]
654 fn label_count_decrements_on_delete_node() {
655 let (_dir, g) = open_tmp();
656 let a = g.add_node("Person", &json!({})).unwrap();
657 let b = g.add_node("Person", &json!({})).unwrap();
658 assert_eq!(g.node_count_by_label("Person").unwrap(), 2);
659
660 g.delete_node(a).unwrap();
661 assert_eq!(g.node_count_by_label("Person").unwrap(), 1);
662
663 g.delete_node(b).unwrap();
664 assert_eq!(g.node_count_by_label("Person").unwrap(), 0);
665
666 g.delete_node(b).unwrap();
668 assert_eq!(g.node_count_by_label("Person").unwrap(), 0);
669 }
670
671 #[test]
672 fn label_count_unchanged_on_update_node() {
673 let (_dir, g) = open_tmp();
674 let id = g.add_node("Person", &json!({})).unwrap();
675 assert_eq!(g.node_count_by_label("Person").unwrap(), 1);
676
677 g.update_node(id, &json!({"name": "Alice"})).unwrap();
679 assert_eq!(g.node_count_by_label("Person").unwrap(), 1);
680 }
681
682 #[test]
683 fn update_node_returns_not_found_for_missing_node() {
684 let (_dir, g) = open_tmp();
685 let res = g.update_node(9999, &json!({}));
686 assert!(matches!(res, Err(Error::NodeNotFound(9999))));
687 }
688
689 #[test]
690 fn type_count_increments_on_add_edge() {
691 let (_dir, g) = open_tmp();
692 let a = g.add_node("N", &json!({})).unwrap();
693 let b = g.add_node("N", &json!({})).unwrap();
694 let c = g.add_node("N", &json!({})).unwrap();
695 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 0);
696
697 g.add_edge(a, b, "KNOWS", &json!({})).unwrap();
698 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 1);
699
700 g.add_edge(b, c, "KNOWS", &json!({})).unwrap();
701 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 2);
702
703 assert_eq!(g.edge_count_by_type("WORKS_AT").unwrap(), 0);
705 }
706
707 #[test]
708 fn type_count_decrements_on_delete_node_cascade() {
709 let (_dir, g) = open_tmp();
710 let a = g.add_node("N", &json!({})).unwrap();
711 let b = g.add_node("N", &json!({})).unwrap();
712 g.add_edge(a, b, "KNOWS", &json!({})).unwrap();
713 g.add_edge(b, a, "KNOWS", &json!({})).unwrap();
714 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 2);
715
716 g.delete_node(a).unwrap();
718 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 0);
719 }
720
721 #[test]
722 fn delete_edge_correctness() {
723 let (_dir, g) = open_tmp();
724 let a = g.add_node("Person", &json!({})).unwrap();
725 let b = g.add_node("Person", &json!({})).unwrap();
726 let eid = g.add_edge(a, b, "KNOWS", &json!({})).unwrap();
727
728 assert!(g.get_edge(eid).unwrap().is_some());
730 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 1);
731
732 let out_neighs = g.out_neighbors(a).unwrap();
734 assert_eq!(out_neighs.len(), 1);
735 assert_eq!(out_neighs[0].node, b);
736 assert_eq!(out_neighs[0].edge, eid);
737
738 let in_neighs = g.in_neighbors(b).unwrap();
739 assert_eq!(in_neighs.len(), 1);
740 assert_eq!(in_neighs[0].node, a);
741 assert_eq!(in_neighs[0].edge, eid);
742
743 g.delete_edge(eid).unwrap();
745
746 assert!(g.get_edge(eid).unwrap().is_none());
748 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 0);
749
750 assert_eq!(g.out_neighbors(a).unwrap().len(), 0);
752 assert_eq!(g.in_neighbors(b).unwrap().len(), 0);
753
754 g.delete_edge(eid).unwrap();
756 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 0);
757 }
758
759 #[test]
760 fn test_node_property_secondary_index_and_scans() {
761 let (_dir, g) = open_tmp();
762
763 let n1 = g
765 .add_node("Person", &json!({"name": "Alice", "age": 30}))
766 .unwrap();
767 let n2 = g
768 .add_node("Person", &json!({"name": "Bob", "age": 25}))
769 .unwrap();
770 let n3 = g
771 .add_node("Person", &json!({"name": "Charlie", "age": 30}))
772 .unwrap();
773 let _n4 = g
774 .add_node("Employee", &json!({"name": "Alice", "age": 40}))
775 .unwrap();
776
777 g.create_node_property_index("Person", "age").unwrap();
779
780 assert!(g.has_node_property_index("Person", "age").unwrap());
782
783 let p30 = g
785 .nodes_by_property("Person", "age", PropValue::Int(30))
786 .unwrap();
787 assert_eq!(p30.len(), 2);
788 assert!(p30.contains(&n1));
789 assert!(p30.contains(&n3));
790
791 let p25 = g
792 .nodes_by_property("Person", "age", PropValue::Int(25))
793 .unwrap();
794 assert_eq!(p25.len(), 1);
795 assert!(p25.contains(&n2));
796
797 let pr = g
799 .nodes_by_property_range(
800 "Person",
801 "age",
802 Some(PropValue::Int(20)),
803 true,
804 Some(PropValue::Int(28)),
805 true,
806 )
807 .unwrap();
808 assert_eq!(pr.len(), 1);
809 assert!(pr.contains(&n2));
810
811 g.create_node_property_index("Person", "name").unwrap();
813 let p_alice = g
814 .nodes_by_property("Person", "name", PropValue::Str("Alice".to_string()))
815 .unwrap();
816 assert_eq!(p_alice.len(), 1);
817 assert!(p_alice.contains(&n1));
818 }
819
820 #[test]
821 fn test_unique_property_constraint() {
822 let (_dir, g) = open_tmp();
823
824 g.create_node_unique_constraint("User", "email").unwrap();
826
827 let _u1 = g
829 .add_node(
830 "User",
831 &json!({"email": "user1@example.com", "name": "User 1"}),
832 )
833 .unwrap();
834
835 let res2 = g.add_node(
837 "User",
838 &json!({"email": "user1@example.com", "name": "User 2"}),
839 );
840 assert!(res2.is_err());
841 assert!(matches!(
842 res2.unwrap_err(),
843 Error::UniqueConstraintViolation { .. }
844 ));
845
846 let u2 = g
848 .add_node(
849 "User",
850 &json!({"email": "user2@example.com", "name": "User 2"}),
851 )
852 .unwrap();
853
854 let update_res =
856 g.update_node(u2, &json!({"email": "user1@example.com", "name": "User 2"}));
857 assert!(update_res.is_err());
858 assert!(matches!(
859 update_res.unwrap_err(),
860 Error::UniqueConstraintViolation { .. }
861 ));
862 }
863
864 #[test]
865 fn test_required_property_constraint() {
866 let (_dir, g) = open_tmp();
867
868 g.create_node_required_constraint("Task", "title").unwrap();
870
871 let t1 = g
873 .add_node("Task", &json!({"title": "Do homework", "done": false}))
874 .unwrap();
875
876 let res2 = g.add_node("Task", &json!({"done": false}));
878 assert!(res2.is_err());
879 assert!(matches!(
880 res2.unwrap_err(),
881 Error::RequiredConstraintViolation { .. }
882 ));
883
884 let update_res = g.update_node(t1, &json!({"done": true}));
886 assert!(update_res.is_err());
887 assert!(matches!(
888 update_res.unwrap_err(),
889 Error::RequiredConstraintViolation { .. }
890 ));
891 }
892
893 #[test]
894 fn test_index_cleanup_on_delete() {
895 let (_dir, g) = open_tmp();
896
897 g.create_node_unique_constraint("Account", "number")
899 .unwrap();
900
901 let a1 = g.add_node("Account", &json!({"number": "12345"})).unwrap();
902
903 g.delete_node(a1).unwrap();
905
906 let a2 = g.add_node("Account", &json!({"number": "12345"}));
908 assert!(a2.is_ok());
909 }
910
911 #[test]
912 fn backup_and_restore_roundtrip() {
913 let dir = TempDir::new().unwrap();
914 let backup_file = dir.path().join("snapshot.mdb");
915 let restore_dir = dir.path().join("restored");
916
917 let n;
919 {
920 let g = Graph::open(&dir.path().join("primary"), 1).unwrap();
921 n = g
922 .add_node("BackupTest", &serde_json::json!({"x": 42}))
923 .unwrap();
924 g.backup(&backup_file).unwrap();
925 }
926
927 Graph::restore(&backup_file, &restore_dir).unwrap();
929 let g2 = Graph::open(&restore_dir, 1).unwrap();
930 let rec = g2
931 .get_node(n)
932 .unwrap()
933 .expect("node must exist in restored graph");
934 let props: serde_json::Value = rmp_serde::from_slice(&rec.props).unwrap();
935 assert_eq!(props["x"], serde_json::json!(42));
936 }
937
938 #[test]
939 fn backup_compact_and_restore_roundtrip() {
940 let dir = TempDir::new().unwrap();
941 let backup_file = dir.path().join("compact.mdb");
942 let restore_dir = dir.path().join("restored");
943
944 let kept;
946 {
947 let g = Graph::open(&dir.path().join("primary"), 1).unwrap();
948 let doomed = g
949 .add_node("BackupTest", &serde_json::json!({"x": 1}))
950 .unwrap();
951 kept = g
952 .add_node("BackupTest", &serde_json::json!({"x": 42}))
953 .unwrap();
954 g.delete_node(doomed).unwrap();
955 g.backup_compact(&backup_file).unwrap();
956 }
957
958 Graph::restore(&backup_file, &restore_dir).unwrap();
960 let g2 = Graph::open(&restore_dir, 1).unwrap();
961 let rec = g2
962 .get_node(kept)
963 .unwrap()
964 .expect("node must exist in restored graph");
965 let props: serde_json::Value = rmp_serde::from_slice(&rec.props).unwrap();
966 assert_eq!(props["x"], serde_json::json!(42));
967 assert_eq!(g2.nodes_by_label("BackupTest").unwrap(), vec![kept]);
968 }
969}