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 fn find_nodes_in_range_iter<'a>(
373 &'a self,
374 property: &'a str,
375 min: Option<&'a Value>,
376 max: Option<&'a Value>,
377 min_inclusive: bool,
378 max_inclusive: bool,
379 ) -> Box<dyn Iterator<Item = NodeId> + 'a> {
380 Box::new(
381 self.find_nodes_in_range(property, min, max, min_inclusive, max_inclusive)
382 .into_iter(),
383 )
384 }
385
386 #[cfg(feature = "text-index")]
390 #[must_use]
391 fn has_text_index(&self, _label: &str, _property: &str) -> bool {
392 false
393 }
394
395 #[cfg(feature = "text-index")]
401 fn score_text(
402 &self,
403 _node_id: NodeId,
404 _label: &str,
405 _property: &str,
406 _query: &str,
407 ) -> Option<f64> {
408 None
409 }
410
411 #[cfg(feature = "text-index")]
416 fn text_search(
417 &self,
418 _label: &str,
419 _property: &str,
420 _query: &str,
421 _k: usize,
422 ) -> Vec<(NodeId, f64)> {
423 Vec::new()
424 }
425
426 #[cfg(feature = "text-index")]
428 fn text_search_with_threshold(
429 &self,
430 _label: &str,
431 _property: &str,
432 _query: &str,
433 _threshold: f64,
434 ) -> Vec<(NodeId, f64)> {
435 Vec::new()
436 }
437
438 #[cfg(feature = "vector-index")]
442 #[must_use]
443 fn has_vector_index(&self, _label: &str, _property: &str) -> bool {
444 false
445 }
446
447 #[cfg(feature = "vector-index")]
454 fn vector_index_metric(&self, _label: &str, _property: &str) -> Option<DistanceMetric> {
455 None
456 }
457
458 #[cfg(feature = "vector-index")]
467 fn vector_search(
468 &self,
469 _label: Option<&str>,
470 _property: &str,
471 _query: &[f32],
472 _k: usize,
473 _metric: DistanceMetric,
474 ) -> Vec<(NodeId, f64)> {
475 Vec::new()
476 }
477
478 #[cfg(feature = "vector-index")]
480 fn vector_search_with_threshold(
481 &self,
482 _label: Option<&str>,
483 _property: &str,
484 _query: &[f32],
485 _threshold: f64,
486 _metric: DistanceMetric,
487 ) -> Vec<(NodeId, f64)> {
488 Vec::new()
489 }
490}
491
492pub trait GraphStoreMut: GraphStoreSearch {
498 fn create_node(&self, labels: &[&str]) -> NodeId;
502
503 fn create_node_versioned(
505 &self,
506 labels: &[&str],
507 epoch: EpochId,
508 transaction_id: TransactionId,
509 ) -> NodeId;
510
511 fn create_edge(&self, src: NodeId, dst: NodeId, edge_type: &str) -> EdgeId;
515
516 fn create_edge_versioned(
518 &self,
519 src: NodeId,
520 dst: NodeId,
521 edge_type: &str,
522 epoch: EpochId,
523 transaction_id: TransactionId,
524 ) -> EdgeId;
525
526 fn batch_create_edges(&self, edges: &[(NodeId, NodeId, &str)]) -> Vec<EdgeId>;
528
529 fn delete_node(&self, id: NodeId) -> bool;
533
534 fn delete_node_versioned(
536 &self,
537 id: NodeId,
538 epoch: EpochId,
539 transaction_id: TransactionId,
540 ) -> bool;
541
542 fn delete_node_edges(&self, node_id: NodeId);
544
545 fn delete_edge(&self, id: EdgeId) -> bool;
547
548 fn delete_edge_versioned(
550 &self,
551 id: EdgeId,
552 epoch: EpochId,
553 transaction_id: TransactionId,
554 ) -> bool;
555
556 fn set_node_property(&self, id: NodeId, key: &str, value: Value);
560
561 fn set_edge_property(&self, id: EdgeId, key: &str, value: Value);
563
564 fn set_node_property_versioned(
569 &self,
570 id: NodeId,
571 key: &str,
572 value: Value,
573 _transaction_id: TransactionId,
574 ) {
575 self.set_node_property(id, key, value);
576 }
577
578 fn set_edge_property_versioned(
583 &self,
584 id: EdgeId,
585 key: &str,
586 value: Value,
587 _transaction_id: TransactionId,
588 ) {
589 self.set_edge_property(id, key, value);
590 }
591
592 fn remove_node_property(&self, id: NodeId, key: &str) -> Option<Value>;
594
595 fn remove_edge_property(&self, id: EdgeId, key: &str) -> Option<Value>;
597
598 fn remove_node_property_versioned(
603 &self,
604 id: NodeId,
605 key: &str,
606 _transaction_id: TransactionId,
607 ) -> Option<Value> {
608 self.remove_node_property(id, key)
609 }
610
611 fn remove_edge_property_versioned(
616 &self,
617 id: EdgeId,
618 key: &str,
619 _transaction_id: TransactionId,
620 ) -> Option<Value> {
621 self.remove_edge_property(id, key)
622 }
623
624 fn add_label(&self, node_id: NodeId, label: &str) -> bool;
628
629 fn remove_label(&self, node_id: NodeId, label: &str) -> bool;
631
632 fn add_label_versioned(
636 &self,
637 node_id: NodeId,
638 label: &str,
639 _transaction_id: TransactionId,
640 ) -> bool {
641 self.add_label(node_id, label)
642 }
643
644 fn remove_label_versioned(
648 &self,
649 node_id: NodeId,
650 label: &str,
651 _transaction_id: TransactionId,
652 ) -> bool {
653 self.remove_label(node_id, label)
654 }
655
656 fn create_node_with_props(
664 &self,
665 labels: &[&str],
666 properties: &[(PropertyKey, Value)],
667 ) -> NodeId {
668 let id = self.create_node(labels);
669 for (key, value) in properties {
670 self.set_node_property(id, key.as_str(), value.clone());
671 }
672 id
673 }
674
675 fn create_edge_with_props(
681 &self,
682 src: NodeId,
683 dst: NodeId,
684 edge_type: &str,
685 properties: &[(PropertyKey, Value)],
686 ) -> EdgeId {
687 let id = self.create_edge(src, dst, edge_type);
688 for (key, value) in properties {
689 self.set_edge_property(id, key.as_str(), value.clone());
690 }
691 id
692 }
693}
694
695pub struct NullGraphStore;
702
703impl GraphStore for NullGraphStore {
704 fn get_node(&self, _: NodeId) -> Option<Node> {
705 None
706 }
707 fn get_edge(&self, _: EdgeId) -> Option<Edge> {
708 None
709 }
710 fn get_node_versioned(&self, _: NodeId, _: EpochId, _: TransactionId) -> Option<Node> {
711 None
712 }
713 fn get_edge_versioned(&self, _: EdgeId, _: EpochId, _: TransactionId) -> Option<Edge> {
714 None
715 }
716 fn get_node_at_epoch(&self, _: NodeId, _: EpochId) -> Option<Node> {
717 None
718 }
719 fn get_edge_at_epoch(&self, _: EdgeId, _: EpochId) -> Option<Edge> {
720 None
721 }
722 fn get_node_property(&self, _: NodeId, _: &PropertyKey) -> Option<Value> {
723 None
724 }
725 fn get_edge_property(&self, _: EdgeId, _: &PropertyKey) -> Option<Value> {
726 None
727 }
728 fn get_node_property_batch(&self, ids: &[NodeId], _: &PropertyKey) -> Vec<Option<Value>> {
729 vec![None; ids.len()]
730 }
731 fn get_nodes_properties_batch(&self, ids: &[NodeId]) -> Vec<FxHashMap<PropertyKey, Value>> {
732 vec![FxHashMap::default(); ids.len()]
733 }
734 fn get_nodes_properties_selective_batch(
735 &self,
736 ids: &[NodeId],
737 _: &[PropertyKey],
738 ) -> Vec<FxHashMap<PropertyKey, Value>> {
739 vec![FxHashMap::default(); ids.len()]
740 }
741 fn get_edges_properties_selective_batch(
742 &self,
743 ids: &[EdgeId],
744 _: &[PropertyKey],
745 ) -> Vec<FxHashMap<PropertyKey, Value>> {
746 vec![FxHashMap::default(); ids.len()]
747 }
748 fn neighbors(&self, _: NodeId, _: Direction) -> Vec<NodeId> {
749 Vec::new()
750 }
751 fn edges_from(&self, _: NodeId, _: Direction) -> Vec<(NodeId, EdgeId)> {
752 Vec::new()
753 }
754 fn out_degree(&self, _: NodeId) -> usize {
755 0
756 }
757 fn in_degree(&self, _: NodeId) -> usize {
758 0
759 }
760 fn has_backward_adjacency(&self) -> bool {
761 false
762 }
763 fn node_ids(&self) -> Vec<NodeId> {
764 Vec::new()
765 }
766 fn nodes_by_label(&self, _: &str) -> Vec<NodeId> {
767 Vec::new()
768 }
769 fn node_count(&self) -> usize {
770 0
771 }
772 fn edge_count(&self) -> usize {
773 0
774 }
775 fn edge_type(&self, _: EdgeId) -> Option<ArcStr> {
776 None
777 }
778 fn find_nodes_by_property(&self, _: &str, _: &Value) -> Vec<NodeId> {
779 Vec::new()
780 }
781 fn find_nodes_by_properties(&self, _: &[(&str, Value)]) -> Vec<NodeId> {
782 Vec::new()
783 }
784 fn find_nodes_in_range(
785 &self,
786 _: &str,
787 _: Option<&Value>,
788 _: Option<&Value>,
789 _: bool,
790 _: bool,
791 ) -> Vec<NodeId> {
792 Vec::new()
793 }
794 fn node_property_might_match(&self, _: &PropertyKey, _: CompareOp, _: &Value) -> bool {
795 false
796 }
797 fn edge_property_might_match(&self, _: &PropertyKey, _: CompareOp, _: &Value) -> bool {
798 false
799 }
800 fn statistics(&self) -> Arc<Statistics> {
801 Arc::new(Statistics::default())
802 }
803 fn estimate_label_cardinality(&self, _: &str) -> f64 {
804 0.0
805 }
806 fn estimate_avg_degree(&self, _: &str, _: bool) -> f64 {
807 0.0
808 }
809 fn current_epoch(&self) -> EpochId {
810 EpochId(0)
811 }
812}
813
814impl GraphStoreSearch for NullGraphStore {}
815
816#[cfg(test)]
817mod tests {
818 use super::*;
819 use std::sync::Mutex;
820
821 #[test]
822 fn null_graph_store_point_lookups() {
823 let store = NullGraphStore;
824 let nid = NodeId(1);
825 let eid = EdgeId(1);
826 let epoch = EpochId(0);
827 let txn = TransactionId(1);
828
829 assert!(store.get_node(nid).is_none());
830 assert!(store.get_edge(eid).is_none());
831 assert!(store.get_node_versioned(nid, epoch, txn).is_none());
832 assert!(store.get_edge_versioned(eid, epoch, txn).is_none());
833 assert!(store.get_node_at_epoch(nid, epoch).is_none());
834 assert!(store.get_edge_at_epoch(eid, epoch).is_none());
835 }
836
837 #[test]
838 fn null_graph_store_property_access() {
839 let store = NullGraphStore;
840 let nid = NodeId(1);
841 let eid = EdgeId(1);
842 let key = PropertyKey::from("name");
843
844 assert!(store.get_node_property(nid, &key).is_none());
845 assert!(store.get_edge_property(eid, &key).is_none());
846 assert_eq!(
847 store.get_node_property_batch(&[nid, NodeId(2)], &key),
848 vec![None, None]
849 );
850
851 let node_props = store.get_nodes_properties_batch(&[nid]);
852 assert_eq!(node_props.len(), 1);
853 assert!(node_props[0].is_empty());
854
855 let selective =
856 store.get_nodes_properties_selective_batch(&[nid], std::slice::from_ref(&key));
857 assert_eq!(selective.len(), 1);
858 assert!(selective[0].is_empty());
859
860 let edge_selective = store.get_edges_properties_selective_batch(&[eid], &[key]);
861 assert_eq!(edge_selective.len(), 1);
862 assert!(edge_selective[0].is_empty());
863 }
864
865 #[test]
866 fn null_graph_store_traversal() {
867 let store = NullGraphStore;
868 let nid = NodeId(1);
869
870 assert!(store.neighbors(nid, Direction::Outgoing).is_empty());
871 assert!(store.edges_from(nid, Direction::Incoming).is_empty());
872 assert_eq!(store.out_degree(nid), 0);
873 assert_eq!(store.in_degree(nid), 0);
874 assert!(!store.has_backward_adjacency());
875 }
876
877 #[test]
878 fn null_graph_store_scans_and_counts() {
879 let store = NullGraphStore;
880
881 assert!(store.node_ids().is_empty());
882 assert!(store.all_node_ids().is_empty());
883 assert!(store.nodes_by_label("Person").is_empty());
884 assert_eq!(store.node_count(), 0);
885 assert_eq!(store.edge_count(), 0);
886 }
887
888 #[test]
889 fn null_graph_store_metadata_and_schema() {
890 let store = NullGraphStore;
891 let eid = EdgeId(1);
892 let epoch = EpochId(0);
893 let txn = TransactionId(1);
894
895 assert!(store.edge_type(eid).is_none());
896 assert!(store.edge_type_versioned(eid, epoch, txn).is_none());
897 assert!(!store.has_property_index("name"));
898 assert!(store.all_labels().is_empty());
899 assert!(store.all_edge_types().is_empty());
900 assert!(store.all_property_keys().is_empty());
901 }
902
903 #[test]
904 fn null_graph_store_search() {
905 let store = NullGraphStore;
906 let key = PropertyKey::from("age");
907 let val = Value::Int64(30);
908
909 assert!(store.find_nodes_by_property("age", &val).is_empty());
910 assert!(
911 store
912 .find_nodes_by_properties(&[("age", val.clone())])
913 .is_empty()
914 );
915 assert!(
916 store
917 .find_nodes_in_range("age", Some(&val), None, true, false)
918 .is_empty()
919 );
920 assert!(!store.node_property_might_match(&key, CompareOp::Eq, &val));
921 assert!(!store.edge_property_might_match(&key, CompareOp::Eq, &val));
922 }
923
924 #[test]
925 fn null_graph_store_statistics() {
926 let store = NullGraphStore;
927
928 let _stats = store.statistics();
929 assert_eq!(store.estimate_label_cardinality("Person"), 0.0);
930 assert_eq!(store.estimate_avg_degree("KNOWS", true), 0.0);
931 assert_eq!(store.current_epoch(), EpochId(0));
932 }
933
934 #[test]
935 fn null_graph_store_visibility() {
936 let store = NullGraphStore;
937 let nid = NodeId(1);
938 let eid = EdgeId(1);
939 let epoch = EpochId(0);
940 let txn = TransactionId(1);
941
942 assert!(!store.is_node_visible_at_epoch(nid, epoch));
943 assert!(!store.is_node_visible_versioned(nid, epoch, txn));
944 assert!(!store.is_edge_visible_at_epoch(eid, epoch));
945 assert!(!store.is_edge_visible_versioned(eid, epoch, txn));
946
947 assert!(
948 store
949 .filter_visible_node_ids(&[nid, NodeId(2)], epoch)
950 .is_empty()
951 );
952 assert!(
953 store
954 .filter_visible_node_ids_versioned(&[nid], epoch, txn)
955 .is_empty()
956 );
957 }
958
959 #[test]
960 fn null_graph_store_history() {
961 let store = NullGraphStore;
962
963 assert!(store.get_node_history(NodeId(1)).is_empty());
964 assert!(store.get_edge_history(EdgeId(1)).is_empty());
965 }
966
967 #[derive(Default)]
971 struct TestMutStore {
972 inner: Mutex<TestMutInner>,
973 }
974
975 #[derive(Default)]
976 struct TestMutInner {
977 next_node: u64,
978 next_edge: u64,
979 nodes: Vec<Node>,
980 edges: Vec<Edge>,
981 }
982
983 impl TestMutStore {
984 fn new() -> Self {
985 Self::default()
986 }
987
988 fn find_node(&self, id: NodeId) -> Option<Node> {
989 self.inner
990 .lock()
991 .unwrap()
992 .nodes
993 .iter()
994 .find(|n| n.id == id)
995 .cloned()
996 }
997
998 fn find_edge(&self, id: EdgeId) -> Option<Edge> {
999 self.inner
1000 .lock()
1001 .unwrap()
1002 .edges
1003 .iter()
1004 .find(|e| e.id == id)
1005 .cloned()
1006 }
1007 }
1008
1009 impl GraphStore for TestMutStore {
1010 fn get_node(&self, id: NodeId) -> Option<Node> {
1011 self.find_node(id)
1012 }
1013 fn get_edge(&self, id: EdgeId) -> Option<Edge> {
1014 self.find_edge(id)
1015 }
1016 fn get_node_versioned(&self, id: NodeId, _: EpochId, _: TransactionId) -> Option<Node> {
1017 self.find_node(id)
1018 }
1019 fn get_edge_versioned(&self, id: EdgeId, _: EpochId, _: TransactionId) -> Option<Edge> {
1020 self.find_edge(id)
1021 }
1022 fn get_node_at_epoch(&self, id: NodeId, _: EpochId) -> Option<Node> {
1023 self.find_node(id)
1024 }
1025 fn get_edge_at_epoch(&self, id: EdgeId, _: EpochId) -> Option<Edge> {
1026 self.find_edge(id)
1027 }
1028 fn get_node_property(&self, id: NodeId, key: &PropertyKey) -> Option<Value> {
1029 self.find_node(id)
1030 .and_then(|n| n.properties.get(key).cloned())
1031 }
1032 fn get_edge_property(&self, id: EdgeId, key: &PropertyKey) -> Option<Value> {
1033 self.find_edge(id)
1034 .and_then(|e| e.properties.get(key).cloned())
1035 }
1036 fn get_node_property_batch(&self, ids: &[NodeId], key: &PropertyKey) -> Vec<Option<Value>> {
1037 ids.iter()
1038 .map(|id| self.get_node_property(*id, key))
1039 .collect()
1040 }
1041 fn get_nodes_properties_batch(&self, ids: &[NodeId]) -> Vec<FxHashMap<PropertyKey, Value>> {
1042 ids.iter()
1043 .map(|id| {
1044 let mut map = FxHashMap::default();
1045 if let Some(n) = self.find_node(*id) {
1046 for (k, v) in n.properties.iter() {
1047 map.insert(k.clone(), v.clone());
1048 }
1049 }
1050 map
1051 })
1052 .collect()
1053 }
1054 fn get_nodes_properties_selective_batch(
1055 &self,
1056 ids: &[NodeId],
1057 _: &[PropertyKey],
1058 ) -> Vec<FxHashMap<PropertyKey, Value>> {
1059 vec![FxHashMap::default(); ids.len()]
1060 }
1061 fn get_edges_properties_selective_batch(
1062 &self,
1063 ids: &[EdgeId],
1064 _: &[PropertyKey],
1065 ) -> Vec<FxHashMap<PropertyKey, Value>> {
1066 vec![FxHashMap::default(); ids.len()]
1067 }
1068 fn neighbors(&self, _: NodeId, _: Direction) -> Vec<NodeId> {
1069 Vec::new()
1070 }
1071 fn edges_from(&self, _: NodeId, _: Direction) -> Vec<(NodeId, EdgeId)> {
1072 Vec::new()
1073 }
1074 fn out_degree(&self, _: NodeId) -> usize {
1075 0
1076 }
1077 fn in_degree(&self, _: NodeId) -> usize {
1078 0
1079 }
1080 fn has_backward_adjacency(&self) -> bool {
1081 false
1082 }
1083 fn node_ids(&self) -> Vec<NodeId> {
1084 self.inner
1085 .lock()
1086 .unwrap()
1087 .nodes
1088 .iter()
1089 .map(|n| n.id)
1090 .collect()
1091 }
1092 fn nodes_by_label(&self, _: &str) -> Vec<NodeId> {
1093 Vec::new()
1094 }
1095 fn node_count(&self) -> usize {
1096 self.inner.lock().unwrap().nodes.len()
1097 }
1098 fn edge_count(&self) -> usize {
1099 self.inner.lock().unwrap().edges.len()
1100 }
1101 fn edge_type(&self, id: EdgeId) -> Option<ArcStr> {
1102 self.find_edge(id).map(|e| e.edge_type)
1103 }
1104 fn find_nodes_by_property(&self, _: &str, _: &Value) -> Vec<NodeId> {
1105 Vec::new()
1106 }
1107 fn find_nodes_by_properties(&self, _: &[(&str, Value)]) -> Vec<NodeId> {
1108 Vec::new()
1109 }
1110 fn find_nodes_in_range(
1111 &self,
1112 _: &str,
1113 _: Option<&Value>,
1114 _: Option<&Value>,
1115 _: bool,
1116 _: bool,
1117 ) -> Vec<NodeId> {
1118 Vec::new()
1119 }
1120 fn node_property_might_match(&self, _: &PropertyKey, _: CompareOp, _: &Value) -> bool {
1121 true
1122 }
1123 fn edge_property_might_match(&self, _: &PropertyKey, _: CompareOp, _: &Value) -> bool {
1124 true
1125 }
1126 fn statistics(&self) -> Arc<Statistics> {
1127 Arc::new(Statistics::default())
1128 }
1129 fn estimate_label_cardinality(&self, _: &str) -> f64 {
1130 0.0
1131 }
1132 fn estimate_avg_degree(&self, _: &str, _: bool) -> f64 {
1133 0.0
1134 }
1135 fn current_epoch(&self) -> EpochId {
1136 EpochId(0)
1137 }
1138 }
1139
1140 impl GraphStoreSearch for TestMutStore {}
1141
1142 impl GraphStoreMut for TestMutStore {
1143 fn create_node(&self, labels: &[&str]) -> NodeId {
1144 let mut inner = self.inner.lock().unwrap();
1145 inner.next_node += 1;
1146 let id = NodeId(inner.next_node);
1147 let mut node = Node::new(id);
1148 for label in labels {
1149 node.add_label(*label);
1150 }
1151 inner.nodes.push(node);
1152 id
1153 }
1154 fn create_node_versioned(&self, labels: &[&str], _: EpochId, _: TransactionId) -> NodeId {
1155 self.create_node(labels)
1156 }
1157 fn create_edge(&self, src: NodeId, dst: NodeId, edge_type: &str) -> EdgeId {
1158 let mut inner = self.inner.lock().unwrap();
1159 inner.next_edge += 1;
1160 let id = EdgeId(inner.next_edge);
1161 inner.edges.push(Edge::new(id, src, dst, edge_type));
1162 id
1163 }
1164 fn create_edge_versioned(
1165 &self,
1166 src: NodeId,
1167 dst: NodeId,
1168 edge_type: &str,
1169 _: EpochId,
1170 _: TransactionId,
1171 ) -> EdgeId {
1172 self.create_edge(src, dst, edge_type)
1173 }
1174 fn batch_create_edges(&self, edges: &[(NodeId, NodeId, &str)]) -> Vec<EdgeId> {
1175 edges
1176 .iter()
1177 .map(|(s, d, t)| self.create_edge(*s, *d, t))
1178 .collect()
1179 }
1180 fn delete_node(&self, id: NodeId) -> bool {
1181 let mut inner = self.inner.lock().unwrap();
1182 if let Some(pos) = inner.nodes.iter().position(|n| n.id == id) {
1183 inner.nodes.remove(pos);
1184 true
1185 } else {
1186 false
1187 }
1188 }
1189 fn delete_node_versioned(&self, id: NodeId, _: EpochId, _: TransactionId) -> bool {
1190 self.delete_node(id)
1191 }
1192 fn delete_node_edges(&self, node_id: NodeId) {
1193 let mut inner = self.inner.lock().unwrap();
1194 inner.edges.retain(|e| e.src != node_id && e.dst != node_id);
1195 }
1196 fn delete_edge(&self, id: EdgeId) -> bool {
1197 let mut inner = self.inner.lock().unwrap();
1198 if let Some(pos) = inner.edges.iter().position(|e| e.id == id) {
1199 inner.edges.remove(pos);
1200 true
1201 } else {
1202 false
1203 }
1204 }
1205 fn delete_edge_versioned(&self, id: EdgeId, _: EpochId, _: TransactionId) -> bool {
1206 self.delete_edge(id)
1207 }
1208 fn set_node_property(&self, id: NodeId, key: &str, value: Value) {
1209 let mut inner = self.inner.lock().unwrap();
1210 if let Some(node) = inner.nodes.iter_mut().find(|n| n.id == id) {
1211 node.set_property(key, value);
1212 }
1213 }
1214 fn set_edge_property(&self, id: EdgeId, key: &str, value: Value) {
1215 let mut inner = self.inner.lock().unwrap();
1216 if let Some(edge) = inner.edges.iter_mut().find(|e| e.id == id) {
1217 edge.set_property(key, value);
1218 }
1219 }
1220 fn remove_node_property(&self, id: NodeId, key: &str) -> Option<Value> {
1221 let mut inner = self.inner.lock().unwrap();
1222 inner
1223 .nodes
1224 .iter_mut()
1225 .find(|n| n.id == id)
1226 .and_then(|n| n.remove_property(key))
1227 }
1228 fn remove_edge_property(&self, id: EdgeId, key: &str) -> Option<Value> {
1229 let mut inner = self.inner.lock().unwrap();
1230 inner
1231 .edges
1232 .iter_mut()
1233 .find(|e| e.id == id)
1234 .and_then(|e| e.remove_property(key))
1235 }
1236 fn add_label(&self, node_id: NodeId, label: &str) -> bool {
1237 let mut inner = self.inner.lock().unwrap();
1238 if let Some(node) = inner.nodes.iter_mut().find(|n| n.id == node_id) {
1239 if node.has_label(label) {
1240 false
1241 } else {
1242 node.add_label(label);
1243 true
1244 }
1245 } else {
1246 false
1247 }
1248 }
1249 fn remove_label(&self, node_id: NodeId, label: &str) -> bool {
1250 let mut inner = self.inner.lock().unwrap();
1251 inner
1252 .nodes
1253 .iter_mut()
1254 .find(|n| n.id == node_id)
1255 .is_some_and(|n| n.remove_label(label))
1256 }
1257 }
1258
1259 #[test]
1260 fn test_mut_store_default_set_versioned_property_delegates() {
1261 let store = TestMutStore::new();
1262 let id = store.create_node(&["Person"]);
1263 let key = PropertyKey::from("name");
1264 let txn = TransactionId(7);
1265
1266 store.set_node_property_versioned(id, "name", Value::from("Vincent"), txn);
1268 assert_eq!(
1269 store.get_node_property(id, &key),
1270 Some(Value::from("Vincent"))
1271 );
1272
1273 let edge_id = {
1274 let src = store.create_node(&["Person"]);
1275 let dst = store.create_node(&["City"]);
1276 store.create_edge(src, dst, "LIVES_IN")
1277 };
1278 let since = PropertyKey::from("since");
1279 store.set_edge_property_versioned(edge_id, "since", Value::Int64(1994), txn);
1280 assert_eq!(
1281 store.get_edge_property(edge_id, &since),
1282 Some(Value::Int64(1994))
1283 );
1284 }
1285
1286 #[test]
1287 fn test_mut_store_default_remove_versioned_property_delegates() {
1288 let store = TestMutStore::new();
1289 let txn = TransactionId(11);
1290
1291 let node_id = store.create_node(&["Person"]);
1292 store.set_node_property(node_id, "city", Value::from("Amsterdam"));
1293 let removed = store.remove_node_property_versioned(node_id, "city", txn);
1294 assert_eq!(removed, Some(Value::from("Amsterdam")));
1295 assert!(
1296 store
1297 .get_node_property(node_id, &PropertyKey::from("city"))
1298 .is_none()
1299 );
1300
1301 let missing = store.remove_node_property_versioned(node_id, "absent", txn);
1302 assert!(missing.is_none());
1303
1304 let src = store.create_node(&["Person"]);
1305 let dst = store.create_node(&["Person"]);
1306 let edge_id = store.create_edge(src, dst, "KNOWS");
1307 store.set_edge_property(edge_id, "weight", Value::Int64(42));
1308 let removed_edge = store.remove_edge_property_versioned(edge_id, "weight", txn);
1309 assert_eq!(removed_edge, Some(Value::Int64(42)));
1310 let removed_again = store.remove_edge_property_versioned(edge_id, "weight", txn);
1311 assert!(removed_again.is_none());
1312 }
1313
1314 #[test]
1315 fn test_mut_store_default_label_versioned_delegates() {
1316 let store = TestMutStore::new();
1317 let txn = TransactionId(3);
1318 let id = store.create_node(&["Person"]);
1319
1320 assert!(store.add_label_versioned(id, "Director", txn));
1322 assert!(!store.add_label_versioned(id, "Director", txn));
1323
1324 assert!(store.remove_label_versioned(id, "Director", txn));
1326 assert!(!store.remove_label_versioned(id, "Director", txn));
1327
1328 let unknown = NodeId(9999);
1330 assert!(!store.add_label_versioned(unknown, "Ghost", txn));
1331 assert!(!store.remove_label_versioned(unknown, "Ghost", txn));
1332 }
1333
1334 #[test]
1335 fn test_mut_store_default_create_node_with_props() {
1336 let store = TestMutStore::new();
1337 let props = vec![
1338 (PropertyKey::from("name"), Value::from("Jules")),
1339 (PropertyKey::from("city"), Value::from("Paris")),
1340 ];
1341
1342 let id = store.create_node_with_props(&["Person"], &props);
1343 let node = store.get_node(id).expect("node should exist");
1344 assert!(node.has_label("Person"));
1345 assert_eq!(
1346 node.properties.get(&PropertyKey::from("name")),
1347 Some(&Value::from("Jules"))
1348 );
1349 assert_eq!(
1350 node.properties.get(&PropertyKey::from("city")),
1351 Some(&Value::from("Paris"))
1352 );
1353
1354 let bare = store.create_node_with_props(&["Person"], &[]);
1356 let bare_node = store.get_node(bare).expect("bare node should exist");
1357 assert!(bare_node.properties.is_empty());
1358 }
1359
1360 #[test]
1361 fn test_mut_store_default_create_edge_with_props() {
1362 let store = TestMutStore::new();
1363 let src = store.create_node_with_props(
1364 &["Person"],
1365 &[(PropertyKey::from("name"), Value::from("Mia"))],
1366 );
1367 let dst = store.create_node_with_props(
1368 &["City"],
1369 &[(PropertyKey::from("name"), Value::from("Berlin"))],
1370 );
1371 let props = vec![
1372 (PropertyKey::from("since"), Value::Int64(2021)),
1373 (PropertyKey::from("role"), Value::from("resident")),
1374 ];
1375
1376 let edge_id = store.create_edge_with_props(src, dst, "LIVES_IN", &props);
1377 let edge = store.get_edge(edge_id).expect("edge should exist");
1378 assert_eq!(edge.src, src);
1379 assert_eq!(edge.dst, dst);
1380 assert_eq!(edge.edge_type.as_str(), "LIVES_IN");
1381 assert_eq!(
1382 edge.properties.get(&PropertyKey::from("since")),
1383 Some(&Value::Int64(2021))
1384 );
1385 assert_eq!(
1386 edge.properties.get(&PropertyKey::from("role")),
1387 Some(&Value::from("resident"))
1388 );
1389
1390 assert_eq!(
1392 store
1393 .edge_type(edge_id)
1394 .as_ref()
1395 .map(arcstr::ArcStr::as_str),
1396 Some("LIVES_IN")
1397 );
1398
1399 let bare = store.create_edge_with_props(src, dst, "VISITED", &[]);
1401 let bare_edge = store.get_edge(bare).expect("bare edge should exist");
1402 assert!(bare_edge.properties.is_empty());
1403 }
1404
1405 #[test]
1406 fn test_mut_store_object_safe_dyn_dispatch() {
1407 let store: Arc<dyn GraphStoreSearch> = Arc::new(TestMutStore::new());
1409 assert_eq!(store.node_count(), 0);
1410 assert_eq!(store.edge_count(), 0);
1411 assert!(store.node_ids().is_empty());
1412 assert!(store.get_node(NodeId(1)).is_none());
1413 assert_eq!(store.current_epoch(), EpochId(0));
1414 }
1415}