Skip to main content

lora_store/
mutation.rs

1//! Mutation events and the optional recorder hook.
2//!
3//! [`MutationEvent`] is the vocabulary a write-ahead log (or any observer —
4//! replication, audit, change-data-capture) would append to a durable stream.
5//! The enum covers every method on [`GraphStorageMut`]: each event carries
6//! exactly the information needed to deterministically re-apply the mutation
7//! against an empty store (or a snapshot) and recover the same state.
8//!
9//! [`MutationRecorder`] is the observer trait. Backends that want to emit
10//! events install a recorder via [`InMemoryGraph::set_mutation_recorder`].
11//! The default is `None` so zero-WAL workloads pay only a null-pointer check
12//! per mutation — no allocation, no clone.
13//!
14//! This module does not include any persistent WAL implementation. That
15//! would live in a future `lora-wal` crate (or similar) and implement
16//! `MutationRecorder` by appending each event to an on-disk log. Snapshot
17//! headers already reserve a `wal_lsn` field so a checkpoint = snapshot +
18//! log-truncation remains trivially expressible.
19
20use serde::{Deserialize, Serialize};
21
22use crate::{NodeId, Properties, PropertyValue, RelationshipId};
23
24/// A durable, replayable mutation against a graph store.
25///
26/// Each variant mirrors a method on `GraphStorageMut`. Applying every event
27/// in order against a store initialised from the snapshot whose `wal_lsn`
28/// immediately precedes the first event reproduces the committed state.
29///
30/// The enum derives `Serialize`/`Deserialize` so a WAL implementation can
31/// bincode-append events straight to disk without a second serialization
32/// layer.
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub enum MutationEvent {
35    CreateNode {
36        /// Id the backend allocated for the new node. Captured so replay
37        /// against a clean store produces the same id assignment as the
38        /// original (`next_node_id` advances deterministically).
39        id: NodeId,
40        labels: Vec<String>,
41        properties: Properties,
42    },
43    CreateRelationship {
44        id: RelationshipId,
45        src: NodeId,
46        dst: NodeId,
47        rel_type: String,
48        properties: Properties,
49    },
50    SetNodeProperty {
51        node_id: NodeId,
52        key: String,
53        value: PropertyValue,
54    },
55    RemoveNodeProperty {
56        node_id: NodeId,
57        key: String,
58    },
59    AddNodeLabel {
60        node_id: NodeId,
61        label: String,
62    },
63    RemoveNodeLabel {
64        node_id: NodeId,
65        label: String,
66    },
67    SetRelationshipProperty {
68        rel_id: RelationshipId,
69        key: String,
70        value: PropertyValue,
71    },
72    RemoveRelationshipProperty {
73        rel_id: RelationshipId,
74        key: String,
75    },
76    DeleteRelationship {
77        rel_id: RelationshipId,
78    },
79    DeleteNode {
80        node_id: NodeId,
81    },
82    DetachDeleteNode {
83        node_id: NodeId,
84    },
85    Clear,
86}
87
88/// Observer that receives every successful mutation in the order the store
89/// applied it.
90///
91/// The recorder sees events *after* the mutation has been applied to the
92/// in-memory state, so it never observes a mutation that the store
93/// rejected (invalid id, empty relationship type, …). This matches the
94/// classic write-ahead-log convention of logging committed changes only.
95///
96/// Implementations must be `Send + Sync` so a shared recorder can be driven
97/// from any thread holding the store's mutex.
98pub trait MutationRecorder: Send + Sync + 'static {
99    fn record(&self, event: &MutationEvent);
100}
101
102/// Convenience adapter that turns any `Fn(&MutationEvent) + Send + Sync`
103/// into a `MutationRecorder` — useful in tests and for quick wiring.
104pub struct ClosureRecorder<F>(pub F)
105where
106    F: Fn(&MutationEvent) + Send + Sync + 'static;
107
108impl<F> MutationRecorder for ClosureRecorder<F>
109where
110    F: Fn(&MutationEvent) + Send + Sync + 'static,
111{
112    fn record(&self, event: &MutationEvent) {
113        (self.0)(event)
114    }
115}