1use std::collections::{BTreeSet, HashMap};
2use std::path::Path;
3
4use crate::ids::{EdgeLabel, NodeId};
5use crate::manifest::{Manifest, SegmentMeta};
6use crate::memtable::Memtable;
7use crate::segment::Segment;
8use crate::store::LocalStore;
9use crate::{Error, Result};
10
11pub struct Graph {
18 memtable: Memtable,
19 segments: Vec<Segment>,
20 store: Option<LocalStore>,
21 key_index: HashMap<String, NodeId>,
24 pending_keys: Vec<(String, NodeId)>,
27 labels: Vec<String>,
28 next_id: u64,
29 generation: u64,
30 committed_edges: u64,
31}
32
33impl Graph {
34 pub fn new() -> Self {
36 Self {
37 memtable: Memtable::new(),
38 segments: Vec::new(),
39 store: None,
40 key_index: HashMap::new(),
41 pending_keys: Vec::new(),
42 labels: Vec::new(),
43 next_id: 0,
44 generation: 0,
45 committed_edges: 0,
46 }
47 }
48
49 pub fn open(dir: impl AsRef<Path>) -> Result<Self> {
51 let store = LocalStore::open(dir)?;
52 let manifest = store.load_manifest()?.unwrap_or_else(Manifest::empty);
53
54 let mut segments = Vec::with_capacity(manifest.segments.len());
55 let mut key_index = HashMap::new();
56 for meta in &manifest.segments {
57 let segment = Segment::decode(&store.read_segment(&meta.name)?)?;
58 for (key, node) in segment.keys() {
59 key_index.insert(key.to_owned(), node);
60 }
61 segments.push(segment);
62 }
63
64 Ok(Self {
65 memtable: Memtable::new(),
66 segments,
67 store: Some(store),
68 key_index,
69 pending_keys: Vec::new(),
70 labels: manifest.labels,
71 next_id: manifest.next_node_id,
72 generation: manifest.generation,
73 committed_edges: manifest.edge_count,
74 })
75 }
76
77 pub fn upsert_node(&mut self, key: &str) -> NodeId {
79 if let Some(&id) = self.key_index.get(key) {
80 return id;
81 }
82 let id = NodeId(self.next_id);
83 self.next_id += 1;
84 self.key_index.insert(key.to_owned(), id);
85 self.pending_keys.push((key.to_owned(), id));
86 self.memtable.insert_node(id);
87 id
88 }
89
90 pub fn node(&self, key: &str) -> Option<NodeId> {
91 self.key_index.get(key).copied()
92 }
93
94 pub fn label(&mut self, name: &str) -> EdgeLabel {
97 if let Some(pos) = self.labels.iter().position(|l| l == name) {
98 return EdgeLabel(pos as u32);
99 }
100 self.labels.push(name.to_owned());
101 EdgeLabel((self.labels.len() - 1) as u32)
102 }
103
104 pub fn add_edge(&mut self, src: NodeId, label: EdgeLabel, dst: NodeId) -> Result<()> {
105 if self
108 .segments
109 .iter()
110 .any(|s| s.out(src, label).any(|n| n == dst))
111 {
112 return Ok(());
113 }
114 self.memtable.insert_edge(src, label, dst)
115 }
116
117 pub fn out(&self, src: NodeId, label: EdgeLabel) -> impl Iterator<Item = NodeId> {
120 let mut merged: BTreeSet<NodeId> = self.memtable.out(src, label).collect();
121 for segment in &self.segments {
122 merged.extend(segment.out(src, label));
123 }
124 merged.into_iter()
125 }
126
127 pub fn r#in(&self, dst: NodeId, label: EdgeLabel) -> impl Iterator<Item = NodeId> {
129 let mut merged: BTreeSet<NodeId> = self.memtable.r#in(dst, label).collect();
130 for segment in &self.segments {
131 merged.extend(segment.r#in(dst, label));
132 }
133 merged.into_iter()
134 }
135
136 pub fn flush(&mut self) -> Result<u64> {
140 let store = self.store.as_ref().ok_or(Error::NoStore)?;
141 if self.memtable.is_empty() && self.pending_keys.is_empty() {
142 return Ok(self.generation);
143 }
144
145 self.pending_keys.sort_by(|(a, _), (b, _)| a.cmp(b));
147 let bytes = Segment::encode(
148 self.memtable.iter_fwd(),
149 self.memtable.iter_rev(),
150 &self.pending_keys,
151 );
152
153 let generation = self.generation + 1;
154 let name = format!("seg-{generation:08}.seg");
155 store.write_segment(&name, &bytes)?;
158
159 let edges = self.memtable.edge_count() as u64;
160 let mut segments_meta: Vec<SegmentMeta> = self
161 .manifest_segments()
162 .unwrap_or_default();
163 segments_meta.push(SegmentMeta {
164 name: name.clone(),
165 level: 0,
166 edges,
167 bytes: bytes.len() as u64,
168 });
169
170 let manifest = Manifest {
171 format_version: crate::manifest::FORMAT_VERSION,
172 generation,
173 labels: self.labels.clone(),
174 next_node_id: self.next_id,
175 node_count: self.key_index.len() as u64,
176 edge_count: self.committed_edges + edges,
177 segments: segments_meta,
178 };
179 store.commit_manifest(&manifest)?;
180
181 self.segments.push(Segment::decode(&bytes)?);
182 self.memtable.clear();
183 self.pending_keys.clear();
184 self.generation = generation;
185 self.committed_edges += edges;
186 Ok(generation)
187 }
188
189 fn manifest_segments(&self) -> Option<Vec<SegmentMeta>> {
192 let store = self.store.as_ref()?;
193 store.load_manifest().ok().flatten().map(|m| m.segments)
194 }
195
196 pub fn generation(&self) -> u64 {
197 self.generation
198 }
199
200 pub fn node_count(&self) -> u64 {
201 self.key_index.len() as u64
202 }
203
204 pub fn edge_count(&self) -> u64 {
205 self.committed_edges + self.memtable.edge_count() as u64
206 }
207}
208
209impl Default for Graph {
210 fn default() -> Self {
211 Self::new()
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn upsert_is_idempotent() {
221 let mut g = Graph::new();
222 let a = g.upsert_node("alice");
223 assert_eq!(a, g.upsert_node("alice"));
224 assert_ne!(a, g.upsert_node("bob"));
225 }
226
227 #[test]
228 fn out_and_in_traversal() {
229 let mut g = Graph::new();
230 let follows = g.label("follows");
231 let a = g.upsert_node("alice");
232 let b = g.upsert_node("bob");
233 let c = g.upsert_node("carol");
234 g.add_edge(a, follows, b).unwrap();
235 g.add_edge(a, follows, c).unwrap();
236 g.add_edge(b, follows, c).unwrap();
237
238 assert_eq!(g.out(a, follows).collect::<Vec<_>>(), vec![b, c]);
239 assert_eq!(g.r#in(c, follows).collect::<Vec<_>>(), vec![a, b]);
240 }
241
242 #[test]
243 fn in_memory_graph_cannot_flush() {
244 let mut g = Graph::new();
245 assert!(matches!(g.flush(), Err(Error::NoStore)));
246 }
247
248 #[test]
249 fn flush_and_reopen_roundtrip() {
250 let dir = tempfile::tempdir().unwrap();
251
252 let (a, b, follows_idx) = {
253 let mut g = Graph::open(dir.path()).unwrap();
254 let follows = g.label("follows");
255 let a = g.upsert_node("alice");
256 let b = g.upsert_node("bob");
257 g.add_edge(a, follows, b).unwrap();
258 assert_eq!(g.flush().unwrap(), 1);
259 (a, b, follows)
260 };
261
262 let mut g = Graph::open(dir.path()).unwrap();
263 assert_eq!(g.generation(), 1);
264 assert_eq!(g.node_count(), 2);
265 assert_eq!(g.edge_count(), 1);
266 assert_eq!(g.node("alice"), Some(a));
268 assert_eq!(g.upsert_node("bob"), b);
269 assert_eq!(g.label("follows"), follows_idx);
271 assert_eq!(g.out(a, follows_idx).collect::<Vec<_>>(), vec![b]);
272 assert_eq!(g.r#in(b, follows_idx).collect::<Vec<_>>(), vec![a]);
273 }
274
275 #[test]
276 fn reads_merge_across_segments_and_memtable() {
277 let dir = tempfile::tempdir().unwrap();
278 let mut g = Graph::open(dir.path()).unwrap();
279 let follows = g.label("follows");
280 let a = g.upsert_node("alice");
281 let b = g.upsert_node("bob");
282 g.add_edge(a, follows, b).unwrap();
283 g.flush().unwrap();
284
285 let c = g.upsert_node("carol");
286 g.add_edge(a, follows, c).unwrap();
287 g.flush().unwrap();
288
289 let d = g.upsert_node("dave");
290 g.add_edge(a, follows, d).unwrap(); assert_eq!(g.out(a, follows).collect::<Vec<_>>(), vec![b, c, d]);
294 assert_eq!(g.generation(), 2);
295 assert_eq!(g.edge_count(), 3);
296 }
297
298 #[test]
299 fn flush_is_noop_when_clean() {
300 let dir = tempfile::tempdir().unwrap();
301 let mut g = Graph::open(dir.path()).unwrap();
302 let follows = g.label("follows");
303 let a = g.upsert_node("alice");
304 let b = g.upsert_node("bob");
305 g.add_edge(a, follows, b).unwrap();
306 assert_eq!(g.flush().unwrap(), 1);
307 assert_eq!(g.flush().unwrap(), 1); g.add_edge(a, follows, b).unwrap();
311 assert_eq!(g.edge_count(), 1);
312 assert_eq!(g.flush().unwrap(), 1);
313 }
314}