1use crate::graph::Direction;
23use crate::graph::lpg::CompareOp;
24use crate::graph::lpg::{Edge, Node};
25#[cfg(feature = "vector-index")]
26use crate::index::vector::DistanceMetric;
27use crate::statistics::Statistics;
28use arcstr::ArcStr;
29use grafeo_common::types::{EdgeId, EpochId, NodeId, PropertyKey, TransactionId, Value};
30use grafeo_common::utils::hash::FxHashMap;
31use std::sync::Arc;
32
33pub trait GraphStore: Send + Sync {
45 fn get_node(&self, id: NodeId) -> Option<Node>;
49
50 fn get_edge(&self, id: EdgeId) -> Option<Edge>;
52
53 fn get_node_versioned(
55 &self,
56 id: NodeId,
57 epoch: EpochId,
58 transaction_id: TransactionId,
59 ) -> Option<Node>;
60
61 fn get_edge_versioned(
63 &self,
64 id: EdgeId,
65 epoch: EpochId,
66 transaction_id: TransactionId,
67 ) -> Option<Edge>;
68
69 fn get_node_at_epoch(&self, id: NodeId, epoch: EpochId) -> Option<Node>;
75
76 fn get_edge_at_epoch(&self, id: EdgeId, epoch: EpochId) -> Option<Edge>;
78
79 fn get_node_property(&self, id: NodeId, key: &PropertyKey) -> Option<Value>;
83
84 fn get_edge_property(&self, id: EdgeId, key: &PropertyKey) -> Option<Value>;
86
87 fn get_node_property_batch(&self, ids: &[NodeId], key: &PropertyKey) -> Vec<Option<Value>>;
89
90 fn get_nodes_properties_batch(&self, ids: &[NodeId]) -> Vec<FxHashMap<PropertyKey, Value>>;
92
93 fn get_nodes_properties_selective_batch(
95 &self,
96 ids: &[NodeId],
97 keys: &[PropertyKey],
98 ) -> Vec<FxHashMap<PropertyKey, Value>>;
99
100 fn get_edges_properties_selective_batch(
102 &self,
103 ids: &[EdgeId],
104 keys: &[PropertyKey],
105 ) -> Vec<FxHashMap<PropertyKey, Value>>;
106
107 fn neighbors(&self, node: NodeId, direction: Direction) -> Vec<NodeId>;
114
115 fn edges_from(&self, node: NodeId, direction: Direction) -> Vec<(NodeId, EdgeId)>;
117
118 fn out_degree(&self, node: NodeId) -> usize;
120
121 fn in_degree(&self, node: NodeId) -> usize;
123
124 fn has_backward_adjacency(&self) -> bool;
126
127 fn node_ids(&self) -> Vec<NodeId>;
131
132 fn all_node_ids(&self) -> Vec<NodeId> {
138 self.node_ids()
140 }
141
142 fn nodes_by_label(&self, label: &str) -> Vec<NodeId>;
144
145 fn nodes_by_label_count(&self, label: &str) -> usize {
151 self.nodes_by_label(label).len()
152 }
153
154 fn node_count(&self) -> usize;
156
157 fn edge_count(&self) -> usize;
159
160 fn edge_type(&self, id: EdgeId) -> Option<ArcStr>;
164
165 fn edge_type_versioned(
169 &self,
170 id: EdgeId,
171 epoch: EpochId,
172 transaction_id: TransactionId,
173 ) -> Option<ArcStr> {
174 let _ = (epoch, transaction_id);
175 self.edge_type(id)
176 }
177
178 fn has_property_index(&self, _property: &str) -> bool {
184 false
185 }
186
187 fn find_nodes_by_property(&self, property: &str, value: &Value) -> Vec<NodeId>;
191
192 fn find_nodes_by_properties(&self, conditions: &[(&str, Value)]) -> Vec<NodeId>;
194
195 fn find_nodes_in_range(
197 &self,
198 property: &str,
199 min: Option<&Value>,
200 max: Option<&Value>,
201 min_inclusive: bool,
202 max_inclusive: bool,
203 ) -> Vec<NodeId>;
204
205 fn node_property_might_match(
210 &self,
211 property: &PropertyKey,
212 op: CompareOp,
213 value: &Value,
214 ) -> bool;
215
216 fn edge_property_might_match(
218 &self,
219 property: &PropertyKey,
220 op: CompareOp,
221 value: &Value,
222 ) -> bool;
223
224 fn statistics(&self) -> Arc<Statistics>;
228
229 fn estimate_label_cardinality(&self, label: &str) -> f64;
231
232 fn estimate_avg_degree(&self, edge_type: &str, outgoing: bool) -> f64;
234
235 fn current_epoch(&self) -> EpochId;
239
240 fn all_labels(&self) -> Vec<String> {
244 Vec::new()
245 }
246
247 fn all_edge_types(&self) -> Vec<String> {
249 Vec::new()
250 }
251
252 fn all_property_keys(&self) -> Vec<String> {
254 Vec::new()
255 }
256
257 fn is_node_visible_at_epoch(&self, id: NodeId, epoch: EpochId) -> bool {
265 self.get_node_at_epoch(id, epoch).is_some()
266 }
267
268 fn is_node_visible_versioned(
271 &self,
272 id: NodeId,
273 epoch: EpochId,
274 transaction_id: TransactionId,
275 ) -> bool {
276 self.get_node_versioned(id, epoch, transaction_id).is_some()
277 }
278
279 fn is_edge_visible_at_epoch(&self, id: EdgeId, epoch: EpochId) -> bool {
285 self.get_edge_at_epoch(id, epoch).is_some()
286 }
287
288 fn is_edge_visible_versioned(
291 &self,
292 id: EdgeId,
293 epoch: EpochId,
294 transaction_id: TransactionId,
295 ) -> bool {
296 self.get_edge_versioned(id, epoch, transaction_id).is_some()
297 }
298
299 fn filter_visible_node_ids(&self, ids: &[NodeId], epoch: EpochId) -> Vec<NodeId> {
304 ids.iter()
305 .copied()
306 .filter(|id| self.is_node_visible_at_epoch(*id, epoch))
307 .collect()
308 }
309
310 fn filter_visible_node_ids_versioned(
312 &self,
313 ids: &[NodeId],
314 epoch: EpochId,
315 transaction_id: TransactionId,
316 ) -> Vec<NodeId> {
317 ids.iter()
318 .copied()
319 .filter(|id| self.is_node_visible_versioned(*id, epoch, transaction_id))
320 .collect()
321 }
322
323 fn get_node_history(&self, _id: NodeId) -> Vec<(EpochId, Option<EpochId>, Node)> {
332 Vec::new()
333 }
334
335 fn get_edge_history(&self, _id: EdgeId) -> Vec<(EpochId, Option<EpochId>, Edge)> {
342 Vec::new()
343 }
344}
345
346pub trait GraphStoreSearch: GraphStore {
361 #[cfg(feature = "text-index")]
365 #[must_use]
366 fn has_text_index(&self, _label: &str, _property: &str) -> bool {
367 false
368 }
369
370 #[cfg(feature = "text-index")]
376 fn score_text(
377 &self,
378 _node_id: NodeId,
379 _label: &str,
380 _property: &str,
381 _query: &str,
382 ) -> Option<f64> {
383 None
384 }
385
386 #[cfg(feature = "text-index")]
391 fn text_search(
392 &self,
393 _label: &str,
394 _property: &str,
395 _query: &str,
396 _k: usize,
397 ) -> Vec<(NodeId, f64)> {
398 Vec::new()
399 }
400
401 #[cfg(feature = "text-index")]
403 fn text_search_with_threshold(
404 &self,
405 _label: &str,
406 _property: &str,
407 _query: &str,
408 _threshold: f64,
409 ) -> Vec<(NodeId, f64)> {
410 Vec::new()
411 }
412
413 #[cfg(feature = "vector-index")]
417 #[must_use]
418 fn has_vector_index(&self, _label: &str, _property: &str) -> bool {
419 false
420 }
421
422 #[cfg(feature = "vector-index")]
429 fn vector_index_metric(&self, _label: &str, _property: &str) -> Option<DistanceMetric> {
430 None
431 }
432
433 #[cfg(feature = "vector-index")]
442 fn vector_search(
443 &self,
444 _label: Option<&str>,
445 _property: &str,
446 _query: &[f32],
447 _k: usize,
448 _metric: DistanceMetric,
449 ) -> Vec<(NodeId, f64)> {
450 Vec::new()
451 }
452
453 #[cfg(feature = "vector-index")]
455 fn vector_search_with_threshold(
456 &self,
457 _label: Option<&str>,
458 _property: &str,
459 _query: &[f32],
460 _threshold: f64,
461 _metric: DistanceMetric,
462 ) -> Vec<(NodeId, f64)> {
463 Vec::new()
464 }
465}
466
467pub trait GraphStoreMut: GraphStoreSearch {
473 fn create_node(&self, labels: &[&str]) -> NodeId;
477
478 fn create_node_versioned(
480 &self,
481 labels: &[&str],
482 epoch: EpochId,
483 transaction_id: TransactionId,
484 ) -> NodeId;
485
486 fn create_edge(&self, src: NodeId, dst: NodeId, edge_type: &str) -> EdgeId;
490
491 fn create_edge_versioned(
493 &self,
494 src: NodeId,
495 dst: NodeId,
496 edge_type: &str,
497 epoch: EpochId,
498 transaction_id: TransactionId,
499 ) -> EdgeId;
500
501 fn batch_create_edges(&self, edges: &[(NodeId, NodeId, &str)]) -> Vec<EdgeId>;
503
504 fn delete_node(&self, id: NodeId) -> bool;
508
509 fn delete_node_versioned(
511 &self,
512 id: NodeId,
513 epoch: EpochId,
514 transaction_id: TransactionId,
515 ) -> bool;
516
517 fn delete_node_edges(&self, node_id: NodeId);
519
520 fn delete_edge(&self, id: EdgeId) -> bool;
522
523 fn delete_edge_versioned(
525 &self,
526 id: EdgeId,
527 epoch: EpochId,
528 transaction_id: TransactionId,
529 ) -> bool;
530
531 fn set_node_property(&self, id: NodeId, key: &str, value: Value);
535
536 fn set_edge_property(&self, id: EdgeId, key: &str, value: Value);
538
539 fn set_node_property_versioned(
544 &self,
545 id: NodeId,
546 key: &str,
547 value: Value,
548 _transaction_id: TransactionId,
549 ) {
550 self.set_node_property(id, key, value);
551 }
552
553 fn set_edge_property_versioned(
558 &self,
559 id: EdgeId,
560 key: &str,
561 value: Value,
562 _transaction_id: TransactionId,
563 ) {
564 self.set_edge_property(id, key, value);
565 }
566
567 fn remove_node_property(&self, id: NodeId, key: &str) -> Option<Value>;
569
570 fn remove_edge_property(&self, id: EdgeId, key: &str) -> Option<Value>;
572
573 fn remove_node_property_versioned(
578 &self,
579 id: NodeId,
580 key: &str,
581 _transaction_id: TransactionId,
582 ) -> Option<Value> {
583 self.remove_node_property(id, key)
584 }
585
586 fn remove_edge_property_versioned(
591 &self,
592 id: EdgeId,
593 key: &str,
594 _transaction_id: TransactionId,
595 ) -> Option<Value> {
596 self.remove_edge_property(id, key)
597 }
598
599 fn add_label(&self, node_id: NodeId, label: &str) -> bool;
603
604 fn remove_label(&self, node_id: NodeId, label: &str) -> bool;
606
607 fn add_label_versioned(
611 &self,
612 node_id: NodeId,
613 label: &str,
614 _transaction_id: TransactionId,
615 ) -> bool {
616 self.add_label(node_id, label)
617 }
618
619 fn remove_label_versioned(
623 &self,
624 node_id: NodeId,
625 label: &str,
626 _transaction_id: TransactionId,
627 ) -> bool {
628 self.remove_label(node_id, label)
629 }
630
631 fn create_node_with_props(
639 &self,
640 labels: &[&str],
641 properties: &[(PropertyKey, Value)],
642 ) -> NodeId {
643 let id = self.create_node(labels);
644 for (key, value) in properties {
645 self.set_node_property(id, key.as_str(), value.clone());
646 }
647 id
648 }
649
650 fn create_edge_with_props(
656 &self,
657 src: NodeId,
658 dst: NodeId,
659 edge_type: &str,
660 properties: &[(PropertyKey, Value)],
661 ) -> EdgeId {
662 let id = self.create_edge(src, dst, edge_type);
663 for (key, value) in properties {
664 self.set_edge_property(id, key.as_str(), value.clone());
665 }
666 id
667 }
668}
669
670pub struct NullGraphStore;
677
678impl GraphStore for NullGraphStore {
679 fn get_node(&self, _: NodeId) -> Option<Node> {
680 None
681 }
682 fn get_edge(&self, _: EdgeId) -> Option<Edge> {
683 None
684 }
685 fn get_node_versioned(&self, _: NodeId, _: EpochId, _: TransactionId) -> Option<Node> {
686 None
687 }
688 fn get_edge_versioned(&self, _: EdgeId, _: EpochId, _: TransactionId) -> Option<Edge> {
689 None
690 }
691 fn get_node_at_epoch(&self, _: NodeId, _: EpochId) -> Option<Node> {
692 None
693 }
694 fn get_edge_at_epoch(&self, _: EdgeId, _: EpochId) -> Option<Edge> {
695 None
696 }
697 fn get_node_property(&self, _: NodeId, _: &PropertyKey) -> Option<Value> {
698 None
699 }
700 fn get_edge_property(&self, _: EdgeId, _: &PropertyKey) -> Option<Value> {
701 None
702 }
703 fn get_node_property_batch(&self, ids: &[NodeId], _: &PropertyKey) -> Vec<Option<Value>> {
704 vec![None; ids.len()]
705 }
706 fn get_nodes_properties_batch(&self, ids: &[NodeId]) -> Vec<FxHashMap<PropertyKey, Value>> {
707 vec![FxHashMap::default(); ids.len()]
708 }
709 fn get_nodes_properties_selective_batch(
710 &self,
711 ids: &[NodeId],
712 _: &[PropertyKey],
713 ) -> Vec<FxHashMap<PropertyKey, Value>> {
714 vec![FxHashMap::default(); ids.len()]
715 }
716 fn get_edges_properties_selective_batch(
717 &self,
718 ids: &[EdgeId],
719 _: &[PropertyKey],
720 ) -> Vec<FxHashMap<PropertyKey, Value>> {
721 vec![FxHashMap::default(); ids.len()]
722 }
723 fn neighbors(&self, _: NodeId, _: Direction) -> Vec<NodeId> {
724 Vec::new()
725 }
726 fn edges_from(&self, _: NodeId, _: Direction) -> Vec<(NodeId, EdgeId)> {
727 Vec::new()
728 }
729 fn out_degree(&self, _: NodeId) -> usize {
730 0
731 }
732 fn in_degree(&self, _: NodeId) -> usize {
733 0
734 }
735 fn has_backward_adjacency(&self) -> bool {
736 false
737 }
738 fn node_ids(&self) -> Vec<NodeId> {
739 Vec::new()
740 }
741 fn nodes_by_label(&self, _: &str) -> Vec<NodeId> {
742 Vec::new()
743 }
744 fn node_count(&self) -> usize {
745 0
746 }
747 fn edge_count(&self) -> usize {
748 0
749 }
750 fn edge_type(&self, _: EdgeId) -> Option<ArcStr> {
751 None
752 }
753 fn find_nodes_by_property(&self, _: &str, _: &Value) -> Vec<NodeId> {
754 Vec::new()
755 }
756 fn find_nodes_by_properties(&self, _: &[(&str, Value)]) -> Vec<NodeId> {
757 Vec::new()
758 }
759 fn find_nodes_in_range(
760 &self,
761 _: &str,
762 _: Option<&Value>,
763 _: Option<&Value>,
764 _: bool,
765 _: bool,
766 ) -> Vec<NodeId> {
767 Vec::new()
768 }
769 fn node_property_might_match(&self, _: &PropertyKey, _: CompareOp, _: &Value) -> bool {
770 false
771 }
772 fn edge_property_might_match(&self, _: &PropertyKey, _: CompareOp, _: &Value) -> bool {
773 false
774 }
775 fn statistics(&self) -> Arc<Statistics> {
776 Arc::new(Statistics::default())
777 }
778 fn estimate_label_cardinality(&self, _: &str) -> f64 {
779 0.0
780 }
781 fn estimate_avg_degree(&self, _: &str, _: bool) -> f64 {
782 0.0
783 }
784 fn current_epoch(&self) -> EpochId {
785 EpochId(0)
786 }
787}
788
789impl GraphStoreSearch for NullGraphStore {}
790
791#[cfg(test)]
792mod tests {
793 use super::*;
794 use std::sync::Mutex;
795
796 #[test]
797 fn null_graph_store_point_lookups() {
798 let store = NullGraphStore;
799 let nid = NodeId(1);
800 let eid = EdgeId(1);
801 let epoch = EpochId(0);
802 let txn = TransactionId(1);
803
804 assert!(store.get_node(nid).is_none());
805 assert!(store.get_edge(eid).is_none());
806 assert!(store.get_node_versioned(nid, epoch, txn).is_none());
807 assert!(store.get_edge_versioned(eid, epoch, txn).is_none());
808 assert!(store.get_node_at_epoch(nid, epoch).is_none());
809 assert!(store.get_edge_at_epoch(eid, epoch).is_none());
810 }
811
812 #[test]
813 fn null_graph_store_property_access() {
814 let store = NullGraphStore;
815 let nid = NodeId(1);
816 let eid = EdgeId(1);
817 let key = PropertyKey::from("name");
818
819 assert!(store.get_node_property(nid, &key).is_none());
820 assert!(store.get_edge_property(eid, &key).is_none());
821 assert_eq!(
822 store.get_node_property_batch(&[nid, NodeId(2)], &key),
823 vec![None, None]
824 );
825
826 let node_props = store.get_nodes_properties_batch(&[nid]);
827 assert_eq!(node_props.len(), 1);
828 assert!(node_props[0].is_empty());
829
830 let selective =
831 store.get_nodes_properties_selective_batch(&[nid], std::slice::from_ref(&key));
832 assert_eq!(selective.len(), 1);
833 assert!(selective[0].is_empty());
834
835 let edge_selective = store.get_edges_properties_selective_batch(&[eid], &[key]);
836 assert_eq!(edge_selective.len(), 1);
837 assert!(edge_selective[0].is_empty());
838 }
839
840 #[test]
841 fn null_graph_store_traversal() {
842 let store = NullGraphStore;
843 let nid = NodeId(1);
844
845 assert!(store.neighbors(nid, Direction::Outgoing).is_empty());
846 assert!(store.edges_from(nid, Direction::Incoming).is_empty());
847 assert_eq!(store.out_degree(nid), 0);
848 assert_eq!(store.in_degree(nid), 0);
849 assert!(!store.has_backward_adjacency());
850 }
851
852 #[test]
853 fn null_graph_store_scans_and_counts() {
854 let store = NullGraphStore;
855
856 assert!(store.node_ids().is_empty());
857 assert!(store.all_node_ids().is_empty());
858 assert!(store.nodes_by_label("Person").is_empty());
859 assert_eq!(store.node_count(), 0);
860 assert_eq!(store.edge_count(), 0);
861 }
862
863 #[test]
864 fn null_graph_store_metadata_and_schema() {
865 let store = NullGraphStore;
866 let eid = EdgeId(1);
867 let epoch = EpochId(0);
868 let txn = TransactionId(1);
869
870 assert!(store.edge_type(eid).is_none());
871 assert!(store.edge_type_versioned(eid, epoch, txn).is_none());
872 assert!(!store.has_property_index("name"));
873 assert!(store.all_labels().is_empty());
874 assert!(store.all_edge_types().is_empty());
875 assert!(store.all_property_keys().is_empty());
876 }
877
878 #[test]
879 fn null_graph_store_search() {
880 let store = NullGraphStore;
881 let key = PropertyKey::from("age");
882 let val = Value::Int64(30);
883
884 assert!(store.find_nodes_by_property("age", &val).is_empty());
885 assert!(
886 store
887 .find_nodes_by_properties(&[("age", val.clone())])
888 .is_empty()
889 );
890 assert!(
891 store
892 .find_nodes_in_range("age", Some(&val), None, true, false)
893 .is_empty()
894 );
895 assert!(!store.node_property_might_match(&key, CompareOp::Eq, &val));
896 assert!(!store.edge_property_might_match(&key, CompareOp::Eq, &val));
897 }
898
899 #[test]
900 fn null_graph_store_statistics() {
901 let store = NullGraphStore;
902
903 let _stats = store.statistics();
904 assert_eq!(store.estimate_label_cardinality("Person"), 0.0);
905 assert_eq!(store.estimate_avg_degree("KNOWS", true), 0.0);
906 assert_eq!(store.current_epoch(), EpochId(0));
907 }
908
909 #[test]
910 fn null_graph_store_visibility() {
911 let store = NullGraphStore;
912 let nid = NodeId(1);
913 let eid = EdgeId(1);
914 let epoch = EpochId(0);
915 let txn = TransactionId(1);
916
917 assert!(!store.is_node_visible_at_epoch(nid, epoch));
918 assert!(!store.is_node_visible_versioned(nid, epoch, txn));
919 assert!(!store.is_edge_visible_at_epoch(eid, epoch));
920 assert!(!store.is_edge_visible_versioned(eid, epoch, txn));
921
922 assert!(
923 store
924 .filter_visible_node_ids(&[nid, NodeId(2)], epoch)
925 .is_empty()
926 );
927 assert!(
928 store
929 .filter_visible_node_ids_versioned(&[nid], epoch, txn)
930 .is_empty()
931 );
932 }
933
934 #[test]
935 fn null_graph_store_history() {
936 let store = NullGraphStore;
937
938 assert!(store.get_node_history(NodeId(1)).is_empty());
939 assert!(store.get_edge_history(EdgeId(1)).is_empty());
940 }
941
942 #[derive(Default)]
946 struct TestMutStore {
947 inner: Mutex<TestMutInner>,
948 }
949
950 #[derive(Default)]
951 struct TestMutInner {
952 next_node: u64,
953 next_edge: u64,
954 nodes: Vec<Node>,
955 edges: Vec<Edge>,
956 }
957
958 impl TestMutStore {
959 fn new() -> Self {
960 Self::default()
961 }
962
963 fn find_node(&self, id: NodeId) -> Option<Node> {
964 self.inner
965 .lock()
966 .unwrap()
967 .nodes
968 .iter()
969 .find(|n| n.id == id)
970 .cloned()
971 }
972
973 fn find_edge(&self, id: EdgeId) -> Option<Edge> {
974 self.inner
975 .lock()
976 .unwrap()
977 .edges
978 .iter()
979 .find(|e| e.id == id)
980 .cloned()
981 }
982 }
983
984 impl GraphStore for TestMutStore {
985 fn get_node(&self, id: NodeId) -> Option<Node> {
986 self.find_node(id)
987 }
988 fn get_edge(&self, id: EdgeId) -> Option<Edge> {
989 self.find_edge(id)
990 }
991 fn get_node_versioned(&self, id: NodeId, _: EpochId, _: TransactionId) -> Option<Node> {
992 self.find_node(id)
993 }
994 fn get_edge_versioned(&self, id: EdgeId, _: EpochId, _: TransactionId) -> Option<Edge> {
995 self.find_edge(id)
996 }
997 fn get_node_at_epoch(&self, id: NodeId, _: EpochId) -> Option<Node> {
998 self.find_node(id)
999 }
1000 fn get_edge_at_epoch(&self, id: EdgeId, _: EpochId) -> Option<Edge> {
1001 self.find_edge(id)
1002 }
1003 fn get_node_property(&self, id: NodeId, key: &PropertyKey) -> Option<Value> {
1004 self.find_node(id)
1005 .and_then(|n| n.properties.get(key).cloned())
1006 }
1007 fn get_edge_property(&self, id: EdgeId, key: &PropertyKey) -> Option<Value> {
1008 self.find_edge(id)
1009 .and_then(|e| e.properties.get(key).cloned())
1010 }
1011 fn get_node_property_batch(&self, ids: &[NodeId], key: &PropertyKey) -> Vec<Option<Value>> {
1012 ids.iter()
1013 .map(|id| self.get_node_property(*id, key))
1014 .collect()
1015 }
1016 fn get_nodes_properties_batch(&self, ids: &[NodeId]) -> Vec<FxHashMap<PropertyKey, Value>> {
1017 ids.iter()
1018 .map(|id| {
1019 let mut map = FxHashMap::default();
1020 if let Some(n) = self.find_node(*id) {
1021 for (k, v) in n.properties.iter() {
1022 map.insert(k.clone(), v.clone());
1023 }
1024 }
1025 map
1026 })
1027 .collect()
1028 }
1029 fn get_nodes_properties_selective_batch(
1030 &self,
1031 ids: &[NodeId],
1032 _: &[PropertyKey],
1033 ) -> Vec<FxHashMap<PropertyKey, Value>> {
1034 vec![FxHashMap::default(); ids.len()]
1035 }
1036 fn get_edges_properties_selective_batch(
1037 &self,
1038 ids: &[EdgeId],
1039 _: &[PropertyKey],
1040 ) -> Vec<FxHashMap<PropertyKey, Value>> {
1041 vec![FxHashMap::default(); ids.len()]
1042 }
1043 fn neighbors(&self, _: NodeId, _: Direction) -> Vec<NodeId> {
1044 Vec::new()
1045 }
1046 fn edges_from(&self, _: NodeId, _: Direction) -> Vec<(NodeId, EdgeId)> {
1047 Vec::new()
1048 }
1049 fn out_degree(&self, _: NodeId) -> usize {
1050 0
1051 }
1052 fn in_degree(&self, _: NodeId) -> usize {
1053 0
1054 }
1055 fn has_backward_adjacency(&self) -> bool {
1056 false
1057 }
1058 fn node_ids(&self) -> Vec<NodeId> {
1059 self.inner
1060 .lock()
1061 .unwrap()
1062 .nodes
1063 .iter()
1064 .map(|n| n.id)
1065 .collect()
1066 }
1067 fn nodes_by_label(&self, _: &str) -> Vec<NodeId> {
1068 Vec::new()
1069 }
1070 fn node_count(&self) -> usize {
1071 self.inner.lock().unwrap().nodes.len()
1072 }
1073 fn edge_count(&self) -> usize {
1074 self.inner.lock().unwrap().edges.len()
1075 }
1076 fn edge_type(&self, id: EdgeId) -> Option<ArcStr> {
1077 self.find_edge(id).map(|e| e.edge_type)
1078 }
1079 fn find_nodes_by_property(&self, _: &str, _: &Value) -> Vec<NodeId> {
1080 Vec::new()
1081 }
1082 fn find_nodes_by_properties(&self, _: &[(&str, Value)]) -> Vec<NodeId> {
1083 Vec::new()
1084 }
1085 fn find_nodes_in_range(
1086 &self,
1087 _: &str,
1088 _: Option<&Value>,
1089 _: Option<&Value>,
1090 _: bool,
1091 _: bool,
1092 ) -> Vec<NodeId> {
1093 Vec::new()
1094 }
1095 fn node_property_might_match(&self, _: &PropertyKey, _: CompareOp, _: &Value) -> bool {
1096 true
1097 }
1098 fn edge_property_might_match(&self, _: &PropertyKey, _: CompareOp, _: &Value) -> bool {
1099 true
1100 }
1101 fn statistics(&self) -> Arc<Statistics> {
1102 Arc::new(Statistics::default())
1103 }
1104 fn estimate_label_cardinality(&self, _: &str) -> f64 {
1105 0.0
1106 }
1107 fn estimate_avg_degree(&self, _: &str, _: bool) -> f64 {
1108 0.0
1109 }
1110 fn current_epoch(&self) -> EpochId {
1111 EpochId(0)
1112 }
1113 }
1114
1115 impl GraphStoreSearch for TestMutStore {}
1116
1117 impl GraphStoreMut for TestMutStore {
1118 fn create_node(&self, labels: &[&str]) -> NodeId {
1119 let mut inner = self.inner.lock().unwrap();
1120 inner.next_node += 1;
1121 let id = NodeId(inner.next_node);
1122 let mut node = Node::new(id);
1123 for label in labels {
1124 node.add_label(*label);
1125 }
1126 inner.nodes.push(node);
1127 id
1128 }
1129 fn create_node_versioned(&self, labels: &[&str], _: EpochId, _: TransactionId) -> NodeId {
1130 self.create_node(labels)
1131 }
1132 fn create_edge(&self, src: NodeId, dst: NodeId, edge_type: &str) -> EdgeId {
1133 let mut inner = self.inner.lock().unwrap();
1134 inner.next_edge += 1;
1135 let id = EdgeId(inner.next_edge);
1136 inner.edges.push(Edge::new(id, src, dst, edge_type));
1137 id
1138 }
1139 fn create_edge_versioned(
1140 &self,
1141 src: NodeId,
1142 dst: NodeId,
1143 edge_type: &str,
1144 _: EpochId,
1145 _: TransactionId,
1146 ) -> EdgeId {
1147 self.create_edge(src, dst, edge_type)
1148 }
1149 fn batch_create_edges(&self, edges: &[(NodeId, NodeId, &str)]) -> Vec<EdgeId> {
1150 edges
1151 .iter()
1152 .map(|(s, d, t)| self.create_edge(*s, *d, t))
1153 .collect()
1154 }
1155 fn delete_node(&self, id: NodeId) -> bool {
1156 let mut inner = self.inner.lock().unwrap();
1157 if let Some(pos) = inner.nodes.iter().position(|n| n.id == id) {
1158 inner.nodes.remove(pos);
1159 true
1160 } else {
1161 false
1162 }
1163 }
1164 fn delete_node_versioned(&self, id: NodeId, _: EpochId, _: TransactionId) -> bool {
1165 self.delete_node(id)
1166 }
1167 fn delete_node_edges(&self, node_id: NodeId) {
1168 let mut inner = self.inner.lock().unwrap();
1169 inner.edges.retain(|e| e.src != node_id && e.dst != node_id);
1170 }
1171 fn delete_edge(&self, id: EdgeId) -> bool {
1172 let mut inner = self.inner.lock().unwrap();
1173 if let Some(pos) = inner.edges.iter().position(|e| e.id == id) {
1174 inner.edges.remove(pos);
1175 true
1176 } else {
1177 false
1178 }
1179 }
1180 fn delete_edge_versioned(&self, id: EdgeId, _: EpochId, _: TransactionId) -> bool {
1181 self.delete_edge(id)
1182 }
1183 fn set_node_property(&self, id: NodeId, key: &str, value: Value) {
1184 let mut inner = self.inner.lock().unwrap();
1185 if let Some(node) = inner.nodes.iter_mut().find(|n| n.id == id) {
1186 node.set_property(key, value);
1187 }
1188 }
1189 fn set_edge_property(&self, id: EdgeId, key: &str, value: Value) {
1190 let mut inner = self.inner.lock().unwrap();
1191 if let Some(edge) = inner.edges.iter_mut().find(|e| e.id == id) {
1192 edge.set_property(key, value);
1193 }
1194 }
1195 fn remove_node_property(&self, id: NodeId, key: &str) -> Option<Value> {
1196 let mut inner = self.inner.lock().unwrap();
1197 inner
1198 .nodes
1199 .iter_mut()
1200 .find(|n| n.id == id)
1201 .and_then(|n| n.remove_property(key))
1202 }
1203 fn remove_edge_property(&self, id: EdgeId, key: &str) -> Option<Value> {
1204 let mut inner = self.inner.lock().unwrap();
1205 inner
1206 .edges
1207 .iter_mut()
1208 .find(|e| e.id == id)
1209 .and_then(|e| e.remove_property(key))
1210 }
1211 fn add_label(&self, node_id: NodeId, label: &str) -> bool {
1212 let mut inner = self.inner.lock().unwrap();
1213 if let Some(node) = inner.nodes.iter_mut().find(|n| n.id == node_id) {
1214 if node.has_label(label) {
1215 false
1216 } else {
1217 node.add_label(label);
1218 true
1219 }
1220 } else {
1221 false
1222 }
1223 }
1224 fn remove_label(&self, node_id: NodeId, label: &str) -> bool {
1225 let mut inner = self.inner.lock().unwrap();
1226 inner
1227 .nodes
1228 .iter_mut()
1229 .find(|n| n.id == node_id)
1230 .is_some_and(|n| n.remove_label(label))
1231 }
1232 }
1233
1234 #[test]
1235 fn test_mut_store_default_set_versioned_property_delegates() {
1236 let store = TestMutStore::new();
1237 let id = store.create_node(&["Person"]);
1238 let key = PropertyKey::from("name");
1239 let txn = TransactionId(7);
1240
1241 store.set_node_property_versioned(id, "name", Value::from("Vincent"), txn);
1243 assert_eq!(
1244 store.get_node_property(id, &key),
1245 Some(Value::from("Vincent"))
1246 );
1247
1248 let edge_id = {
1249 let src = store.create_node(&["Person"]);
1250 let dst = store.create_node(&["City"]);
1251 store.create_edge(src, dst, "LIVES_IN")
1252 };
1253 let since = PropertyKey::from("since");
1254 store.set_edge_property_versioned(edge_id, "since", Value::Int64(1994), txn);
1255 assert_eq!(
1256 store.get_edge_property(edge_id, &since),
1257 Some(Value::Int64(1994))
1258 );
1259 }
1260
1261 #[test]
1262 fn test_mut_store_default_remove_versioned_property_delegates() {
1263 let store = TestMutStore::new();
1264 let txn = TransactionId(11);
1265
1266 let node_id = store.create_node(&["Person"]);
1267 store.set_node_property(node_id, "city", Value::from("Amsterdam"));
1268 let removed = store.remove_node_property_versioned(node_id, "city", txn);
1269 assert_eq!(removed, Some(Value::from("Amsterdam")));
1270 assert!(
1271 store
1272 .get_node_property(node_id, &PropertyKey::from("city"))
1273 .is_none()
1274 );
1275
1276 let missing = store.remove_node_property_versioned(node_id, "absent", txn);
1277 assert!(missing.is_none());
1278
1279 let src = store.create_node(&["Person"]);
1280 let dst = store.create_node(&["Person"]);
1281 let edge_id = store.create_edge(src, dst, "KNOWS");
1282 store.set_edge_property(edge_id, "weight", Value::Int64(42));
1283 let removed_edge = store.remove_edge_property_versioned(edge_id, "weight", txn);
1284 assert_eq!(removed_edge, Some(Value::Int64(42)));
1285 let removed_again = store.remove_edge_property_versioned(edge_id, "weight", txn);
1286 assert!(removed_again.is_none());
1287 }
1288
1289 #[test]
1290 fn test_mut_store_default_label_versioned_delegates() {
1291 let store = TestMutStore::new();
1292 let txn = TransactionId(3);
1293 let id = store.create_node(&["Person"]);
1294
1295 assert!(store.add_label_versioned(id, "Director", txn));
1297 assert!(!store.add_label_versioned(id, "Director", txn));
1298
1299 assert!(store.remove_label_versioned(id, "Director", txn));
1301 assert!(!store.remove_label_versioned(id, "Director", txn));
1302
1303 let unknown = NodeId(9999);
1305 assert!(!store.add_label_versioned(unknown, "Ghost", txn));
1306 assert!(!store.remove_label_versioned(unknown, "Ghost", txn));
1307 }
1308
1309 #[test]
1310 fn test_mut_store_default_create_node_with_props() {
1311 let store = TestMutStore::new();
1312 let props = vec![
1313 (PropertyKey::from("name"), Value::from("Jules")),
1314 (PropertyKey::from("city"), Value::from("Paris")),
1315 ];
1316
1317 let id = store.create_node_with_props(&["Person"], &props);
1318 let node = store.get_node(id).expect("node should exist");
1319 assert!(node.has_label("Person"));
1320 assert_eq!(
1321 node.properties.get(&PropertyKey::from("name")),
1322 Some(&Value::from("Jules"))
1323 );
1324 assert_eq!(
1325 node.properties.get(&PropertyKey::from("city")),
1326 Some(&Value::from("Paris"))
1327 );
1328
1329 let bare = store.create_node_with_props(&["Person"], &[]);
1331 let bare_node = store.get_node(bare).expect("bare node should exist");
1332 assert!(bare_node.properties.is_empty());
1333 }
1334
1335 #[test]
1336 fn test_mut_store_default_create_edge_with_props() {
1337 let store = TestMutStore::new();
1338 let src = store.create_node_with_props(
1339 &["Person"],
1340 &[(PropertyKey::from("name"), Value::from("Mia"))],
1341 );
1342 let dst = store.create_node_with_props(
1343 &["City"],
1344 &[(PropertyKey::from("name"), Value::from("Berlin"))],
1345 );
1346 let props = vec![
1347 (PropertyKey::from("since"), Value::Int64(2021)),
1348 (PropertyKey::from("role"), Value::from("resident")),
1349 ];
1350
1351 let edge_id = store.create_edge_with_props(src, dst, "LIVES_IN", &props);
1352 let edge = store.get_edge(edge_id).expect("edge should exist");
1353 assert_eq!(edge.src, src);
1354 assert_eq!(edge.dst, dst);
1355 assert_eq!(edge.edge_type.as_str(), "LIVES_IN");
1356 assert_eq!(
1357 edge.properties.get(&PropertyKey::from("since")),
1358 Some(&Value::Int64(2021))
1359 );
1360 assert_eq!(
1361 edge.properties.get(&PropertyKey::from("role")),
1362 Some(&Value::from("resident"))
1363 );
1364
1365 assert_eq!(
1367 store
1368 .edge_type(edge_id)
1369 .as_ref()
1370 .map(arcstr::ArcStr::as_str),
1371 Some("LIVES_IN")
1372 );
1373
1374 let bare = store.create_edge_with_props(src, dst, "VISITED", &[]);
1376 let bare_edge = store.get_edge(bare).expect("bare edge should exist");
1377 assert!(bare_edge.properties.is_empty());
1378 }
1379
1380 #[test]
1381 fn test_mut_store_object_safe_dyn_dispatch() {
1382 let store: Arc<dyn GraphStoreSearch> = Arc::new(TestMutStore::new());
1384 assert_eq!(store.node_count(), 0);
1385 assert_eq!(store.edge_count(), 0);
1386 assert!(store.node_ids().is_empty());
1387 assert!(store.get_node(NodeId(1)).is_none());
1388 assert_eq!(store.current_epoch(), EpochId(0));
1389 }
1390}