Skip to main content

uqa_graph/memory_store/
store.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! In-memory state management and persistence hydration helpers.
8
9use super::{
10    graphid_label_id, BTreeSet, Edge, EdgeId, GraphLabelInfo, GraphLabelRegistry, GraphStore as _,
11    GraphStoreError, GraphStoreResult, LabelKind, MemoryGraphStore, Partition, Vertex, VertexId,
12};
13
14impl MemoryGraphStore {
15    pub fn new() -> Self {
16        Self {
17            next_vertex_id: 1,
18            next_edge_id: 1,
19            ..Self::default()
20        }
21    }
22
23    #[cfg(test)]
24    pub(crate) fn remove_edge_record_for_corruption_test(&mut self, edge_id: EdgeId) {
25        self.edges.remove(&edge_id);
26    }
27
28    #[cfg(test)]
29    pub(super) fn remove_vertex_record_for_corruption_test(&mut self, vertex_id: VertexId) {
30        self.vertices.remove(&vertex_id);
31    }
32
33    pub(super) fn require_partition_mut(&mut self, name: &str) -> GraphStoreResult<&mut Partition> {
34        self.graphs
35            .get_mut(name)
36            .ok_or_else(|| GraphStoreError::UnknownGraph(name.to_string()))
37    }
38
39    pub(super) fn require_partition(&self, name: &str) -> GraphStoreResult<&Partition> {
40        self.graphs
41            .get(name)
42            .ok_or_else(|| GraphStoreError::UnknownGraph(name.to_string()))
43    }
44
45    pub(super) fn require_query_vertex(
46        &self,
47        partition: &Partition,
48        vertex_id: VertexId,
49        graph: &str,
50    ) -> GraphStoreResult<()> {
51        if !partition.vertex_ids.contains(&vertex_id) {
52            return Err(GraphStoreError::InvalidQuery(format!(
53                "vertex {vertex_id} is not a member of graph {graph:?}"
54            )));
55        }
56        self.require_partition_vertex(partition, vertex_id, graph)
57            .map(|_| ())
58    }
59
60    pub(super) fn require_partition_vertex<'a>(
61        &'a self,
62        partition: &Partition,
63        vertex_id: VertexId,
64        graph: &str,
65    ) -> GraphStoreResult<&'a Vertex> {
66        if !partition.vertex_ids.contains(&vertex_id) {
67            return Err(GraphStoreError::CorruptGraph(format!(
68                "graph {graph:?} references vertex {vertex_id} outside its membership set"
69            )));
70        }
71        self.vertices.get(&vertex_id).ok_or_else(|| {
72            GraphStoreError::CorruptGraph(format!(
73                "graph {graph:?} references missing vertex {vertex_id}"
74            ))
75        })
76    }
77
78    pub(super) fn require_partition_edge<'a>(
79        &'a self,
80        partition: &Partition,
81        edge_id: EdgeId,
82        graph: &str,
83    ) -> GraphStoreResult<&'a Edge> {
84        if !partition.edge_ids.contains(&edge_id) {
85            return Err(GraphStoreError::CorruptGraph(format!(
86                "graph {graph:?} adjacency references edge {edge_id} outside its membership set"
87            )));
88        }
89        let edge = self.edges.get(&edge_id).ok_or_else(|| {
90            GraphStoreError::CorruptGraph(format!(
91                "graph {graph:?} references missing edge {edge_id}"
92            ))
93        })?;
94        self.require_edge_endpoint(partition, edge.source_id, graph)?;
95        self.require_edge_endpoint(partition, edge.target_id, graph)?;
96        Ok(edge)
97    }
98
99    pub(super) fn require_edge_endpoint(
100        &self,
101        partition: &Partition,
102        vertex_id: VertexId,
103        graph: &str,
104    ) -> GraphStoreResult<()> {
105        if partition.vertex_ids.contains(&vertex_id) && self.vertices.contains_key(&vertex_id) {
106            return Ok(());
107        }
108        let label_id = graphid_label_id(vertex_id);
109        if self
110            .label_registry(graph)
111            .dropped_label_ids
112            .contains(&label_id)
113        {
114            return Ok(());
115        }
116        Err(GraphStoreError::CorruptGraph(format!(
117            "graph {graph:?} edge references missing vertex {vertex_id}"
118        )))
119    }
120
121    pub(super) fn ensure_partition(&mut self, name: &str) {
122        if !self.graphs.contains_key(name) {
123            self.graphs.insert(name.to_string(), Partition::default());
124        }
125    }
126
127    pub(super) fn release_vertex_if_orphan(&mut self, vertex_id: VertexId) {
128        let still_referenced = self
129            .vertex_membership
130            .get(&vertex_id)
131            .is_some_and(|set| !set.is_empty());
132        if !still_referenced {
133            self.vertices.remove(&vertex_id);
134            self.vertex_membership.remove(&vertex_id);
135        }
136    }
137
138    pub(super) fn release_edge_if_orphan(&mut self, edge_id: EdgeId) {
139        let still_referenced = self
140            .edge_membership
141            .get(&edge_id)
142            .is_some_and(|set| !set.is_empty());
143        if !still_referenced {
144            self.edges.remove(&edge_id);
145            self.edge_membership.remove(&edge_id);
146        }
147    }
148
149    pub(super) fn populate_graph_from_ids(
150        &mut self,
151        vertex_ids: &BTreeSet<VertexId>,
152        edge_ids: &BTreeSet<EdgeId>,
153        target: &str,
154    ) -> GraphStoreResult<()> {
155        let vertices = vertex_ids
156            .iter()
157            .map(|id| {
158                self.vertices.get(id).cloned().ok_or_else(|| {
159                    GraphStoreError::CorruptGraph(format!(
160                        "graph membership references missing vertex {id}"
161                    ))
162                })
163            })
164            .collect::<GraphStoreResult<Vec<_>>>()?;
165        let edges = edge_ids
166            .iter()
167            .map(|id| {
168                self.edges.get(id).cloned().ok_or_else(|| {
169                    GraphStoreError::CorruptGraph(format!(
170                        "graph membership references missing edge {id}"
171                    ))
172                })
173            })
174            .collect::<GraphStoreResult<Vec<_>>>()?;
175
176        self.ensure_partition(target);
177        for vertex in vertices {
178            let id = vertex.vertex_id;
179            self.require_partition_mut(target)?.add_vertex(&vertex);
180            self.vertex_membership
181                .entry(id)
182                .or_default()
183                .insert(target.to_string());
184        }
185        for edge in edges {
186            let id = edge.edge_id;
187            self.require_partition_mut(target)?.add_edge(&edge);
188            self.edge_membership
189                .entry(id)
190                .or_default()
191                .insert(target.to_string());
192        }
193        Ok(())
194    }
195
196    /// Insert a vertex into the global registry without attaching it
197    /// to any graph. Used by the `SQLite`-backed store on hydration
198    /// before membership is restored from the persisted catalog.
199    pub fn insert_raw_vertex(&mut self, vertex: Vertex) -> GraphStoreResult<()> {
200        let next = if vertex.vertex_id >= self.next_vertex_id {
201            vertex.vertex_id.checked_add(1).ok_or_else(|| {
202                GraphStoreError::IdExhausted("raw vertex id counter overflow".into())
203            })?
204        } else {
205            self.next_vertex_id
206        };
207        self.vertices.insert(vertex.vertex_id, vertex);
208        self.next_vertex_id = next;
209        Ok(())
210    }
211
212    /// Insert an edge before graph memberships are restored. Endpoint
213    /// validation is deferred until `attach_edge`, when the graph's persisted
214    /// label tombstones are available.
215    pub fn insert_raw_edge(&mut self, edge: Edge) -> GraphStoreResult<()> {
216        let next = if edge.edge_id >= self.next_edge_id {
217            edge.edge_id.checked_add(1).ok_or_else(|| {
218                GraphStoreError::IdExhausted("raw edge id counter overflow".into())
219            })?
220        } else {
221            self.next_edge_id
222        };
223        self.edges.insert(edge.edge_id, edge);
224        self.next_edge_id = next;
225        Ok(())
226    }
227
228    /// Attach a previously inserted vertex to the named graph. The
229    /// graph must already exist (`create_graph`).
230    pub fn attach_vertex(&mut self, vertex_id: VertexId, graph: &str) -> GraphStoreResult<()> {
231        let vertex = self.vertices.get(&vertex_id).cloned().ok_or_else(|| {
232            GraphStoreError::CorruptGraph(format!("cannot attach missing vertex {vertex_id}"))
233        })?;
234        let part = self.require_partition_mut(graph)?;
235        part.add_vertex(&vertex);
236        self.vertex_membership
237            .entry(vertex_id)
238            .or_default()
239            .insert(graph.to_string());
240        Ok(())
241    }
242
243    pub fn attach_edge(&mut self, edge_id: EdgeId, graph: &str) -> GraphStoreResult<()> {
244        // Snapshot the edge fields so we can populate the partition's
245        // adjacency indexes without re-borrowing `self.edges`.
246        let edge = self.edges.get(&edge_id).cloned().ok_or_else(|| {
247            GraphStoreError::CorruptGraph(format!("cannot attach missing edge {edge_id}"))
248        })?;
249        let partition = self.require_partition(graph)?;
250        self.require_edge_endpoint(partition, edge.source_id, graph)?;
251        self.require_edge_endpoint(partition, edge.target_id, graph)?;
252        let part = self.require_partition_mut(graph)?;
253        part.add_edge(&edge);
254        self.edge_membership
255            .entry(edge_id)
256            .or_default()
257            .insert(graph.to_string());
258        Ok(())
259    }
260
261    /// All edge ids that participate in `graph`, in stable id order.
262    /// Helper for the `SQLite`-backed store's bulk write paths.
263    pub fn out_edge_ids_for_graph(&self, graph: &str) -> GraphStoreResult<BTreeSet<EdgeId>> {
264        Ok(self.require_partition(graph)?.edge_ids.clone())
265    }
266
267    fn remove_vertex_preserving_incident_edges(
268        &mut self,
269        vertex_id: VertexId,
270        graph: &str,
271    ) -> GraphStoreResult<()> {
272        let partition = self.require_partition_mut(graph)?;
273        if !partition.vertex_ids.remove(&vertex_id) {
274            return Ok(());
275        }
276        for ids in partition.vertex_label_index.values_mut() {
277            ids.remove(&vertex_id);
278        }
279        if let Some(memberships) = self.vertex_membership.get_mut(&vertex_id) {
280            memberships.remove(graph);
281        }
282        self.release_vertex_if_orphan(vertex_id);
283        Ok(())
284    }
285
286    /// Snapshot of the AGE label registry for `graph` (empty registry
287    /// when the graph has never allocated an id).
288    pub fn label_registry(&self, graph: &str) -> GraphLabelRegistry {
289        self.label_registries
290            .get(graph)
291            .cloned()
292            .unwrap_or_default()
293    }
294
295    /// Install (merge) a persisted label registry for `graph`. Existing
296    /// in-memory state wins on conflicts except that the larger
297    /// sequence / label-id watermarks are kept.
298    pub fn import_label_registry(&mut self, graph: &str, registry: &GraphLabelRegistry) {
299        self.label_registries
300            .entry(graph.to_string())
301            .or_default()
302            .merge(registry);
303    }
304
305    /// Re-derive the label registry of `graph` from the ids of the
306    /// entities currently attached to it. Self-heals restored graphs
307    /// whose registry metadata is missing: `label_id = id >> 48`.
308    pub fn rebuild_label_registry_from_ids(&mut self, graph: &str) {
309        let mut observations: Vec<(String, u64, LabelKind)> = Vec::new();
310        if let Some(part) = self.graphs.get(graph) {
311            for vid in &part.vertex_ids {
312                if let Some(vertex) = self.vertices.get(vid) {
313                    observations.push((vertex.label.clone(), vertex.vertex_id, LabelKind::Vertex));
314                }
315            }
316            for eid in &part.edge_ids {
317                if let Some(edge) = self.edges.get(eid) {
318                    observations.push((edge.label.clone(), edge.edge_id, LabelKind::Edge));
319                }
320            }
321        }
322        let registry = self.label_registries.entry(graph.to_string()).or_default();
323        for (label, id, kind) in observations {
324            registry.observe(&label, id, kind);
325        }
326    }
327
328    /// Every present `ag_label` entry of `graph`, in label-id order.
329    pub fn graph_labels(&self, graph: &str) -> GraphStoreResult<Vec<GraphLabelInfo>> {
330        self.require_partition(graph)?;
331        Ok(self.label_registry(graph).labels())
332    }
333
334    /// The kind of `label` in `graph`, or `None` when the label is not
335    /// registered there.
336    pub fn graph_label_kind(
337        &self,
338        graph: &str,
339        label: &str,
340    ) -> GraphStoreResult<Option<LabelKind>> {
341        self.require_partition(graph)?;
342        Ok(self.label_registry(graph).label_kind(label))
343    }
344
345    /// Register an empty user label in `graph` (`create_vlabel` /
346    /// `create_elabel`). Returns the label id, or `None` when a label of
347    /// that name already exists in the graph. A missing default relation of
348    /// the requested kind prevents new child labels, as it does in AGE.
349    pub fn create_label(
350        &mut self,
351        graph: &str,
352        label: &str,
353        kind: LabelKind,
354    ) -> GraphStoreResult<Option<u32>> {
355        self.require_partition(graph)?;
356        let mut candidate = self.label_registry(graph);
357        let id = candidate.register_label(label, kind)?;
358        if id.is_some() {
359            self.label_registries.insert(graph.to_string(), candidate);
360        }
361        Ok(id)
362    }
363
364    /// Drop a label from `graph` together with every entity that carries it
365    /// (`drop_label`, which AGE implements as `DROP TABLE` on the label
366    /// relation). Vertex rows are removed while incident rows in edge-label
367    /// relations survive with dangling endpoint ids. A default label can be
368    /// dropped only when no user label of that kind inherits from it. Returns
369    /// the released label id and kind, or `None` when the label is not
370    /// registered.
371    pub fn drop_label(
372        &mut self,
373        graph: &str,
374        label: &str,
375    ) -> GraphStoreResult<Option<(u32, LabelKind)>> {
376        self.require_partition(graph)?;
377        let registry = self.label_registry(graph);
378        let Some(kind) = registry.label_kind(label) else {
379            return Ok(None);
380        };
381        let id = if label == kind.default_label_name() {
382            if let Some(dependent) = registry
383                .labels
384                .keys()
385                .find(|candidate| registry.label_kind(candidate) == Some(kind))
386            {
387                return Err(GraphStoreError::InvalidMutation(format!(
388                    "cannot drop default label {label} while label {dependent} depends on it"
389                )));
390            }
391            kind.default_label_id()
392        } else {
393            registry.labels.get(label).copied().ok_or_else(|| {
394                GraphStoreError::CorruptGraph(format!(
395                    "graph {graph:?} label {label:?} has no registry id"
396                ))
397            })?
398        };
399        match kind {
400            LabelKind::Vertex => {
401                let vertex_ids = if id == kind.default_label_id() {
402                    self.require_partition(graph)?
403                        .vertex_ids
404                        .iter()
405                        .copied()
406                        .filter(|vertex_id| graphid_label_id(*vertex_id) == id)
407                        .collect()
408                } else {
409                    self.vertex_ids_by_label(label, graph)?
410                };
411                for vertex_id in vertex_ids {
412                    self.remove_vertex_preserving_incident_edges(vertex_id, graph)?;
413                }
414            }
415            LabelKind::Edge => {
416                let edge_ids = if id == kind.default_label_id() {
417                    self.require_partition(graph)?
418                        .edge_ids
419                        .iter()
420                        .copied()
421                        .filter(|edge_id| graphid_label_id(*edge_id) == id)
422                        .collect()
423                } else {
424                    self.edge_ids_by_label(label, graph)?
425                };
426                for edge_id in edge_ids {
427                    self.remove_edge(edge_id, graph)?;
428                }
429            }
430        }
431        self.label_registries
432            .entry(graph.to_string())
433            .or_default()
434            .remove_label(label);
435        Ok(Some((id, kind)))
436    }
437
438    /// Rename graph `from` to `to`, carrying its partition, memberships,
439    /// and label registry over. Fails when `from` is missing or `to`
440    /// already exists.
441    pub fn rename_graph(&mut self, from: &str, to: &str) -> GraphStoreResult<()> {
442        if from == to {
443            return Ok(());
444        }
445        if self.graphs.contains_key(to) {
446            return Err(GraphStoreError::InvalidMutation(format!(
447                "graph {to:?} already exists"
448            )));
449        }
450        let partition = self
451            .graphs
452            .remove(from)
453            .ok_or_else(|| GraphStoreError::UnknownGraph(from.to_string()))?;
454        for vertex_id in &partition.vertex_ids {
455            if let Some(set) = self.vertex_membership.get_mut(vertex_id) {
456                if set.remove(from) {
457                    set.insert(to.to_string());
458                }
459            }
460        }
461        for edge_id in &partition.edge_ids {
462            if let Some(set) = self.edge_membership.get_mut(edge_id) {
463                if set.remove(from) {
464                    set.insert(to.to_string());
465                }
466            }
467        }
468        self.graphs.insert(to.to_string(), partition);
469        if let Some(registry) = self.label_registries.remove(from) {
470            self.label_registries.insert(to.to_string(), registry);
471        }
472        Ok(())
473    }
474}