1use super::{AcidTransaction, IsolationLevel, TransactionId, TransactionState};
13use crate::model::{GraphName, NamedNode, Object, Predicate, Quad, Subject};
14use crate::OxirsError;
15use ahash::{AHashMap, AHashSet};
16use std::sync::{Arc, RwLock};
17
18pub struct NamedGraphTransaction {
20 inner: AcidTransaction,
22 graph_operations: AHashMap<GraphName, GraphOperations>,
24 graph_locks: Arc<RwLock<AHashSet<GraphName>>>,
26}
27
28#[derive(Debug, Clone, Default)]
30struct GraphOperations {
31 inserts: Vec<Quad>,
33 deletes: Vec<Quad>,
35 cleared: bool,
37 created: bool,
39 dropped: bool,
41}
42
43impl NamedGraphTransaction {
44 pub(super) fn new(
46 inner: AcidTransaction,
47 graph_locks: Arc<RwLock<AHashSet<GraphName>>>,
48 ) -> Self {
49 Self {
50 inner,
51 graph_operations: AHashMap::new(),
52 graph_locks,
53 }
54 }
55
56 pub fn id(&self) -> TransactionId {
58 self.inner.id()
59 }
60
61 pub fn state(&self) -> TransactionState {
63 self.inner.state()
64 }
65
66 pub fn isolation(&self) -> IsolationLevel {
68 self.inner.isolation()
69 }
70
71 pub fn insert_into_graph(
73 &mut self,
74 graph: GraphName,
75 subject: Subject,
76 predicate: Predicate,
77 object: Object,
78 ) -> Result<bool, OxirsError> {
79 let quad = Quad::new(subject, predicate, object, graph.clone());
81
82 let ops = self.graph_operations.entry(graph).or_default();
84 ops.inserts.push(quad.clone());
85
86 self.inner.insert(quad)
88 }
89
90 pub fn delete_from_graph(
92 &mut self,
93 graph: GraphName,
94 subject: Subject,
95 predicate: Predicate,
96 object: Object,
97 ) -> Result<bool, OxirsError> {
98 let quad = Quad::new(subject, predicate, object, graph.clone());
100
101 let ops = self.graph_operations.entry(graph).or_default();
103 ops.deletes.push(quad.clone());
104
105 self.inner.delete(quad)
107 }
108
109 pub fn clear_graph(&mut self, graph: GraphName) -> Result<usize, OxirsError> {
111 let ops = self.graph_operations.entry(graph.clone()).or_default();
113 ops.cleared = true;
114
115 Ok(0)
118 }
119
120 pub fn create_graph(&mut self, graph: NamedNode) -> Result<(), OxirsError> {
122 let graph_name = GraphName::NamedNode(graph);
123
124 let ops = self.graph_operations.entry(graph_name).or_default();
126 if ops.dropped {
127 return Err(OxirsError::Store(
128 "Cannot create a graph that was dropped in the same transaction".to_string(),
129 ));
130 }
131 ops.created = true;
132
133 Ok(())
134 }
135
136 pub fn drop_graph(&mut self, graph: NamedNode) -> Result<(), OxirsError> {
138 let graph_name = GraphName::NamedNode(graph);
139
140 let should_remove = if let Some(ops) = self.graph_operations.get(&graph_name) {
142 ops.created
143 } else {
144 false
145 };
146
147 if should_remove {
148 self.graph_operations.remove(&graph_name);
150 } else {
151 let ops = self.graph_operations.entry(graph_name).or_default();
153 ops.dropped = true;
154 ops.cleared = true; }
156
157 Ok(())
158 }
159
160 pub fn copy_graph(
162 &mut self,
163 _source: GraphName,
164 _destination: GraphName,
165 ) -> Result<usize, OxirsError> {
166 Ok(0)
174 }
175
176 pub fn move_graph(
178 &mut self,
179 source: GraphName,
180 destination: GraphName,
181 ) -> Result<usize, OxirsError> {
182 let count = self.copy_graph(source.clone(), destination)?;
184 if let GraphName::NamedNode(node) = source {
185 self.drop_graph(node)?;
186 }
187 Ok(count)
188 }
189
190 pub fn add_graph(
192 &mut self,
193 _source: GraphName,
194 _destination: GraphName,
195 ) -> Result<usize, OxirsError> {
196 Ok(0)
203 }
204
205 pub fn graph_stats(&self, graph: &GraphName) -> Option<GraphStats> {
207 self.graph_operations.get(graph).map(|ops| GraphStats {
208 inserts: ops.inserts.len(),
209 deletes: ops.deletes.len(),
210 cleared: ops.cleared,
211 created: ops.created,
212 dropped: ops.dropped,
213 })
214 }
215
216 pub fn modified_graphs(&self) -> Vec<GraphName> {
218 self.graph_operations.keys().cloned().collect()
219 }
220
221 pub fn lock_graph(&mut self, graph: GraphName) -> Result<(), OxirsError> {
223 if self.isolation() == IsolationLevel::Serializable {
224 let mut locks = self.graph_locks.write().map_err(|e| {
225 OxirsError::ConcurrencyError(format!("Failed to acquire graph lock: {}", e))
226 })?;
227
228 if !locks.insert(graph) {
229 return Err(OxirsError::ConcurrencyError(
230 "Graph is already locked by another transaction".to_string(),
231 ));
232 }
233 }
234 Ok(())
235 }
236
237 fn release_locks(&self) -> Result<(), OxirsError> {
239 let mut locks = self.graph_locks.write().map_err(|e| {
240 OxirsError::ConcurrencyError(format!("Failed to release graph locks: {}", e))
241 })?;
242
243 for graph in self.graph_operations.keys() {
244 locks.remove(graph);
245 }
246
247 Ok(())
248 }
249
250 pub fn commit(self) -> Result<(), OxirsError> {
252 self.release_locks()?;
254
255 self.inner.commit()
257 }
258
259 pub fn rollback(self) -> Result<(), OxirsError> {
261 self.release_locks()?;
263
264 self.inner.abort()
266 }
267}
268
269#[derive(Debug, Clone)]
271pub struct GraphStats {
272 pub inserts: usize,
274 pub deletes: usize,
276 pub cleared: bool,
278 pub created: bool,
280 pub dropped: bool,
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287 use crate::model::{Literal, NamedNode};
288
289 fn create_test_quad(graph: GraphName) -> Quad {
290 Quad::new(
291 Subject::NamedNode(NamedNode::new("http://example.org/subject").unwrap()),
292 Predicate::from(NamedNode::new("http://example.org/predicate").unwrap()),
293 Object::Literal(Literal::new("test")),
294 graph,
295 )
296 }
297
298 #[test]
299 fn test_graph_operations_tracking() {
300 let graph = GraphName::NamedNode(NamedNode::new("http://example.org/graph1").unwrap());
304
305 let mut ops = GraphOperations::default();
306 ops.inserts.push(create_test_quad(graph.clone()));
307
308 assert_eq!(ops.inserts.len(), 1);
309 assert_eq!(ops.deletes.len(), 0);
310 assert!(!ops.cleared);
311 }
312
313 #[test]
314 fn test_graph_stats() {
315 let mut ops = GraphOperations::default();
316 ops.inserts.push(create_test_quad(GraphName::DefaultGraph));
317 ops.cleared = true;
318
319 assert_eq!(ops.inserts.len(), 1);
320 assert!(ops.cleared);
321 }
322}