1use std::collections::{BTreeMap, BTreeSet};
2
3use lora_ast::Direction;
4
5use crate::{
6 GraphStorage, GraphStorageMut, NodeId, NodeRecord, Properties, PropertyValue, RelationshipId,
7 RelationshipRecord,
8};
9
10#[derive(Debug, Clone, Default)]
11pub struct InMemoryGraph {
12 next_node_id: NodeId,
13 next_rel_id: RelationshipId,
14
15 nodes: BTreeMap<NodeId, NodeRecord>,
16 relationships: BTreeMap<RelationshipId, RelationshipRecord>,
17
18 outgoing: BTreeMap<NodeId, BTreeSet<RelationshipId>>,
20 incoming: BTreeMap<NodeId, BTreeSet<RelationshipId>>,
21
22 nodes_by_label: BTreeMap<String, BTreeSet<NodeId>>,
24 relationships_by_type: BTreeMap<String, BTreeSet<RelationshipId>>,
25}
26
27impl InMemoryGraph {
28 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn with_capacity_hint(_nodes: usize, _relationships: usize) -> Self {
33 Self::default()
35 }
36
37 pub fn clear(&mut self) {
38 *self = Self::default();
39 }
40
41 pub fn contains_node(&self, node_id: NodeId) -> bool {
42 self.nodes.contains_key(&node_id)
43 }
44
45 pub fn contains_relationship(&self, rel_id: RelationshipId) -> bool {
46 self.relationships.contains_key(&rel_id)
47 }
48
49 fn alloc_node_id(&mut self) -> NodeId {
50 let id = self.next_node_id;
51 self.next_node_id += 1;
52 id
53 }
54
55 fn alloc_rel_id(&mut self) -> RelationshipId {
56 let id = self.next_rel_id;
57 self.next_rel_id += 1;
58 id
59 }
60
61 fn normalize_labels(labels: Vec<String>) -> Vec<String> {
62 let mut seen = BTreeSet::new();
63
64 labels
65 .into_iter()
66 .map(|s| s.trim().to_string())
67 .filter(|s| !s.is_empty())
68 .filter(|s| seen.insert(s.clone()))
69 .collect()
70 }
71
72 fn insert_node_label_index(&mut self, node_id: NodeId, label: &str) {
73 self.nodes_by_label
74 .entry(label.to_string())
75 .or_default()
76 .insert(node_id);
77 }
78
79 fn remove_node_label_index(&mut self, node_id: NodeId, label: &str) {
80 if let Some(ids) = self.nodes_by_label.get_mut(label) {
81 ids.remove(&node_id);
82 if ids.is_empty() {
83 self.nodes_by_label.remove(label);
84 }
85 }
86 }
87
88 fn insert_relationship_type_index(&mut self, rel_id: RelationshipId, rel_type: &str) {
89 self.relationships_by_type
90 .entry(rel_type.to_string())
91 .or_default()
92 .insert(rel_id);
93 }
94
95 fn remove_relationship_type_index(&mut self, rel_id: RelationshipId, rel_type: &str) {
96 if let Some(ids) = self.relationships_by_type.get_mut(rel_type) {
97 ids.remove(&rel_id);
98 if ids.is_empty() {
99 self.relationships_by_type.remove(rel_type);
100 }
101 }
102 }
103
104 fn attach_relationship(&mut self, rel: &RelationshipRecord) {
105 self.outgoing.entry(rel.src).or_default().insert(rel.id);
106 self.incoming.entry(rel.dst).or_default().insert(rel.id);
107 self.insert_relationship_type_index(rel.id, &rel.rel_type);
108 }
109
110 fn detach_relationship_indexes(&mut self, rel: &RelationshipRecord) {
111 if let Some(ids) = self.outgoing.get_mut(&rel.src) {
112 ids.remove(&rel.id);
113 if ids.is_empty() {
114 self.outgoing.remove(&rel.src);
115 }
116 }
117
118 if let Some(ids) = self.incoming.get_mut(&rel.dst) {
119 ids.remove(&rel.id);
120 if ids.is_empty() {
121 self.incoming.remove(&rel.dst);
122 }
123 }
124
125 self.remove_relationship_type_index(rel.id, &rel.rel_type);
126 }
127
128 fn relationship_ids_for_direction(
129 &self,
130 node_id: NodeId,
131 direction: Direction,
132 ) -> Vec<RelationshipId> {
133 match direction {
134 Direction::Left => self
135 .incoming
136 .get(&node_id)
137 .map(|ids| ids.iter().copied().collect())
138 .unwrap_or_default(),
139
140 Direction::Right => self
141 .outgoing
142 .get(&node_id)
143 .map(|ids| ids.iter().copied().collect())
144 .unwrap_or_default(),
145
146 Direction::Undirected => {
147 let mut ids = BTreeSet::new();
148
149 if let Some(out) = self.outgoing.get(&node_id) {
150 ids.extend(out.iter().copied());
151 }
152 if let Some(inc) = self.incoming.get(&node_id) {
153 ids.extend(inc.iter().copied());
154 }
155
156 ids.into_iter().collect()
157 }
158 }
159 }
160
161 fn other_endpoint(rel: &RelationshipRecord, node_id: NodeId) -> Option<NodeId> {
162 if rel.src == node_id {
163 Some(rel.dst)
164 } else if rel.dst == node_id {
165 Some(rel.src)
166 } else {
167 None
168 }
169 }
170
171 fn has_incident_relationships(&self, node_id: NodeId) -> bool {
172 self.outgoing
173 .get(&node_id)
174 .map(|ids| !ids.is_empty())
175 .unwrap_or(false)
176 || self
177 .incoming
178 .get(&node_id)
179 .map(|ids| !ids.is_empty())
180 .unwrap_or(false)
181 }
182
183 fn incident_relationship_ids(&self, node_id: NodeId) -> BTreeSet<RelationshipId> {
184 let mut rel_ids = BTreeSet::new();
185
186 if let Some(ids) = self.outgoing.get(&node_id) {
187 rel_ids.extend(ids.iter().copied());
188 }
189 if let Some(ids) = self.incoming.get(&node_id) {
190 rel_ids.extend(ids.iter().copied());
191 }
192
193 rel_ids
194 }
195}
196
197impl GraphStorage for InMemoryGraph {
198 fn all_nodes(&self) -> Vec<NodeRecord> {
199 self.nodes.values().cloned().collect()
200 }
201
202 fn nodes_by_label(&self, label: &str) -> Vec<NodeRecord> {
203 self.nodes_by_label
204 .get(label)
205 .into_iter()
206 .flat_map(|ids| ids.iter())
207 .filter_map(|id| self.nodes.get(id).cloned())
208 .collect()
209 }
210
211 fn node_ref(&self, id: NodeId) -> Option<&NodeRecord> {
212 self.nodes.get(&id)
213 }
214
215 fn all_node_ids(&self) -> Vec<NodeId> {
216 self.nodes.keys().copied().collect()
217 }
218
219 fn node_ids_by_label(&self, label: &str) -> Vec<NodeId> {
220 match self.nodes_by_label.get(label) {
221 Some(ids) => ids.iter().copied().collect(),
222 None => Vec::new(),
223 }
224 }
225
226 fn node_count(&self) -> usize {
227 self.nodes.len()
228 }
229
230 fn has_node(&self, id: NodeId) -> bool {
231 self.nodes.contains_key(&id)
232 }
233
234 fn all_relationships(&self) -> Vec<RelationshipRecord> {
235 self.relationships.values().cloned().collect()
236 }
237
238 fn relationships_by_type(&self, rel_type: &str) -> Vec<RelationshipRecord> {
239 self.relationships_by_type
240 .get(rel_type)
241 .into_iter()
242 .flat_map(|ids| ids.iter())
243 .filter_map(|id| self.relationships.get(id).cloned())
244 .collect()
245 }
246
247 fn relationship_ref(&self, id: RelationshipId) -> Option<&RelationshipRecord> {
248 self.relationships.get(&id)
249 }
250
251 fn all_rel_ids(&self) -> Vec<RelationshipId> {
252 self.relationships.keys().copied().collect()
253 }
254
255 fn rel_ids_by_type(&self, rel_type: &str) -> Vec<RelationshipId> {
256 match self.relationships_by_type.get(rel_type) {
257 Some(ids) => ids.iter().copied().collect(),
258 None => Vec::new(),
259 }
260 }
261
262 fn relationship_count(&self) -> usize {
263 self.relationships.len()
264 }
265
266 fn has_relationship(&self, id: RelationshipId) -> bool {
267 self.relationships.contains_key(&id)
268 }
269
270 fn all_labels(&self) -> Vec<String> {
271 self.nodes_by_label.keys().cloned().collect()
272 }
273
274 fn all_relationship_types(&self) -> Vec<String> {
275 self.relationships_by_type.keys().cloned().collect()
276 }
277
278 fn all_node_property_keys(&self) -> Vec<String> {
279 let mut keys = BTreeSet::new();
280 for node in self.nodes.values() {
281 for key in node.properties.keys() {
282 keys.insert(key.clone());
283 }
284 }
285 keys.into_iter().collect()
286 }
287
288 fn all_relationship_property_keys(&self) -> Vec<String> {
289 let mut keys = BTreeSet::new();
290 for rel in self.relationships.values() {
291 for key in rel.properties.keys() {
292 keys.insert(key.clone());
293 }
294 }
295 keys.into_iter().collect()
296 }
297
298 fn label_property_keys(&self, label: &str) -> Vec<String> {
299 let mut keys = BTreeSet::new();
300
301 if let Some(ids) = self.nodes_by_label.get(label) {
302 for id in ids {
303 if let Some(node) = self.nodes.get(id) {
304 for key in node.properties.keys() {
305 keys.insert(key.clone());
306 }
307 }
308 }
309 }
310
311 keys.into_iter().collect()
312 }
313
314 fn rel_type_property_keys(&self, rel_type: &str) -> Vec<String> {
315 let mut keys = BTreeSet::new();
316
317 if let Some(ids) = self.relationships_by_type.get(rel_type) {
318 for id in ids {
319 if let Some(rel) = self.relationships.get(id) {
320 for key in rel.properties.keys() {
321 keys.insert(key.clone());
322 }
323 }
324 }
325 }
326
327 keys.into_iter().collect()
328 }
329
330 fn node_has_label(&self, node_id: NodeId, label: &str) -> bool {
331 self.nodes
332 .get(&node_id)
333 .map(|n| n.labels.iter().any(|l| l == label))
334 .unwrap_or(false)
335 }
336
337 fn node_property(&self, node_id: NodeId, key: &str) -> Option<PropertyValue> {
338 self.nodes
339 .get(&node_id)
340 .and_then(|n| n.properties.get(key).cloned())
341 }
342
343 fn relationship_property(&self, rel_id: RelationshipId, key: &str) -> Option<PropertyValue> {
344 self.relationships
345 .get(&rel_id)
346 .and_then(|r| r.properties.get(key).cloned())
347 }
348
349 fn expand(
350 &self,
351 node_id: NodeId,
352 direction: Direction,
353 types: &[String],
354 ) -> Vec<(RelationshipRecord, NodeRecord)> {
355 if !self.nodes.contains_key(&node_id) {
356 return Vec::new();
357 }
358
359 let type_filter: Option<BTreeSet<&str>> = if types.is_empty() {
360 None
361 } else {
362 Some(types.iter().map(String::as_str).collect())
363 };
364
365 self.relationship_ids_for_direction(node_id, direction)
366 .into_iter()
367 .filter_map(|rel_id| self.relationships.get(&rel_id))
368 .filter(|rel| {
369 type_filter
370 .as_ref()
371 .map(|allowed| allowed.contains(rel.rel_type.as_str()))
372 .unwrap_or(true)
373 })
374 .filter_map(|rel| {
375 let other_id = Self::other_endpoint(rel, node_id)?;
376 let other = self.nodes.get(&other_id)?;
377 Some((rel.clone(), other.clone()))
378 })
379 .collect()
380 }
381
382 fn expand_ids(
383 &self,
384 node_id: NodeId,
385 direction: Direction,
386 types: &[String],
387 ) -> Vec<(RelationshipId, NodeId)> {
388 if !self.nodes.contains_key(&node_id) {
389 return Vec::new();
390 }
391
392 if types.is_empty() {
394 return self
395 .relationship_ids_for_direction(node_id, direction)
396 .into_iter()
397 .filter_map(|rel_id| {
398 let rel = self.relationships.get(&rel_id)?;
399 let other_id = Self::other_endpoint(rel, node_id)?;
400 Some((rel_id, other_id))
401 })
402 .collect();
403 }
404
405 self.relationship_ids_for_direction(node_id, direction)
409 .into_iter()
410 .filter_map(|rel_id| {
411 let rel = self.relationships.get(&rel_id)?;
412 if !types.iter().any(|t| t == &rel.rel_type) {
413 return None;
414 }
415 let other_id = Self::other_endpoint(rel, node_id)?;
416 Some((rel_id, other_id))
417 })
418 .collect()
419 }
420
421 fn outgoing_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord> {
422 self.outgoing
423 .get(&node_id)
424 .into_iter()
425 .flat_map(|ids| ids.iter())
426 .filter_map(|id| self.relationships.get(id).cloned())
427 .collect()
428 }
429
430 fn incoming_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord> {
431 self.incoming
432 .get(&node_id)
433 .into_iter()
434 .flat_map(|ids| ids.iter())
435 .filter_map(|id| self.relationships.get(id).cloned())
436 .collect()
437 }
438
439 fn relationship_ids_of(&self, node_id: NodeId, direction: Direction) -> Vec<RelationshipId> {
440 self.relationship_ids_for_direction(node_id, direction)
441 }
442
443 fn degree(&self, node_id: NodeId, direction: Direction) -> usize {
444 match direction {
445 Direction::Right => self.outgoing.get(&node_id).map(|s| s.len()).unwrap_or(0),
446 Direction::Left => self.incoming.get(&node_id).map(|s| s.len()).unwrap_or(0),
447 Direction::Undirected => {
448 self.outgoing.get(&node_id).map(|s| s.len()).unwrap_or(0)
449 + self.incoming.get(&node_id).map(|s| s.len()).unwrap_or(0)
450 }
451 }
452 }
453}
454
455impl GraphStorageMut for InMemoryGraph {
456 fn create_node(&mut self, labels: Vec<String>, properties: Properties) -> NodeRecord {
457 let id = self.alloc_node_id();
458 let labels = Self::normalize_labels(labels);
459
460 let node = NodeRecord {
461 id,
462 labels: labels.clone(),
463 properties,
464 };
465
466 self.nodes.insert(id, node.clone());
467
468 for label in &labels {
469 self.insert_node_label_index(id, label);
470 }
471
472 self.outgoing.entry(id).or_default();
473 self.incoming.entry(id).or_default();
474
475 node
476 }
477
478 fn create_relationship(
479 &mut self,
480 src: NodeId,
481 dst: NodeId,
482 rel_type: &str,
483 properties: Properties,
484 ) -> Option<RelationshipRecord> {
485 if !self.nodes.contains_key(&src) || !self.nodes.contains_key(&dst) {
486 return None;
487 }
488
489 let trimmed = rel_type.trim();
490 if trimmed.is_empty() {
491 return None;
492 }
493
494 let id = self.alloc_rel_id();
495 let rel = RelationshipRecord {
496 id,
497 src,
498 dst,
499 rel_type: trimmed.to_string(),
500 properties,
501 };
502
503 self.attach_relationship(&rel);
504 self.relationships.insert(id, rel.clone());
505
506 Some(rel)
507 }
508
509 fn set_node_property(&mut self, node_id: NodeId, key: String, value: PropertyValue) -> bool {
510 match self.nodes.get_mut(&node_id) {
511 Some(node) => {
512 node.properties.insert(key, value);
513 true
514 }
515 None => false,
516 }
517 }
518
519 fn remove_node_property(&mut self, node_id: NodeId, key: &str) -> bool {
520 match self.nodes.get_mut(&node_id) {
521 Some(node) => node.properties.remove(key).is_some(),
522 None => false,
523 }
524 }
525
526 fn add_node_label(&mut self, node_id: NodeId, label: &str) -> bool {
527 let label = label.trim();
528 if label.is_empty() {
529 return false;
530 }
531
532 match self.nodes.get_mut(&node_id) {
533 Some(node) => {
534 if node.labels.iter().any(|l| l == label) {
535 return false;
536 }
537
538 node.labels.push(label.to_string());
539 self.insert_node_label_index(node_id, label);
540 true
541 }
542 None => false,
543 }
544 }
545
546 fn remove_node_label(&mut self, node_id: NodeId, label: &str) -> bool {
547 match self.nodes.get_mut(&node_id) {
548 Some(node) => {
549 let original_len = node.labels.len();
550 node.labels.retain(|l| l != label);
551
552 if node.labels.len() != original_len {
553 self.remove_node_label_index(node_id, label);
554 true
555 } else {
556 false
557 }
558 }
559 None => false,
560 }
561 }
562
563 fn set_relationship_property(
564 &mut self,
565 rel_id: RelationshipId,
566 key: String,
567 value: PropertyValue,
568 ) -> bool {
569 match self.relationships.get_mut(&rel_id) {
570 Some(rel) => {
571 rel.properties.insert(key, value);
572 true
573 }
574 None => false,
575 }
576 }
577
578 fn remove_relationship_property(&mut self, rel_id: RelationshipId, key: &str) -> bool {
579 match self.relationships.get_mut(&rel_id) {
580 Some(rel) => rel.properties.remove(key).is_some(),
581 None => false,
582 }
583 }
584
585 fn delete_relationship(&mut self, rel_id: RelationshipId) -> bool {
586 match self.relationships.remove(&rel_id) {
587 Some(rel) => {
588 self.detach_relationship_indexes(&rel);
589 true
590 }
591 None => false,
592 }
593 }
594
595 fn delete_node(&mut self, node_id: NodeId) -> bool {
596 if !self.nodes.contains_key(&node_id) {
597 return false;
598 }
599
600 if self.has_incident_relationships(node_id) {
601 return false;
602 }
603
604 let node = match self.nodes.remove(&node_id) {
605 Some(node) => node,
606 None => return false,
607 };
608
609 for label in &node.labels {
610 self.remove_node_label_index(node_id, label);
611 }
612
613 self.outgoing.remove(&node_id);
614 self.incoming.remove(&node_id);
615
616 true
617 }
618
619 fn detach_delete_node(&mut self, node_id: NodeId) -> bool {
620 if !self.nodes.contains_key(&node_id) {
621 return false;
622 }
623
624 let rel_ids: Vec<_> = self
625 .incident_relationship_ids(node_id)
626 .into_iter()
627 .collect();
628
629 for rel_id in rel_ids {
630 let _ = self.delete_relationship(rel_id);
631 }
632
633 self.delete_node(node_id)
634 }
635}
636
637#[cfg(test)]
638mod tests {
639 use super::*;
640
641 fn props(pairs: &[(&str, PropertyValue)]) -> Properties {
642 pairs
643 .iter()
644 .map(|(k, v)| ((*k).to_string(), v.clone()))
645 .collect()
646 }
647
648 #[test]
649 fn create_and_lookup_nodes() {
650 let mut g = InMemoryGraph::new();
651
652 let a = g.create_node(
653 vec!["Person".into(), "Employee".into()],
654 props(&[("name", PropertyValue::String("Alice".into()))]),
655 );
656 let b = g.create_node(
657 vec!["Person".into()],
658 props(&[("name", PropertyValue::String("Bob".into()))]),
659 );
660
661 assert_eq!(a.id, 0);
662 assert_eq!(b.id, 1);
663
664 assert_eq!(g.all_nodes().len(), 2);
665 assert_eq!(g.nodes_by_label("Person").len(), 2);
666 assert_eq!(g.nodes_by_label("Employee").len(), 1);
667 assert!(g.node_has_label(a.id, "Person"));
668 assert_eq!(
669 g.node_property(a.id, "name"),
670 Some(PropertyValue::String("Alice".into()))
671 );
672 }
673
674 #[test]
675 fn create_and_expand_relationships() {
676 let mut g = InMemoryGraph::new();
677
678 let a = g.create_node(vec!["Person".into()], Properties::new());
679 let b = g.create_node(vec!["Person".into()], Properties::new());
680 let c = g.create_node(vec!["Company".into()], Properties::new());
681
682 let r1 = g
683 .create_relationship(a.id, b.id, "KNOWS", Properties::new())
684 .unwrap();
685 let r2 = g
686 .create_relationship(a.id, c.id, "WORKS_AT", Properties::new())
687 .unwrap();
688
689 assert_eq!(g.all_relationships().len(), 2);
690 assert_eq!(g.relationships_by_type("KNOWS").len(), 1);
691 assert_eq!(g.outgoing_relationships(a.id).len(), 2);
692 assert_eq!(g.incoming_relationships(b.id).len(), 1);
693
694 let knows = g.expand(a.id, Direction::Right, &[String::from("KNOWS")]);
695 assert_eq!(knows.len(), 1);
696 assert_eq!(knows[0].0.id, r1.id);
697 assert_eq!(knows[0].1.id, b.id);
698
699 let undirected = g.expand(a.id, Direction::Undirected, &[]);
700 assert_eq!(undirected.len(), 2);
701
702 assert_eq!(g.relationship(r2.id).unwrap().dst, c.id);
703 }
704
705 #[test]
706 fn incoming_and_outgoing_are_distinct() {
707 let mut g = InMemoryGraph::new();
708
709 let a = g.create_node(vec!["Person".into()], Properties::new());
710 let b = g.create_node(vec!["Person".into()], Properties::new());
711 let c = g.create_node(vec!["Person".into()], Properties::new());
712
713 g.create_relationship(a.id, b.id, "KNOWS", Properties::new())
714 .unwrap();
715 g.create_relationship(c.id, a.id, "LIKES", Properties::new())
716 .unwrap();
717
718 let outgoing = g.expand(a.id, Direction::Right, &[]);
719 let incoming = g.expand(a.id, Direction::Left, &[]);
720
721 assert_eq!(outgoing.len(), 1);
722 assert_eq!(incoming.len(), 1);
723 assert_eq!(outgoing[0].1.id, b.id);
724 assert_eq!(incoming[0].1.id, c.id);
725 }
726
727 #[test]
728 fn set_and_remove_properties() {
729 let mut g = InMemoryGraph::new();
730
731 let n = g.create_node(vec!["Person".into()], Properties::new());
732 assert!(g.set_node_property(n.id, "age".into(), PropertyValue::Int(42)));
733 assert_eq!(g.node_property(n.id, "age"), Some(PropertyValue::Int(42)));
734 assert!(g.remove_node_property(n.id, "age"));
735 assert_eq!(g.node_property(n.id, "age"), None);
736
737 let m = g.create_node(vec!["Person".into()], Properties::new());
738 let r = g
739 .create_relationship(n.id, m.id, "KNOWS", Properties::new())
740 .unwrap();
741
742 assert!(g.set_relationship_property(r.id, "since".into(), PropertyValue::Int(2020)));
743 assert_eq!(
744 g.relationship_property(r.id, "since"),
745 Some(PropertyValue::Int(2020))
746 );
747 assert!(g.remove_relationship_property(r.id, "since"));
748 assert_eq!(g.relationship_property(r.id, "since"), None);
749 }
750
751 #[test]
752 fn delete_requires_detach() {
753 let mut g = InMemoryGraph::new();
754
755 let a = g.create_node(vec!["Person".into()], Properties::new());
756 let b = g.create_node(vec!["Person".into()], Properties::new());
757 let r = g
758 .create_relationship(a.id, b.id, "KNOWS", Properties::new())
759 .unwrap();
760
761 assert!(!g.delete_node(a.id));
762 assert!(g.delete_relationship(r.id));
763 assert!(g.delete_node(a.id));
764 assert!(g.node(a.id).is_none());
765 }
766
767 #[test]
768 fn detach_delete_removes_incident_relationships() {
769 let mut g = InMemoryGraph::new();
770
771 let a = g.create_node(vec!["Person".into()], Properties::new());
772 let b = g.create_node(vec!["Person".into()], Properties::new());
773 let c = g.create_node(vec!["Person".into()], Properties::new());
774
775 let r1 = g
776 .create_relationship(a.id, b.id, "KNOWS", Properties::new())
777 .unwrap();
778 let r2 = g
779 .create_relationship(c.id, a.id, "LIKES", Properties::new())
780 .unwrap();
781
782 assert!(g.detach_delete_node(a.id));
783 assert!(g.node(a.id).is_none());
784 assert!(g.relationship(r1.id).is_none());
785 assert!(g.relationship(r2.id).is_none());
786 assert_eq!(g.all_relationships().len(), 0);
787 }
788
789 #[test]
790 fn duplicate_labels_are_normalized_on_create() {
791 let mut g = InMemoryGraph::new();
792
793 let n = g.create_node(
794 vec!["Person".into(), "Person".into(), "Admin".into()],
795 Properties::new(),
796 );
797
798 assert_eq!(n.labels, vec!["Person".to_string(), "Admin".to_string()]);
799 assert_eq!(g.nodes_by_label("Person").len(), 1);
800 assert_eq!(g.nodes_by_label("Admin").len(), 1);
801 }
802
803 #[test]
804 fn empty_labels_are_ignored() {
805 let mut g = InMemoryGraph::new();
806
807 let n = g.create_node(
808 vec!["Person".into(), "".into(), " ".into()],
809 Properties::new(),
810 );
811
812 assert_eq!(n.labels, vec!["Person".to_string()]);
813 }
814
815 #[test]
816 fn empty_relationship_type_is_rejected() {
817 let mut g = InMemoryGraph::new();
818
819 let a = g.create_node(vec!["A".into()], Properties::new());
820 let b = g.create_node(vec!["B".into()], Properties::new());
821
822 assert!(g
823 .create_relationship(a.id, b.id, "", Properties::new())
824 .is_none());
825 }
826
827 #[test]
828 fn storage_schema_helpers_work() {
829 let mut g = InMemoryGraph::new();
830
831 let a = g.create_node(
832 vec!["Person".into()],
833 props(&[("name", PropertyValue::String("Alice".into()))]),
834 );
835 let b = g.create_node(
836 vec!["Company".into()],
837 props(&[("title", PropertyValue::String("Acme".into()))]),
838 );
839
840 g.create_relationship(
841 a.id,
842 b.id,
843 "WORKS_AT",
844 props(&[("since", PropertyValue::Int(2020))]),
845 )
846 .unwrap();
847
848 assert!(g.has_label_name("Person"));
849 assert!(g.has_relationship_type_name("WORKS_AT"));
850 assert!(g.has_property_key("name"));
851 assert!(g.has_property_key("since"));
852 assert!(g.label_has_property_key("Person", "name"));
853 assert!(g.rel_type_has_property_key("WORKS_AT", "since"));
854 }
855}