Skip to main content

uqa_graph/memory_store/
trait_impl.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! `GraphStore` mutation, traversal, statistics, and id-allocation contract.
8
9use super::{
10    make_graphid, usize_to_f64_exact, BTreeMap, BTreeSet, Direction, Edge, EdgeId, GraphStore,
11    GraphStoreError, GraphStoreResult, LabelKind, MemoryGraphStore, Vertex, VertexId,
12};
13
14impl GraphStore for MemoryGraphStore {
15    fn create_graph(&mut self, name: &str) {
16        self.graphs.entry(name.to_string()).or_default();
17    }
18
19    fn drop_graph(&mut self, name: &str) {
20        // AGE drops every per-graph label table with the graph, so a
21        // re-created graph starts a fresh label / sequence space.
22        self.label_registries.remove(name);
23        let Some(partition) = self.graphs.remove(name) else {
24            return;
25        };
26        for vid in &partition.vertex_ids {
27            if let Some(set) = self.vertex_membership.get_mut(vid) {
28                set.remove(name);
29            }
30        }
31        for eid in &partition.edge_ids {
32            if let Some(set) = self.edge_membership.get_mut(eid) {
33                set.remove(name);
34            }
35        }
36        // Drop any vertex / edge that no longer has any membership.
37        let orphan_vertices: Vec<VertexId> = partition.vertex_ids.iter().copied().collect();
38        for vid in orphan_vertices {
39            self.release_vertex_if_orphan(vid);
40        }
41        let orphan_edges: Vec<EdgeId> = partition.edge_ids.iter().copied().collect();
42        for eid in orphan_edges {
43            self.release_edge_if_orphan(eid);
44        }
45    }
46
47    fn graph_names(&self) -> Vec<String> {
48        self.graphs.keys().cloned().collect()
49    }
50
51    fn has_graph(&self, name: &str) -> bool {
52        self.graphs.contains_key(name)
53    }
54
55    fn union_graphs(&mut self, g1: &str, g2: &str, target: &str) -> GraphStoreResult<()> {
56        let v_union: BTreeSet<VertexId> = self
57            .require_partition(g1)?
58            .vertex_ids
59            .union(&self.require_partition(g2)?.vertex_ids)
60            .copied()
61            .collect();
62        let e_union: BTreeSet<EdgeId> = self
63            .require_partition(g1)?
64            .edge_ids
65            .union(&self.require_partition(g2)?.edge_ids)
66            .copied()
67            .collect();
68        self.populate_graph_from_ids(&v_union, &e_union, target)
69    }
70
71    fn intersect_graphs(&mut self, g1: &str, g2: &str, target: &str) -> GraphStoreResult<()> {
72        let v_inter: BTreeSet<VertexId> = self
73            .require_partition(g1)?
74            .vertex_ids
75            .intersection(&self.require_partition(g2)?.vertex_ids)
76            .copied()
77            .collect();
78        let e_inter: BTreeSet<EdgeId> = self
79            .require_partition(g1)?
80            .edge_ids
81            .intersection(&self.require_partition(g2)?.edge_ids)
82            .copied()
83            .collect();
84        self.populate_graph_from_ids(&v_inter, &e_inter, target)
85    }
86
87    fn difference_graphs(&mut self, g1: &str, g2: &str, target: &str) -> GraphStoreResult<()> {
88        let v_diff: BTreeSet<VertexId> = self
89            .require_partition(g1)?
90            .vertex_ids
91            .difference(&self.require_partition(g2)?.vertex_ids)
92            .copied()
93            .collect();
94        let e_diff: BTreeSet<EdgeId> = self
95            .require_partition(g1)?
96            .edge_ids
97            .difference(&self.require_partition(g2)?.edge_ids)
98            .copied()
99            .collect();
100        self.populate_graph_from_ids(&v_diff, &e_diff, target)
101    }
102
103    fn copy_graph(&mut self, source: &str, target: &str) -> GraphStoreResult<()> {
104        let v_copy = self.require_partition(source)?.vertex_ids.clone();
105        let e_copy = self.require_partition(source)?.edge_ids.clone();
106        self.populate_graph_from_ids(&v_copy, &e_copy, target)
107    }
108
109    fn add_vertex(&mut self, vertex: Vertex, graph: &str) -> GraphStoreResult<()> {
110        self.require_partition(graph)?;
111        let vid = vertex.vertex_id;
112        let next_vertex_id = if vid >= self.next_vertex_id {
113            vid.checked_add(1).ok_or_else(|| {
114                GraphStoreError::IdExhausted("vertex id counter overflow".to_string())
115            })?
116        } else {
117            self.next_vertex_id
118        };
119        self.vertices.insert(vid, vertex.clone());
120        self.require_partition_mut(graph)?.add_vertex(&vertex);
121        self.vertex_membership
122            .entry(vid)
123            .or_default()
124            .insert(graph.to_string());
125        self.next_vertex_id = next_vertex_id;
126        Ok(())
127    }
128
129    fn add_edge(&mut self, edge: Edge, graph: &str) -> GraphStoreResult<()> {
130        let partition = self.require_partition(graph)?;
131        if !partition.vertex_ids.contains(&edge.source_id)
132            || !partition.vertex_ids.contains(&edge.target_id)
133        {
134            return Err(GraphStoreError::InvalidMutation(format!(
135                "edge {} references endpoint outside graph {graph:?}: {} -> {}",
136                edge.edge_id, edge.source_id, edge.target_id
137            )));
138        }
139        self.require_partition_vertex(partition, edge.source_id, graph)?;
140        self.require_partition_vertex(partition, edge.target_id, graph)?;
141        let eid = edge.edge_id;
142        let next_edge_id = if eid >= self.next_edge_id {
143            eid.checked_add(1).ok_or_else(|| {
144                GraphStoreError::IdExhausted("edge id counter overflow".to_string())
145            })?
146        } else {
147            self.next_edge_id
148        };
149        self.edges.insert(eid, edge.clone());
150        self.require_partition_mut(graph)?.add_edge(&edge);
151        self.edge_membership
152            .entry(eid)
153            .or_default()
154            .insert(graph.to_string());
155        self.next_edge_id = next_edge_id;
156        Ok(())
157    }
158
159    fn remove_vertex(&mut self, vertex_id: VertexId, graph: &str) -> GraphStoreResult<()> {
160        let partition = self.require_partition_mut(graph)?;
161        if !partition.vertex_ids.remove(&vertex_id) {
162            return Ok(());
163        }
164        for vids in partition.vertex_label_index.values_mut() {
165            vids.remove(&vertex_id);
166        }
167        // Remove all incident edges from this graph.
168        let out_edges: Vec<EdgeId> = partition
169            .adj_out
170            .remove(&vertex_id)
171            .map(|s| s.into_iter().collect())
172            .unwrap_or_default();
173        let in_edges: Vec<EdgeId> = partition
174            .adj_in
175            .remove(&vertex_id)
176            .map(|s| s.into_iter().collect())
177            .unwrap_or_default();
178        let edge_ids_to_drop: Vec<EdgeId> = out_edges.into_iter().chain(in_edges).collect();
179        for eid in &edge_ids_to_drop {
180            if let Some(edge) = self.edges.get(eid).cloned() {
181                if let Some(part) = self.graphs.get_mut(graph) {
182                    part.remove_edge(&edge);
183                }
184                if let Some(set) = self.edge_membership.get_mut(eid) {
185                    set.remove(graph);
186                }
187            }
188        }
189        if let Some(set) = self.vertex_membership.get_mut(&vertex_id) {
190            set.remove(graph);
191        }
192        for eid in edge_ids_to_drop {
193            self.release_edge_if_orphan(eid);
194        }
195        self.release_vertex_if_orphan(vertex_id);
196        Ok(())
197    }
198
199    fn remove_edge(&mut self, edge_id: EdgeId, graph: &str) -> GraphStoreResult<()> {
200        self.require_partition(graph)?;
201        let Some(edge) = self.edges.get(&edge_id).cloned() else {
202            return Ok(());
203        };
204        let partition = self.require_partition_mut(graph)?;
205        if !partition.edge_ids.contains(&edge_id) {
206            return Ok(());
207        }
208        partition.remove_edge(&edge);
209        if let Some(set) = self.edge_membership.get_mut(&edge_id) {
210            set.remove(graph);
211        }
212        self.release_edge_if_orphan(edge_id);
213        Ok(())
214    }
215
216    fn neighbors(
217        &self,
218        vertex_id: VertexId,
219        label: Option<&str>,
220        direction: Direction,
221        graph: &str,
222    ) -> GraphStoreResult<Vec<VertexId>> {
223        let partition = self.require_partition(graph)?;
224        self.require_query_vertex(partition, vertex_id, graph)?;
225        let mut result = Vec::new();
226        let mut collect = |set: &BTreeSet<EdgeId>, take_target: bool| -> GraphStoreResult<()> {
227            for eid in set {
228                let edge = self.require_partition_edge(partition, *eid, graph)?;
229                let expected_endpoint = if take_target {
230                    edge.source_id
231                } else {
232                    edge.target_id
233                };
234                if expected_endpoint != vertex_id {
235                    return Err(GraphStoreError::CorruptGraph(format!(
236                        "graph {graph:?} adjacency for vertex {vertex_id} references edge {eid} with endpoints {} -> {}",
237                        edge.source_id, edge.target_id
238                    )));
239                }
240                if let Some(want) = label {
241                    if edge.label != want {
242                        continue;
243                    }
244                }
245                result.push(if take_target {
246                    edge.target_id
247                } else {
248                    edge.source_id
249                });
250            }
251            Ok(())
252        };
253        match direction {
254            Direction::Out => {
255                if let Some(set) = partition.adj_out.get(&vertex_id) {
256                    collect(set, true)?;
257                }
258            }
259            Direction::In => {
260                if let Some(set) = partition.adj_in.get(&vertex_id) {
261                    collect(set, false)?;
262                }
263            }
264            Direction::Both => {
265                if let Some(set) = partition.adj_out.get(&vertex_id) {
266                    collect(set, true)?;
267                }
268                if let Some(set) = partition.adj_in.get(&vertex_id) {
269                    collect(set, false)?;
270                }
271                result.sort_unstable();
272                result.dedup();
273            }
274        }
275        Ok(result)
276    }
277
278    fn vertices_by_label(&self, label: &str, graph: &str) -> GraphStoreResult<Vec<Vertex>> {
279        let partition = self.require_partition(graph)?;
280        partition
281            .vertex_label_index
282            .get(label)
283            .into_iter()
284            .flat_map(|set| set.iter())
285            .map(|vertex_id| {
286                self.require_partition_vertex(partition, *vertex_id, graph)
287                    .cloned()
288            })
289            .collect()
290    }
291
292    fn vertex_ids_by_label(&self, label: &str, graph: &str) -> GraphStoreResult<Vec<VertexId>> {
293        let partition = self.require_partition(graph)?;
294        partition
295            .vertex_label_index
296            .get(label)
297            .into_iter()
298            .flat_map(|set| set.iter())
299            .map(|vertex_id| {
300                self.require_partition_vertex(partition, *vertex_id, graph)?;
301                Ok(*vertex_id)
302            })
303            .collect()
304    }
305
306    fn vertices_in_graph(&self, graph: &str) -> GraphStoreResult<Vec<Vertex>> {
307        let partition = self.require_partition(graph)?;
308        partition
309            .vertex_ids
310            .iter()
311            .map(|vertex_id| {
312                self.require_partition_vertex(partition, *vertex_id, graph)
313                    .cloned()
314            })
315            .collect()
316    }
317
318    fn edges_in_graph(&self, graph: &str) -> GraphStoreResult<Vec<Edge>> {
319        let partition = self.require_partition(graph)?;
320        partition
321            .edge_ids
322            .iter()
323            .map(|edge_id| {
324                self.require_partition_edge(partition, *edge_id, graph)
325                    .cloned()
326            })
327            .collect()
328    }
329
330    fn vertex_graphs(&self, vertex_id: VertexId) -> BTreeSet<String> {
331        self.vertex_membership
332            .get(&vertex_id)
333            .cloned()
334            .unwrap_or_default()
335    }
336
337    fn out_edge_ids(&self, vertex_id: VertexId, graph: &str) -> GraphStoreResult<BTreeSet<EdgeId>> {
338        let partition = self.require_partition(graph)?;
339        self.require_query_vertex(partition, vertex_id, graph)?;
340        let edges = partition
341            .adj_out
342            .get(&vertex_id)
343            .cloned()
344            .unwrap_or_default();
345        for edge_id in &edges {
346            let edge = self.require_partition_edge(partition, *edge_id, graph)?;
347            if edge.source_id != vertex_id {
348                return Err(GraphStoreError::CorruptGraph(format!(
349                    "graph {graph:?} outgoing adjacency for vertex {vertex_id} references edge {edge_id} sourced at {}",
350                    edge.source_id
351                )));
352            }
353        }
354        Ok(edges)
355    }
356
357    fn in_edge_ids(&self, vertex_id: VertexId, graph: &str) -> GraphStoreResult<BTreeSet<EdgeId>> {
358        let partition = self.require_partition(graph)?;
359        self.require_query_vertex(partition, vertex_id, graph)?;
360        let edges = partition
361            .adj_in
362            .get(&vertex_id)
363            .cloned()
364            .unwrap_or_default();
365        for edge_id in &edges {
366            let edge = self.require_partition_edge(partition, *edge_id, graph)?;
367            if edge.target_id != vertex_id {
368                return Err(GraphStoreError::CorruptGraph(format!(
369                    "graph {graph:?} incoming adjacency for vertex {vertex_id} references edge {edge_id} targeted at {}",
370                    edge.target_id
371                )));
372            }
373        }
374        Ok(edges)
375    }
376
377    fn edge_ids_by_label(&self, label: &str, graph: &str) -> GraphStoreResult<BTreeSet<EdgeId>> {
378        let partition = self.require_partition(graph)?;
379        let edges = partition
380            .label_index
381            .get(label)
382            .cloned()
383            .unwrap_or_default();
384        for edge_id in &edges {
385            let edge = self.require_partition_edge(partition, *edge_id, graph)?;
386            if edge.label != label {
387                return Err(GraphStoreError::CorruptGraph(format!(
388                    "graph {graph:?} label index {label:?} references edge {edge_id} labelled {:?}",
389                    edge.label
390                )));
391            }
392        }
393        Ok(edges)
394    }
395
396    fn vertex_ids_in_graph(&self, graph: &str) -> GraphStoreResult<BTreeSet<VertexId>> {
397        let partition = self.require_partition(graph)?;
398        for vertex_id in &partition.vertex_ids {
399            self.require_partition_vertex(partition, *vertex_id, graph)?;
400        }
401        Ok(partition.vertex_ids.clone())
402    }
403
404    fn require_vertex_in_graph(&self, vertex_id: VertexId, graph: &str) -> GraphStoreResult<()> {
405        let partition = self.require_partition(graph)?;
406        self.require_query_vertex(partition, vertex_id, graph)
407    }
408
409    fn degree_distribution(&self, graph: &str) -> GraphStoreResult<BTreeMap<VertexId, u64>> {
410        let partition = self.require_partition(graph)?;
411        let mut out = BTreeMap::new();
412        for vid in &partition.vertex_ids {
413            self.require_partition_vertex(partition, *vid, graph)?;
414            let edge_ids = partition.adj_out.get(vid);
415            if let Some(edge_ids) = edge_ids {
416                for edge_id in edge_ids {
417                    let edge = self.require_partition_edge(partition, *edge_id, graph)?;
418                    if edge.source_id != *vid {
419                        return Err(GraphStoreError::CorruptGraph(format!(
420                            "graph {graph:?} outgoing adjacency for vertex {vid} references edge {edge_id} sourced at {}",
421                            edge.source_id
422                        )));
423                    }
424                }
425            }
426            let degree = u64::try_from(edge_ids.map_or(0, BTreeSet::len)).map_err(|_| {
427                GraphStoreError::CorruptGraph(format!("out-degree for vertex {vid} exceeds u64"))
428            })?;
429            out.insert(*vid, degree);
430        }
431        Ok(out)
432    }
433
434    fn label_degree(&self, label: &str, graph: &str) -> GraphStoreResult<f64> {
435        let partition = self.require_partition(graph)?;
436        let Some(eids) = partition.label_index.get(label) else {
437            return Ok(0.0);
438        };
439        if eids.is_empty() {
440            return Ok(0.0);
441        }
442        let mut sources: BTreeSet<VertexId> = BTreeSet::new();
443        for eid in eids {
444            let edge = self.require_partition_edge(partition, *eid, graph)?;
445            if edge.label != label {
446                return Err(GraphStoreError::CorruptGraph(format!(
447                    "graph {graph:?} label index {label:?} references edge {eid} labelled {:?}",
448                    edge.label
449                )));
450            }
451            sources.insert(edge.source_id);
452        }
453        if sources.is_empty() {
454            Ok(0.0)
455        } else {
456            Ok(usize_to_f64_exact(eids.len(), "edge label count")?
457                / usize_to_f64_exact(sources.len(), "edge label source count")?)
458        }
459    }
460
461    fn vertex_label_counts(&self, graph: &str) -> GraphStoreResult<BTreeMap<String, u64>> {
462        let partition = self.require_partition(graph)?;
463        let mut out = BTreeMap::new();
464        for (label, vids) in &partition.vertex_label_index {
465            for vertex_id in vids {
466                let vertex = self.require_partition_vertex(partition, *vertex_id, graph)?;
467                if vertex.label != *label {
468                    return Err(GraphStoreError::CorruptGraph(format!(
469                        "graph {graph:?} vertex label index {label:?} references vertex {vertex_id} labelled {:?}",
470                        vertex.label
471                    )));
472                }
473            }
474            let count = u64::try_from(vids.len()).map_err(|_| {
475                GraphStoreError::CorruptGraph(format!(
476                    "vertex count for label {label:?} exceeds u64"
477                ))
478            })?;
479            if count > 0 {
480                out.insert(label.clone(), count);
481            }
482        }
483        Ok(out)
484    }
485
486    fn get_vertex(&self, vertex_id: VertexId) -> Option<&Vertex> {
487        self.vertices.get(&vertex_id)
488    }
489
490    fn get_edge(&self, edge_id: EdgeId) -> Option<&Edge> {
491        self.edges.get(&edge_id)
492    }
493
494    fn next_vertex_id(&mut self) -> GraphStoreResult<VertexId> {
495        let id = self.next_vertex_id;
496        self.next_vertex_id = id.checked_add(1).ok_or_else(|| {
497            GraphStoreError::IdExhausted("vertex id counter overflow".to_string())
498        })?;
499        Ok(id)
500    }
501
502    fn next_edge_id(&mut self) -> GraphStoreResult<EdgeId> {
503        let id = self.next_edge_id;
504        self.next_edge_id = id
505            .checked_add(1)
506            .ok_or_else(|| GraphStoreError::IdExhausted("edge id counter overflow".to_string()))?;
507        Ok(id)
508    }
509
510    fn allocate_vertex_id(&mut self, label: &str, graph: &str) -> GraphStoreResult<VertexId> {
511        if !self.has_graph(graph) {
512            return Err(GraphStoreError::UnknownGraph(graph.to_string()));
513        }
514        let mut candidate = self
515            .label_registries
516            .get(graph)
517            .cloned()
518            .unwrap_or_default();
519        let label_id = candidate.label_id(label, LabelKind::Vertex)?;
520        let id = make_graphid(label_id, candidate.next_sequence(label_id)?)?;
521        self.label_registries.insert(graph.to_string(), candidate);
522        Ok(id)
523    }
524
525    fn allocate_edge_id(&mut self, label: &str, graph: &str) -> GraphStoreResult<EdgeId> {
526        if !self.has_graph(graph) {
527            return Err(GraphStoreError::UnknownGraph(graph.to_string()));
528        }
529        let mut candidate = self
530            .label_registries
531            .get(graph)
532            .cloned()
533            .unwrap_or_default();
534        let label_id = candidate.label_id(label, LabelKind::Edge)?;
535        let id = make_graphid(label_id, candidate.next_sequence(label_id)?)?;
536        self.label_registries.insert(graph.to_string(), candidate);
537        Ok(id)
538    }
539
540    fn clear(&mut self) {
541        self.vertices.clear();
542        self.edges.clear();
543        self.graphs.clear();
544        self.vertex_membership.clear();
545        self.edge_membership.clear();
546        self.label_registries.clear();
547        self.next_vertex_id = 1;
548        self.next_edge_id = 1;
549    }
550
551    fn vertices(&self) -> BTreeMap<VertexId, Vertex> {
552        self.vertices.clone()
553    }
554
555    fn edges(&self) -> BTreeMap<EdgeId, Edge> {
556        self.edges.clone()
557    }
558}