selene_graph/
adjacency.rs1use smallvec::SmallVec;
4
5use selene_core::{DbString, EdgeId, NodeId};
6
7#[derive(Clone, Debug, Eq, Hash, PartialEq)]
9pub struct AdjacencyEdge {
10 pub label: DbString,
12 pub neighbor: NodeId,
14 pub edge_id: EdgeId,
16}
17
18#[derive(Clone, Debug, Eq, Hash, PartialEq)]
20pub struct AdjacencyEntry {
21 pub edges: SmallVec<[AdjacencyEdge; 4]>,
23}
24
25impl AdjacencyEntry {
26 #[must_use]
28 pub fn new() -> Self {
29 Self {
30 edges: SmallVec::new(),
31 }
32 }
33
34 #[must_use]
36 pub fn len(&self) -> usize {
37 self.edges.len()
38 }
39
40 #[must_use]
42 pub fn is_empty(&self) -> bool {
43 self.edges.is_empty()
44 }
45
46 pub fn iter(&self) -> impl Iterator<Item = &AdjacencyEdge> {
48 self.edges.iter()
49 }
50
51 pub fn iter_label<'a>(&'a self, label: &DbString) -> impl Iterator<Item = &'a AdjacencyEdge> {
56 let range = self.label_range(label);
57 self.edges[range].iter()
58 }
59
60 pub fn add(&mut self, edge: AdjacencyEdge) {
62 let key = adjacency_key(&edge);
63 let index = self
64 .edges
65 .binary_search_by_key(&key, adjacency_key)
66 .unwrap_or_else(|index| index);
67 self.edges.insert(index, edge);
68 }
69
70 pub fn remove(&mut self, edge_id: EdgeId) -> Option<AdjacencyEdge> {
72 self.edges
73 .iter()
74 .position(|edge| edge.edge_id == edge_id)
75 .map(|index| self.edges.remove(index))
76 }
77
78 fn label_range(&self, label: &DbString) -> std::ops::Range<usize> {
79 let start = self
80 .edges
81 .partition_point(|edge| edge.label.as_str() < label.as_str());
82 let len = self.edges[start..].partition_point(|edge| edge.label.as_str() == label.as_str());
83 start..start + len
84 }
85
86 #[cfg(test)]
87 fn spilled(&self) -> bool {
88 self.edges.spilled()
89 }
90}
91
92impl Default for AdjacencyEntry {
93 fn default() -> Self {
94 Self::new()
95 }
96}
97
98fn adjacency_key(edge: &AdjacencyEdge) -> (DbString, NodeId, EdgeId) {
99 (edge.label.clone(), edge.neighbor, edge.edge_id)
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use selene_core::db_string;
106
107 fn label(name: &str) -> DbString {
108 db_string(name).unwrap()
109 }
110
111 fn edge(label_name: &str, neighbor: u64, edge_id: u64) -> AdjacencyEdge {
112 AdjacencyEdge {
113 label: label(label_name),
114 neighbor: NodeId::new(neighbor),
115 edge_id: EdgeId::new(edge_id),
116 }
117 }
118
119 #[test]
120 fn add_inserts_in_sorted_order() {
121 let mut entry = AdjacencyEntry::new();
122 let a = edge("adj.a", 2, 2);
123 let b = edge("adj.a", 2, 1);
124 let c = edge("adj.b", 1, 3);
125 entry.add(c.clone());
126 entry.add(a.clone());
127 entry.add(b.clone());
128 assert_eq!(entry.iter().cloned().collect::<Vec<_>>(), vec![b, a, c]);
129 }
130
131 #[test]
132 fn add_handles_inline_path_under_4_edges() {
133 let mut entry = AdjacencyEntry::new();
134 for id in 1..=4 {
135 entry.add(edge("adj.inline", id, id));
136 }
137 assert_eq!(entry.len(), 4);
138 assert!(!entry.spilled());
139 }
140
141 #[test]
142 fn add_spills_after_4_edges() {
143 let mut entry = AdjacencyEntry::new();
144 for id in 1..=5 {
145 entry.add(edge("adj.spill", id, id));
146 }
147 assert!(entry.spilled());
148 }
149
150 #[test]
151 fn remove_returns_removed_edge() {
152 let mut entry = AdjacencyEntry::new();
153 let e = edge("adj.remove", 2, 1);
154 entry.add(e.clone());
155 assert_eq!(entry.remove(EdgeId::new(1)), Some(e));
156 assert!(entry.is_empty());
157 }
158
159 #[test]
160 fn remove_returns_none_when_absent() {
161 let mut entry = AdjacencyEntry::new();
162 assert_eq!(entry.remove(EdgeId::new(1)), None);
163 }
164
165 #[test]
166 fn iter_label_returns_only_matching_label_range() {
167 let mut entry = AdjacencyEntry::new();
168 let a1 = edge("adj.a", 3, 1);
169 let a2 = edge("adj.a", 4, 2);
170 let b = edge("adj.b", 5, 3);
171 let c = edge("adj.c", 6, 4);
172 entry.add(c);
173 entry.add(a2.clone());
174 entry.add(b);
175 entry.add(a1.clone());
176
177 assert_eq!(
178 entry
179 .iter_label(&label("adj.a"))
180 .cloned()
181 .collect::<Vec<_>>(),
182 vec![a1, a2],
183 );
184 }
185
186 #[test]
187 fn iter_label_returns_empty_for_absent_labels() {
188 let mut entry = AdjacencyEntry::new();
189 entry.add(edge("adj.a", 2, 1));
190 entry.add(edge("adj.c", 3, 2));
191
192 assert_eq!(entry.iter_label(&label("adj.b")).count(), 0);
193 }
194
195 #[test]
196 fn parallel_edges_sort_by_edge_id() {
197 let mut entry = AdjacencyEntry::new();
198 let first = edge("adj.parallel", 2, 1);
199 let second = edge("adj.parallel", 2, 2);
200 entry.add(second.clone());
201 entry.add(first.clone());
202 assert_eq!(
203 entry.iter().cloned().collect::<Vec<_>>(),
204 vec![first, second]
205 );
206 }
207}