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 self.delta.added_edge_ids.push(edge_id);
334 Ok(edge_id)
335 }
336
337 #[doc(hidden)]
338 pub fn put_vector_bytes(&mut self, n: NodeId, bytes: &[u8]) -> Result<(), Error> {
339 self.graph.put_vector_bytes_impl(&mut self.wtxn, n, bytes)?;
340 self.mutations_count += 1;
341 Ok(())
342 }
343
344 #[doc(hidden)]
346 pub fn delete_vector_bytes(&mut self, n: NodeId) -> Result<(), Error> {
347 self.graph.delete_vector_bytes_impl(&mut self.wtxn, n)?;
348 self.mutations_count += 1;
349 Ok(())
350 }
351
352 #[doc(hidden)]
353 pub fn create_node_text_index(&mut self, label: &str, property: &str) -> Result<(), Error> {
354 self.graph.create_node_text_index_impl(
355 &mut self.wtxn,
356 label,
357 property,
358 Language::English,
359 )?;
360 self.mutations_count += 1;
361 Ok(())
362 }
363
364 #[doc(hidden)]
365 pub fn drop_node_text_index(&mut self, label: &str, property: &str) -> Result<(), Error> {
366 self.graph
367 .drop_node_text_index_impl(&mut self.wtxn, label, property)?;
368 self.mutations_count += 1;
369 Ok(())
370 }
371
372 #[doc(hidden)]
373 pub fn has_node_text_index(&self, label: &str, property: &str) -> Result<bool, Error> {
374 let rtxn: &heed::RoTxn = &self.wtxn;
375 self.graph.has_node_text_index_impl(rtxn, label, property)
376 }
377
378 #[doc(hidden)]
379 pub fn fts_stats(&self, label: &str, property: &str) -> Result<Option<(u64, u64)>, Error> {
380 let rtxn: &heed::RoTxn = &self.wtxn;
381 self.graph.fts_stats_impl(rtxn, label, property)
382 }
383
384 #[doc(hidden)]
385 pub fn fts_doc_len(
386 &self,
387 label: &str,
388 property: &str,
389 node_id: NodeId,
390 ) -> Result<Option<u32>, Error> {
391 let rtxn: &heed::RoTxn = &self.wtxn;
392 self.graph.fts_doc_len_impl(rtxn, label, property, node_id)
393 }
394
395 #[doc(hidden)]
396 pub fn fts_postings(
397 &self,
398 label: &str,
399 property: &str,
400 term: &str,
401 ) -> Result<Vec<(NodeId, u32)>, Error> {
402 let rtxn: &heed::RoTxn = &self.wtxn;
403 self.graph.fts_postings_impl(rtxn, label, property, term)
404 }
405
406 #[doc(hidden)]
407 pub fn create_node_text_index_with_language(
408 &mut self,
409 label: &str,
410 property: &str,
411 lang: Language,
412 ) -> Result<(), Error> {
413 self.graph
414 .create_node_text_index_impl(&mut self.wtxn, label, property, lang)?;
415 self.mutations_count += 1;
416 Ok(())
417 }
418
419 #[doc(hidden)]
420 pub fn active_text_indexes(&self) -> Result<Vec<(String, String, Language)>, Error> {
421 let rtxn: &heed::RoTxn = &self.wtxn;
422 self.graph.active_text_indexes_impl(rtxn)
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use serde_json::json;
429 use tempfile::TempDir;
430
431 use super::*;
432
433 fn open_tmp() -> (TempDir, Graph) {
434 let dir = TempDir::new().unwrap();
435 let g = Graph::open(dir.path(), 1).unwrap();
436 (dir, g)
437 }
438
439 #[test]
440 fn test_transaction_read_only() {
441 let (_dir, g) = open_tmp();
442 let a = g.add_node("Person", &json!({"name": "Alice"})).unwrap();
443 let b = g.add_node("Person", &json!({"name": "Bob"})).unwrap();
444
445 g.view(|txn| {
446 let node_a = txn.get_node(a).unwrap().unwrap();
447 let props_a: serde_json::Value = rmp_serde::from_slice(&node_a.props).unwrap();
448 assert_eq!(props_a["name"], "Alice");
449
450 let node_b = txn.get_node(b).unwrap().unwrap();
451 let props_b: serde_json::Value = rmp_serde::from_slice(&node_b.props).unwrap();
452 assert_eq!(props_b["name"], "Bob");
453
454 let nodes = txn.all_nodes().unwrap();
455 assert_eq!(nodes.len(), 2);
456
457 Ok(())
458 })
459 .unwrap();
460 }
461
462 #[test]
463 fn test_transaction_write_commit() {
464 let (_dir, g) = open_tmp();
465
466 let (a, b) = g
467 .update(|txn| {
468 let a = txn.add_node("Person", &json!({"name": "Alice"})).unwrap();
469 let b = txn.add_node("Person", &json!({"name": "Bob"})).unwrap();
470 txn.add_edge(a, b, "KNOWS", &json!({"since": 2020}))
471 .unwrap();
472 Ok((a, b))
473 })
474 .unwrap();
475
476 let node_a = g.get_node(a).unwrap().unwrap();
478 let props_a: serde_json::Value = rmp_serde::from_slice(&node_a.props).unwrap();
479 assert_eq!(props_a["name"], "Alice");
480
481 let neighbors = g.out_neighbors(a).unwrap();
482 assert_eq!(neighbors.len(), 1);
483 assert_eq!(neighbors[0].node, b);
484 }
485
486 #[test]
487 fn test_transaction_write_rollback() {
488 let (_dir, g) = open_tmp();
489
490 let res: Result<(), Error> = g.update(|txn| {
491 txn.add_node("Person", &json!({"name": "Alice"})).unwrap();
492 Err(Error::Corrupt("simulated failure"))
494 });
495
496 assert!(res.is_err());
497
498 let nodes = g.all_nodes().unwrap();
500 assert_eq!(nodes.len(), 0);
501 }
502
503 #[test]
509 fn graphblas_multi_source_empty_seeds_returns_empty() {
510 let (_dir, g) = open_tmp();
511 g.add_node("N", &json!({})).unwrap();
512 g.rebuild_csr().unwrap();
513 let result = g.bfs_multi_source_graphblas(&[], 2, None).unwrap();
514 assert!(result.is_empty());
515 }
516
517 #[test]
518 fn graphblas_multi_source_hops_zero_returns_only_seeds() {
519 let (_dir, g) = open_tmp();
520 let a = g.add_node("N", &json!({})).unwrap();
521 let b = g.add_node("N", &json!({})).unwrap();
522 let c = g.add_node("N", &json!({})).unwrap();
523 g.add_edge(a, c, "E", &json!({})).unwrap();
524 g.rebuild_csr().unwrap();
525
526 let mut result = g.bfs_multi_source_graphblas(&[a, b], 0, None).unwrap();
527 result.sort_unstable();
528 assert_eq!(result, vec![a, b]);
529 assert!(!result.contains(&c));
530 }
531
532 #[test]
533 fn graphblas_multi_source_expands_to_correct_depth() {
534 let (_dir, g) = open_tmp();
535 let a = g.add_node("N", &json!({})).unwrap();
537 let b = g.add_node("N", &json!({})).unwrap();
538 let c = g.add_node("N", &json!({})).unwrap();
539 let d = g.add_node("N", &json!({})).unwrap();
540 g.add_edge(a, b, "E", &json!({})).unwrap();
541 g.add_edge(b, c, "E", &json!({})).unwrap();
542 g.add_edge(c, d, "E", &json!({})).unwrap();
543 g.rebuild_csr().unwrap();
544
545 let r1 = g.bfs_multi_source_graphblas(&[a], 1, None).unwrap();
546 assert!(r1.contains(&a));
547 assert!(r1.contains(&b));
548 assert!(!r1.contains(&c));
549 assert!(!r1.contains(&d));
550
551 let r2 = g.bfs_multi_source_graphblas(&[a], 2, None).unwrap();
552 assert!(r2.contains(&a));
553 assert!(r2.contains(&b));
554 assert!(r2.contains(&c));
555 assert!(!r2.contains(&d));
556 }
557
558 #[test]
559 fn graphblas_multi_source_max_nodes_cap_respected() {
560 let (_dir, g) = open_tmp();
561 let a = g.add_node("N", &json!({})).unwrap();
563 let b = g.add_node("N", &json!({})).unwrap();
564 let c = g.add_node("N", &json!({})).unwrap();
565 let d = g.add_node("N", &json!({})).unwrap();
566 let e = g.add_node("N", &json!({})).unwrap();
567 g.add_edge(a, b, "E", &json!({})).unwrap();
568 g.add_edge(a, c, "E", &json!({})).unwrap();
569 g.add_edge(a, d, "E", &json!({})).unwrap();
570 g.add_edge(b, e, "E", &json!({})).unwrap();
571 g.rebuild_csr().unwrap();
572
573 let result = g.bfs_multi_source_graphblas(&[a], 2, Some(3)).unwrap();
574 assert!(
575 result.len() <= 3,
576 "expected at most 3 nodes, got {}",
577 result.len()
578 );
579 }
580
581 #[test]
582 fn graphblas_multi_source_two_seeds_union_disconnected_components() {
583 let (_dir, g) = open_tmp();
584 let a = g.add_node("N", &json!({})).unwrap();
586 let b = g.add_node("N", &json!({})).unwrap();
587 let c = g.add_node("N", &json!({})).unwrap();
588 let d = g.add_node("N", &json!({})).unwrap();
589 g.add_edge(a, b, "E", &json!({})).unwrap();
590 g.add_edge(c, d, "E", &json!({})).unwrap();
591 g.rebuild_csr().unwrap();
592
593 let result = g.bfs_multi_source_graphblas(&[a, c], 1, None).unwrap();
594 assert!(result.contains(&a));
595 assert!(result.contains(&b));
596 assert!(result.contains(&c));
597 assert!(result.contains(&d));
598 }
599
600 #[test]
601 fn graphblas_multi_source_deduplicates_shared_neighbors() {
602 let (_dir, g) = open_tmp();
603 let a = g.add_node("N", &json!({})).unwrap();
605 let b = g.add_node("N", &json!({})).unwrap();
606 let c = g.add_node("N", &json!({})).unwrap();
607 g.add_edge(a, c, "E", &json!({})).unwrap();
608 g.add_edge(b, c, "E", &json!({})).unwrap();
609 g.rebuild_csr().unwrap();
610
611 let result = g.bfs_multi_source_graphblas(&[a, b], 1, None).unwrap();
612 let count_c = result.iter().filter(|&&n| n == c).count();
613 assert_eq!(count_c, 1);
614 assert_eq!(result.len(), 3); }
616
617 #[test]
618 fn graphblas_multi_source_handles_newly_added_seeds_via_dynamic_materialization() {
619 let (_dir, g) = open_tmp();
620 let a = g.add_node("N", &json!({})).unwrap();
623 let c = g.add_node("N", &json!({})).unwrap();
624 g.add_edge(a, c, "E", &json!({})).unwrap();
625 g.rebuild_csr().unwrap();
626
627 let b = g.add_node("N", &json!({})).unwrap();
629 let d = g.add_node("N", &json!({})).unwrap();
630 g.add_edge(b, d, "E", &json!({})).unwrap();
631
632 let result = g.bfs_multi_source_graphblas(&[a, b], 1, None).unwrap();
634 assert!(result.contains(&a), "seed a must be present");
635 assert!(result.contains(&b), "seed b must be present");
636 assert!(result.contains(&c), "c reachable from a");
637 assert!(result.contains(&d), "d reachable from b");
638 }
639
640 #[test]
643 fn label_count_increments_on_add_node() {
644 let (_dir, g) = open_tmp();
645 assert_eq!(g.node_count_by_label("Person").unwrap(), 0);
646 g.add_node("Person", &json!({})).unwrap();
647 assert_eq!(g.node_count_by_label("Person").unwrap(), 1);
648 g.add_node("Person", &json!({})).unwrap();
649 assert_eq!(g.node_count_by_label("Person").unwrap(), 2);
650 assert_eq!(g.node_count_by_label("Company").unwrap(), 0);
652 }
653
654 #[test]
655 fn label_count_decrements_on_delete_node() {
656 let (_dir, g) = open_tmp();
657 let a = g.add_node("Person", &json!({})).unwrap();
658 let b = g.add_node("Person", &json!({})).unwrap();
659 assert_eq!(g.node_count_by_label("Person").unwrap(), 2);
660
661 g.delete_node(a).unwrap();
662 assert_eq!(g.node_count_by_label("Person").unwrap(), 1);
663
664 g.delete_node(b).unwrap();
665 assert_eq!(g.node_count_by_label("Person").unwrap(), 0);
666
667 g.delete_node(b).unwrap();
669 assert_eq!(g.node_count_by_label("Person").unwrap(), 0);
670 }
671
672 #[test]
673 fn label_count_unchanged_on_update_node() {
674 let (_dir, g) = open_tmp();
675 let id = g.add_node("Person", &json!({})).unwrap();
676 assert_eq!(g.node_count_by_label("Person").unwrap(), 1);
677
678 g.update_node(id, &json!({"name": "Alice"})).unwrap();
680 assert_eq!(g.node_count_by_label("Person").unwrap(), 1);
681 }
682
683 #[test]
684 fn update_node_returns_not_found_for_missing_node() {
685 let (_dir, g) = open_tmp();
686 let res = g.update_node(9999, &json!({}));
687 assert!(matches!(res, Err(Error::NodeNotFound(9999))));
688 }
689
690 #[test]
691 fn type_count_increments_on_add_edge() {
692 let (_dir, g) = open_tmp();
693 let a = g.add_node("N", &json!({})).unwrap();
694 let b = g.add_node("N", &json!({})).unwrap();
695 let c = g.add_node("N", &json!({})).unwrap();
696 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 0);
697
698 g.add_edge(a, b, "KNOWS", &json!({})).unwrap();
699 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 1);
700
701 g.add_edge(b, c, "KNOWS", &json!({})).unwrap();
702 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 2);
703
704 assert_eq!(g.edge_count_by_type("WORKS_AT").unwrap(), 0);
706 }
707
708 #[test]
709 fn type_count_decrements_on_delete_node_cascade() {
710 let (_dir, g) = open_tmp();
711 let a = g.add_node("N", &json!({})).unwrap();
712 let b = g.add_node("N", &json!({})).unwrap();
713 g.add_edge(a, b, "KNOWS", &json!({})).unwrap();
714 g.add_edge(b, a, "KNOWS", &json!({})).unwrap();
715 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 2);
716
717 g.delete_node(a).unwrap();
719 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 0);
720 }
721
722 #[test]
723 fn delete_edge_correctness() {
724 let (_dir, g) = open_tmp();
725 let a = g.add_node("Person", &json!({})).unwrap();
726 let b = g.add_node("Person", &json!({})).unwrap();
727 let eid = g.add_edge(a, b, "KNOWS", &json!({})).unwrap();
728
729 assert!(g.get_edge(eid).unwrap().is_some());
731 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 1);
732
733 let out_neighs = g.out_neighbors(a).unwrap();
735 assert_eq!(out_neighs.len(), 1);
736 assert_eq!(out_neighs[0].node, b);
737 assert_eq!(out_neighs[0].edge, eid);
738
739 let in_neighs = g.in_neighbors(b).unwrap();
740 assert_eq!(in_neighs.len(), 1);
741 assert_eq!(in_neighs[0].node, a);
742 assert_eq!(in_neighs[0].edge, eid);
743
744 g.delete_edge(eid).unwrap();
746
747 assert!(g.get_edge(eid).unwrap().is_none());
749 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 0);
750
751 assert_eq!(g.out_neighbors(a).unwrap().len(), 0);
753 assert_eq!(g.in_neighbors(b).unwrap().len(), 0);
754
755 g.delete_edge(eid).unwrap();
757 assert_eq!(g.edge_count_by_type("KNOWS").unwrap(), 0);
758 }
759
760 #[test]
761 fn test_node_property_secondary_index_and_scans() {
762 let (_dir, g) = open_tmp();
763
764 let n1 = g
766 .add_node("Person", &json!({"name": "Alice", "age": 30}))
767 .unwrap();
768 let n2 = g
769 .add_node("Person", &json!({"name": "Bob", "age": 25}))
770 .unwrap();
771 let n3 = g
772 .add_node("Person", &json!({"name": "Charlie", "age": 30}))
773 .unwrap();
774 let _n4 = g
775 .add_node("Employee", &json!({"name": "Alice", "age": 40}))
776 .unwrap();
777
778 g.create_node_property_index("Person", "age").unwrap();
780
781 assert!(g.has_node_property_index("Person", "age").unwrap());
783
784 let p30 = g
786 .nodes_by_property("Person", "age", PropValue::Int(30))
787 .unwrap();
788 assert_eq!(p30.len(), 2);
789 assert!(p30.contains(&n1));
790 assert!(p30.contains(&n3));
791
792 let p25 = g
793 .nodes_by_property("Person", "age", PropValue::Int(25))
794 .unwrap();
795 assert_eq!(p25.len(), 1);
796 assert!(p25.contains(&n2));
797
798 let pr = g
800 .nodes_by_property_range(
801 "Person",
802 "age",
803 Some(PropValue::Int(20)),
804 true,
805 Some(PropValue::Int(28)),
806 true,
807 )
808 .unwrap();
809 assert_eq!(pr.len(), 1);
810 assert!(pr.contains(&n2));
811
812 g.create_node_property_index("Person", "name").unwrap();
814 let p_alice = g
815 .nodes_by_property("Person", "name", PropValue::Str("Alice".to_string()))
816 .unwrap();
817 assert_eq!(p_alice.len(), 1);
818 assert!(p_alice.contains(&n1));
819 }
820
821 #[test]
822 fn test_unique_property_constraint() {
823 let (_dir, g) = open_tmp();
824
825 g.create_node_unique_constraint("User", "email").unwrap();
827
828 let _u1 = g
830 .add_node(
831 "User",
832 &json!({"email": "user1@example.com", "name": "User 1"}),
833 )
834 .unwrap();
835
836 let res2 = g.add_node(
838 "User",
839 &json!({"email": "user1@example.com", "name": "User 2"}),
840 );
841 assert!(res2.is_err());
842 assert!(matches!(
843 res2.unwrap_err(),
844 Error::UniqueConstraintViolation { .. }
845 ));
846
847 let u2 = g
849 .add_node(
850 "User",
851 &json!({"email": "user2@example.com", "name": "User 2"}),
852 )
853 .unwrap();
854
855 let update_res =
857 g.update_node(u2, &json!({"email": "user1@example.com", "name": "User 2"}));
858 assert!(update_res.is_err());
859 assert!(matches!(
860 update_res.unwrap_err(),
861 Error::UniqueConstraintViolation { .. }
862 ));
863 }
864
865 #[test]
866 fn test_required_property_constraint() {
867 let (_dir, g) = open_tmp();
868
869 g.create_node_required_constraint("Task", "title").unwrap();
871
872 let t1 = g
874 .add_node("Task", &json!({"title": "Do homework", "done": false}))
875 .unwrap();
876
877 let res2 = g.add_node("Task", &json!({"done": false}));
879 assert!(res2.is_err());
880 assert!(matches!(
881 res2.unwrap_err(),
882 Error::RequiredConstraintViolation { .. }
883 ));
884
885 let update_res = g.update_node(t1, &json!({"done": true}));
887 assert!(update_res.is_err());
888 assert!(matches!(
889 update_res.unwrap_err(),
890 Error::RequiredConstraintViolation { .. }
891 ));
892 }
893
894 #[test]
895 fn test_index_cleanup_on_delete() {
896 let (_dir, g) = open_tmp();
897
898 g.create_node_unique_constraint("Account", "number")
900 .unwrap();
901
902 let a1 = g.add_node("Account", &json!({"number": "12345"})).unwrap();
903
904 g.delete_node(a1).unwrap();
906
907 let a2 = g.add_node("Account", &json!({"number": "12345"}));
909 assert!(a2.is_ok());
910 }
911
912 #[test]
913 fn backup_and_restore_roundtrip() {
914 let dir = TempDir::new().unwrap();
915 let backup_file = dir.path().join("snapshot.mdb");
916 let restore_dir = dir.path().join("restored");
917
918 let n;
920 {
921 let g = Graph::open(&dir.path().join("primary"), 1).unwrap();
922 n = g
923 .add_node("BackupTest", &serde_json::json!({"x": 42}))
924 .unwrap();
925 g.backup(&backup_file).unwrap();
926 }
927
928 Graph::restore(&backup_file, &restore_dir).unwrap();
930 let g2 = Graph::open(&restore_dir, 1).unwrap();
931 let rec = g2
932 .get_node(n)
933 .unwrap()
934 .expect("node must exist in restored graph");
935 let props: serde_json::Value = rmp_serde::from_slice(&rec.props).unwrap();
936 assert_eq!(props["x"], serde_json::json!(42));
937 }
938
939 #[test]
940 fn backup_compact_and_restore_roundtrip() {
941 let dir = TempDir::new().unwrap();
942 let backup_file = dir.path().join("compact.mdb");
943 let restore_dir = dir.path().join("restored");
944
945 let kept;
947 {
948 let g = Graph::open(&dir.path().join("primary"), 1).unwrap();
949 let doomed = g
950 .add_node("BackupTest", &serde_json::json!({"x": 1}))
951 .unwrap();
952 kept = g
953 .add_node("BackupTest", &serde_json::json!({"x": 42}))
954 .unwrap();
955 g.delete_node(doomed).unwrap();
956 g.backup_compact(&backup_file).unwrap();
957 }
958
959 Graph::restore(&backup_file, &restore_dir).unwrap();
961 let g2 = Graph::open(&restore_dir, 1).unwrap();
962 let rec = g2
963 .get_node(kept)
964 .unwrap()
965 .expect("node must exist in restored graph");
966 let props: serde_json::Value = rmp_serde::from_slice(&rec.props).unwrap();
967 assert_eq!(props["x"], serde_json::json!(42));
968 assert_eq!(g2.nodes_by_label("BackupTest").unwrap(), vec![kept]);
969 }
970}