Skip to main content

cypherlite_core/
traits.rs

1// Transaction and registry trait definitions
2
3/// A read-only view of the database at a specific point in time.
4pub trait TransactionView {
5    /// Returns the WAL frame number representing this transaction's snapshot.
6    fn snapshot_frame(&self) -> u64;
7}
8
9/// Registry for resolving string names to u32 IDs (labels, property keys, relationship types).
10pub trait LabelRegistry {
11    /// Get or create a label ID for the given name.
12    fn get_or_create_label(&mut self, name: &str) -> u32;
13    /// Look up a label ID by name (returns None if not found).
14    fn label_id(&self, name: &str) -> Option<u32>;
15    /// Look up a label name by ID (returns None if not found).
16    fn label_name(&self, id: u32) -> Option<&str>;
17
18    /// Get or create a relationship type ID.
19    fn get_or_create_rel_type(&mut self, name: &str) -> u32;
20    /// Look up a relationship type ID by name.
21    fn rel_type_id(&self, name: &str) -> Option<u32>;
22    /// Look up a relationship type name by ID.
23    fn rel_type_name(&self, id: u32) -> Option<&str>;
24
25    /// Get or create a property key ID.
26    fn get_or_create_prop_key(&mut self, name: &str) -> u32;
27    /// Look up a property key ID by name.
28    fn prop_key_id(&self, name: &str) -> Option<u32>;
29    /// Look up a property key name by ID.
30    fn prop_key_name(&self, id: u32) -> Option<&str>;
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use std::collections::HashMap;
37
38    struct MockTxView {
39        frame: u64,
40    }
41
42    impl TransactionView for MockTxView {
43        fn snapshot_frame(&self) -> u64 {
44            self.frame
45        }
46    }
47
48    // REQ-TX-001: Read transactions capture snapshot point
49    #[test]
50    fn test_transaction_view_snapshot_frame() {
51        let view = MockTxView { frame: 42 };
52        assert_eq!(view.snapshot_frame(), 42);
53    }
54
55    #[test]
56    fn test_transaction_view_zero_frame() {
57        let view = MockTxView { frame: 0 };
58        assert_eq!(view.snapshot_frame(), 0);
59    }
60
61    // Verify trait is object-safe
62    #[test]
63    fn test_transaction_view_is_object_safe() {
64        let view: Box<dyn TransactionView> = Box::new(MockTxView { frame: 10 });
65        assert_eq!(view.snapshot_frame(), 10);
66    }
67
68    // -- LabelRegistry tests --
69
70    struct MockRegistry {
71        labels: HashMap<String, u32>,
72        labels_rev: HashMap<u32, String>,
73        label_next: u32,
74        rel_types: HashMap<String, u32>,
75        rel_types_rev: HashMap<u32, String>,
76        rel_next: u32,
77        prop_keys: HashMap<String, u32>,
78        prop_keys_rev: HashMap<u32, String>,
79        prop_next: u32,
80    }
81
82    impl MockRegistry {
83        fn new() -> Self {
84            Self {
85                labels: HashMap::new(),
86                labels_rev: HashMap::new(),
87                label_next: 0,
88                rel_types: HashMap::new(),
89                rel_types_rev: HashMap::new(),
90                rel_next: 0,
91                prop_keys: HashMap::new(),
92                prop_keys_rev: HashMap::new(),
93                prop_next: 0,
94            }
95        }
96    }
97
98    impl LabelRegistry for MockRegistry {
99        fn get_or_create_label(&mut self, name: &str) -> u32 {
100            if let Some(&id) = self.labels.get(name) {
101                return id;
102            }
103            let id = self.label_next;
104            self.label_next += 1;
105            self.labels.insert(name.to_string(), id);
106            self.labels_rev.insert(id, name.to_string());
107            id
108        }
109
110        fn label_id(&self, name: &str) -> Option<u32> {
111            self.labels.get(name).copied()
112        }
113
114        fn label_name(&self, id: u32) -> Option<&str> {
115            self.labels_rev.get(&id).map(|s| s.as_str())
116        }
117
118        fn get_or_create_rel_type(&mut self, name: &str) -> u32 {
119            if let Some(&id) = self.rel_types.get(name) {
120                return id;
121            }
122            let id = self.rel_next;
123            self.rel_next += 1;
124            self.rel_types.insert(name.to_string(), id);
125            self.rel_types_rev.insert(id, name.to_string());
126            id
127        }
128
129        fn rel_type_id(&self, name: &str) -> Option<u32> {
130            self.rel_types.get(name).copied()
131        }
132
133        fn rel_type_name(&self, id: u32) -> Option<&str> {
134            self.rel_types_rev.get(&id).map(|s| s.as_str())
135        }
136
137        fn get_or_create_prop_key(&mut self, name: &str) -> u32 {
138            if let Some(&id) = self.prop_keys.get(name) {
139                return id;
140            }
141            let id = self.prop_next;
142            self.prop_next += 1;
143            self.prop_keys.insert(name.to_string(), id);
144            self.prop_keys_rev.insert(id, name.to_string());
145            id
146        }
147
148        fn prop_key_id(&self, name: &str) -> Option<u32> {
149            self.prop_keys.get(name).copied()
150        }
151
152        fn prop_key_name(&self, id: u32) -> Option<&str> {
153            self.prop_keys_rev.get(&id).map(|s| s.as_str())
154        }
155    }
156
157    // REQ-CATALOG-001: Get or create label returns stable IDs
158    #[test]
159    fn test_label_registry_get_or_create_label() {
160        let mut reg = MockRegistry::new();
161        let id1 = reg.get_or_create_label("Person");
162        let id2 = reg.get_or_create_label("Person");
163        assert_eq!(id1, id2, "Same name must return same ID");
164    }
165
166    #[test]
167    fn test_label_registry_different_labels_get_different_ids() {
168        let mut reg = MockRegistry::new();
169        let id1 = reg.get_or_create_label("Person");
170        let id2 = reg.get_or_create_label("Company");
171        assert_ne!(id1, id2);
172    }
173
174    #[test]
175    fn test_label_registry_lookup_by_name() {
176        let mut reg = MockRegistry::new();
177        assert_eq!(reg.label_id("Person"), None);
178        let id = reg.get_or_create_label("Person");
179        assert_eq!(reg.label_id("Person"), Some(id));
180    }
181
182    #[test]
183    fn test_label_registry_lookup_by_id() {
184        let mut reg = MockRegistry::new();
185        assert_eq!(reg.label_name(0), None);
186        let id = reg.get_or_create_label("Person");
187        assert_eq!(reg.label_name(id), Some("Person"));
188    }
189
190    // REQ-CATALOG-002: Relationship type registry
191    #[test]
192    fn test_label_registry_rel_types() {
193        let mut reg = MockRegistry::new();
194        let id = reg.get_or_create_rel_type("KNOWS");
195        assert_eq!(reg.rel_type_id("KNOWS"), Some(id));
196        assert_eq!(reg.rel_type_name(id), Some("KNOWS"));
197        assert_eq!(reg.get_or_create_rel_type("KNOWS"), id);
198    }
199
200    // REQ-CATALOG-003: Property key registry
201    #[test]
202    fn test_label_registry_prop_keys() {
203        let mut reg = MockRegistry::new();
204        let id = reg.get_or_create_prop_key("name");
205        assert_eq!(reg.prop_key_id("name"), Some(id));
206        assert_eq!(reg.prop_key_name(id), Some("name"));
207        assert_eq!(reg.get_or_create_prop_key("name"), id);
208    }
209
210    // REQ-CATALOG-004: Namespaces are independent
211    #[test]
212    fn test_label_registry_namespaces_are_independent() {
213        let mut reg = MockRegistry::new();
214        let label_id = reg.get_or_create_label("name");
215        let rel_id = reg.get_or_create_rel_type("name");
216        let prop_id = reg.get_or_create_prop_key("name");
217        // Same name in different namespaces can have same or different IDs,
218        // but lookups must stay within their namespace.
219        assert_eq!(reg.label_id("name"), Some(label_id));
220        assert_eq!(reg.rel_type_id("name"), Some(rel_id));
221        assert_eq!(reg.prop_key_id("name"), Some(prop_id));
222    }
223}