uqa_graph/
versioned_store.rs1use std::collections::BTreeSet;
15
16use crate::delta::{DeltaOp, GraphDelta};
17use crate::store::{GraphStore, GraphStoreError, GraphStoreResult};
18
19type InvalidationCallback = Box<dyn Fn(&BTreeSet<String>) + Send + Sync>;
20type InverseDelta = Vec<(String, DeltaOp)>;
21
22fn record_vertex_removal<G: GraphStore>(
23 candidate: &mut G,
24 graph: &str,
25 vertex_id: u64,
26 inverse: &mut InverseDelta,
27 affected_labels: &mut BTreeSet<String>,
28) -> GraphStoreResult<()> {
29 if !candidate
30 .vertex_graphs(vertex_id)?
31 .iter()
32 .any(|owner| owner == graph)
33 {
34 return Ok(());
35 }
36 let existing = candidate
37 .get_vertex(vertex_id)?
38 .ok_or_else(|| GraphStoreError::CorruptGraph(format!("missing vertex {vertex_id}")))?;
39 let mut incident_edges = Vec::new();
40 let mut ids = candidate.out_edge_ids(vertex_id, graph)?;
41 ids.extend(candidate.in_edge_ids(vertex_id, graph)?);
42 for id in ids {
43 let edge = candidate
44 .get_edge(id)?
45 .ok_or_else(|| GraphStoreError::CorruptGraph(format!("missing incident edge {id}")))?;
46 affected_labels.insert(edge.label.clone());
47 incident_edges.push(edge);
48 }
49 candidate.remove_vertex(vertex_id, graph)?;
50 for edge in incident_edges {
51 inverse.push((graph.to_string(), DeltaOp::AddEdge(edge)));
52 }
53 inverse.push((graph.to_string(), DeltaOp::AddVertex(existing)));
54 Ok(())
55}
56
57pub struct VersionedGraphStore<'a, G: GraphStore> {
58 base: &'a mut G,
59 graph: String,
60 version: u64,
61 deltas: Vec<GraphDelta>,
62 inverse_deltas: Vec<InverseDelta>,
63 on_invalidate: Vec<InvalidationCallback>,
64}
65
66impl<'a, G: GraphStore> VersionedGraphStore<'a, G> {
67 pub fn new(base: &'a mut G, graph: impl Into<String>) -> Self {
68 Self {
69 base,
70 graph: graph.into(),
71 version: 0,
72 deltas: Vec::new(),
73 inverse_deltas: Vec::new(),
74 on_invalidate: Vec::new(),
75 }
76 }
77
78 pub fn version(&self) -> u64 {
79 self.version
80 }
81
82 pub fn base(&self) -> &G {
83 &*self.base
84 }
85
86 pub fn base_mut(&mut self) -> &mut G {
87 self.base
88 }
89
90 pub fn apply(&mut self, delta: GraphDelta) -> GraphStoreResult<u64> {
93 if !self.base.has_graph(&self.graph)? {
94 return Err(GraphStoreError::UnknownGraph(self.graph.clone()));
95 }
96 let next_version = self.version.checked_add(1).ok_or_else(|| {
97 GraphStoreError::IdExhausted("graph version counter overflow".to_string())
98 })?;
99 let mut inverse = InverseDelta::new();
100 let mut affected_labels = delta.affected_edge_labels();
101 self.base.transaction(|candidate| {
102 for op in delta.ops() {
103 match op {
104 DeltaOp::AddVertex(vertex) => {
105 if let Some(previous) = candidate.get_vertex(vertex.vertex_id)? {
106 let owners = candidate.vertex_graphs(vertex.vertex_id)?;
107 let owner = owners.first().ok_or_else(|| {
108 GraphStoreError::CorruptGraph(format!(
109 "vertex {} has no owning graph",
110 vertex.vertex_id
111 ))
112 })?;
113 inverse.push((owner.clone(), DeltaOp::AddVertex(previous)));
114 if !owners.contains(&self.graph) {
115 inverse.push((
116 self.graph.clone(),
117 DeltaOp::RemoveVertex(vertex.vertex_id),
118 ));
119 }
120 } else {
121 inverse.push((
122 self.graph.clone(),
123 DeltaOp::RemoveVertex(vertex.vertex_id),
124 ));
125 }
126 candidate.add_vertex(vertex.clone(), &self.graph)?;
127 }
128 DeltaOp::RemoveVertex(vertex_id) => {
129 record_vertex_removal(
130 candidate,
131 &self.graph,
132 *vertex_id,
133 &mut inverse,
134 &mut affected_labels,
135 )?;
136 }
137 DeltaOp::AddEdge(edge) => {
138 if let Some(previous) = candidate.get_edge(edge.edge_id)? {
139 affected_labels.insert(previous.label.clone());
140 let owners = candidate.edge_graphs(edge.edge_id)?;
141 let owner = owners.first().ok_or_else(|| {
142 GraphStoreError::CorruptGraph(format!(
143 "edge {} has no owning graph",
144 edge.edge_id
145 ))
146 })?;
147 inverse.push((owner.clone(), DeltaOp::AddEdge(previous)));
148 if !owners.contains(&self.graph) {
149 inverse
150 .push((self.graph.clone(), DeltaOp::RemoveEdge(edge.edge_id)));
151 }
152 } else {
153 inverse.push((self.graph.clone(), DeltaOp::RemoveEdge(edge.edge_id)));
154 }
155 candidate.add_edge(edge.clone(), &self.graph)?;
156 }
157 DeltaOp::RemoveEdge(edge_id) => {
158 if !candidate.edge_graphs(*edge_id)?.contains(&self.graph) {
159 continue;
160 }
161 let existing = candidate.get_edge(*edge_id)?.ok_or_else(|| {
162 GraphStoreError::CorruptGraph(format!("missing edge {edge_id}"))
163 })?;
164 affected_labels.insert(existing.label.clone());
165 candidate.remove_edge(*edge_id, &self.graph)?;
166 inverse.push((self.graph.clone(), DeltaOp::AddEdge(existing)));
167 }
168 }
169 }
170 Ok(())
171 })?;
172 self.version = next_version;
173 self.deltas.push(delta);
174 self.inverse_deltas.push(inverse);
175 if !affected_labels.is_empty() {
176 for callback in &self.on_invalidate {
177 callback(&affected_labels);
178 }
179 }
180 Ok(self.version)
181 }
182
183 pub fn rollback(&mut self, to_version: u64) -> GraphStoreResult<()> {
186 if to_version > self.version {
187 return Err(GraphStoreError::InvalidMutation(format!(
188 "cannot rollback to version {to_version} (current: {})",
189 self.version
190 )));
191 }
192 let mut remaining_version = self.version;
193 let mut inverse_count = 0usize;
194 self.base.transaction(|candidate| {
195 while remaining_version > to_version {
196 let offset = inverse_count.checked_add(1).ok_or_else(|| {
197 GraphStoreError::CorruptGraph("version history index overflow".to_string())
198 })?;
199 let inverse = self
200 .inverse_deltas
201 .get(
202 self.inverse_deltas
203 .len()
204 .checked_sub(offset)
205 .ok_or_else(|| {
206 GraphStoreError::CorruptGraph(
207 "version history is shorter than the current graph version"
208 .to_string(),
209 )
210 })?,
211 )
212 .ok_or_else(|| {
213 GraphStoreError::CorruptGraph(
214 "version history is shorter than the current graph version".to_string(),
215 )
216 })?;
217 for (graph, op) in inverse.iter().rev() {
218 match op {
219 DeltaOp::AddVertex(vertex) => {
220 candidate.add_vertex(vertex.clone(), graph)?;
221 }
222 DeltaOp::RemoveVertex(vertex_id) => {
223 candidate.remove_vertex(*vertex_id, graph)?;
224 }
225 DeltaOp::AddEdge(edge) => {
226 candidate.add_edge(edge.clone(), graph)?;
227 }
228 DeltaOp::RemoveEdge(edge_id) => {
229 candidate.remove_edge(*edge_id, graph)?;
230 }
231 }
232 }
233 remaining_version = remaining_version.checked_sub(1).ok_or_else(|| {
234 GraphStoreError::CorruptGraph("graph version underflow".to_string())
235 })?;
236 inverse_count = inverse_count.checked_add(1).ok_or_else(|| {
237 GraphStoreError::CorruptGraph("version history index overflow".to_string())
238 })?;
239 }
240 Ok(())
241 })?;
242 self.version = remaining_version;
243 let new_len = self
244 .inverse_deltas
245 .len()
246 .checked_sub(inverse_count)
247 .ok_or_else(|| {
248 GraphStoreError::CorruptGraph("version history truncation underflow".to_string())
249 })?;
250 self.inverse_deltas.truncate(new_len);
251 self.deltas.truncate(new_len);
252 Ok(())
253 }
254
255 pub fn on_invalidate<F>(&mut self, callback: F)
258 where
259 F: Fn(&BTreeSet<String>) + Send + Sync + 'static,
260 {
261 self.on_invalidate.push(Box::new(callback));
262 }
263}