1use std::collections::{HashMap, HashSet};
4use std::sync::{Mutex, MutexGuard};
5
6use oxrdf::{Dataset, GraphName, NamedNode, Quad, Term};
7use spareval::QueryEvaluator;
8use spargebra::SparqlParser;
9
10use crate::error::{AppendError, StoreError};
11use crate::graph::{GraphCapabilities, GraphStore, QueryResults, RdfCapability};
12use crate::journal::{
13 AppendMeta, Entry, JournalCapabilities, JournalRecord, JournalStore, Position, prepare_append,
14};
15use crate::sparql::collect_results;
16
17#[derive(Default)]
18struct Inner {
19 dataset: Dataset,
21 journals: HashMap<NamedNode, Vec<Entry>>,
23 entry_names: HashSet<NamedNode>,
25 epochs: HashMap<NamedNode, u64>,
27}
28
29pub struct MemoryStore {
37 inner: Mutex<Inner>,
38 journal_caps: JournalCapabilities,
39 rdf: RdfCapability,
40}
41
42impl Default for MemoryStore {
43 fn default() -> Self {
44 MemoryStore::new()
45 }
46}
47
48impl std::fmt::Debug for MemoryStore {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 f.debug_struct("MemoryStore")
51 .field("journal_caps", &self.journal_caps)
52 .field("rdf", &self.rdf)
53 .finish_non_exhaustive()
54 }
55}
56
57impl MemoryStore {
58 pub fn new() -> Self {
60 MemoryStore {
61 inner: Mutex::new(Inner::default()),
62 journal_caps: JournalCapabilities {
63 fencing: true,
64 state_in_append: true,
65 },
66 rdf: RdfCapability::Rdf12,
67 }
68 }
69
70 pub fn with_fencing(mut self, on: bool) -> Self {
72 self.journal_caps.fencing = on;
73 self
74 }
75
76 pub fn with_state_in_append(mut self, on: bool) -> Self {
78 self.journal_caps.state_in_append = on;
79 self
80 }
81
82 pub fn with_rdf(mut self, rdf: RdfCapability) -> Self {
84 self.rdf = rdf;
85 self
86 }
87
88 pub fn dataset(&self) -> Dataset {
90 self.lock().dataset.clone()
91 }
92
93 fn lock(&self) -> MutexGuard<'_, Inner> {
94 self.inner
97 .lock()
98 .unwrap_or_else(std::sync::PoisonError::into_inner)
99 }
100
101 fn check_terms(&self, quads: &[Quad]) -> Result<(), StoreError> {
102 if self.rdf == RdfCapability::Rdf11
103 && quads.iter().any(|q| matches!(q.object, Term::Triple(_)))
104 {
105 return Err(StoreError::Unsupported(
106 "triple terms on a store that declares RDF 1.1".into(),
107 ));
108 }
109 Ok(())
110 }
111}
112
113impl JournalStore for MemoryStore {
114 fn capabilities(&self) -> JournalCapabilities {
115 self.journal_caps
116 }
117
118 fn append(
119 &self,
120 actor: &NamedNode,
121 expected: Position,
122 entries: &[Entry],
123 meta: &AppendMeta,
124 ) -> Result<Position, AppendError> {
125 let stamped = prepare_append(actor, expected, entries, meta, self.journal_caps)?;
126 let mut inner = self.lock();
127 if let Some(token) = &meta.fencing {
129 let highest = inner.epochs.get(&token.scope).copied().unwrap_or(0);
130 if token.epoch < highest {
131 return Err(AppendError::Fenced {
132 scope: token.scope.clone(),
133 epoch: token.epoch,
134 highest,
135 });
136 }
137 }
138 let actual = Position::new(inner.journals.get(actor).map_or(0, |j| j.len() as u64));
139 if actual != expected {
140 return Err(AppendError::Conflict { expected, actual });
141 }
142 if let Some(dup) = stamped
143 .iter()
144 .find(|e| inner.entry_names.contains(e.name()))
145 {
146 return Err(AppendError::Invalid(format!(
147 "entry {} is already in the store",
148 dup.name()
149 )));
150 }
151 for e in &stamped {
152 self.check_terms(&e.quads())?;
153 }
154 if let Some(delta) = &meta.state {
155 let g = GraphName::NamedNode(actor.clone());
156 let quads: Vec<Quad> = delta
157 .assert
158 .iter()
159 .map(|t| t.clone().in_graph(g.clone()))
160 .collect();
161 self.check_terms(&quads)?;
162 }
163 if let Some(token) = &meta.fencing {
165 inner.epochs.insert(token.scope.clone(), token.epoch);
166 }
167 for e in &stamped {
168 for q in e.quads() {
169 inner.dataset.insert(&q);
170 }
171 inner.entry_names.insert(e.name().clone());
172 }
173 if let Some(delta) = &meta.state {
174 let g = GraphName::NamedNode(actor.clone());
175 for t in &delta.retract {
176 inner.dataset.remove(&t.clone().in_graph(g.clone()));
177 }
178 for t in &delta.assert {
179 inner.dataset.insert(&t.clone().in_graph(g.clone()));
180 }
181 }
182 let journal = inner.journals.entry(actor.clone()).or_default();
183 journal.extend(stamped);
184 Ok(Position::new(journal.len() as u64))
185 }
186
187 fn read(&self, actor: &NamedNode, from: Position) -> Result<Vec<JournalRecord>, StoreError> {
188 let inner = self.lock();
189 let Some(journal) = inner.journals.get(actor) else {
190 return Ok(Vec::new());
191 };
192 let start = from.get().max(1);
193 Ok(journal
194 .iter()
195 .enumerate()
196 .skip((start - 1) as usize)
197 .map(|(i, e)| JournalRecord {
198 position: Position::new(i as u64 + 1),
199 entry: e.clone(),
200 })
201 .collect())
202 }
203
204 fn head(&self, actor: &NamedNode) -> Result<Position, StoreError> {
205 Ok(Position::new(
206 self.lock()
207 .journals
208 .get(actor)
209 .map_or(0, |j| j.len() as u64),
210 ))
211 }
212
213 fn actors(&self) -> Result<Vec<NamedNode>, StoreError> {
214 let mut actors: Vec<NamedNode> = self.lock().journals.keys().cloned().collect();
215 actors.sort();
216 Ok(actors)
217 }
218}
219
220impl GraphStore for MemoryStore {
221 fn capabilities(&self) -> GraphCapabilities {
222 GraphCapabilities::plain(self.rdf)
223 }
224
225 fn insert_quads(&self, quads: &[Quad]) -> Result<(), StoreError> {
226 self.check_terms(quads)?;
227 let mut inner = self.lock();
228 for q in quads {
229 inner.dataset.insert(q);
230 }
231 Ok(())
232 }
233
234 fn remove_quads(&self, quads: &[Quad]) -> Result<(), StoreError> {
235 let mut inner = self.lock();
236 for q in quads {
237 inner.dataset.remove(q);
238 }
239 Ok(())
240 }
241
242 fn clear_graph(&self, graph: &NamedNode) -> Result<(), StoreError> {
243 let mut inner = self.lock();
244 let doomed: Vec<Quad> = inner
245 .dataset
246 .quads_for_graph_name(graph)
247 .map(|q| q.into_owned())
248 .collect();
249 for q in &doomed {
250 inner.dataset.remove(q);
251 }
252 Ok(())
253 }
254
255 fn query(&self, sparql: &str) -> Result<QueryResults, StoreError> {
256 let query = SparqlParser::new()
257 .parse_query(sparql)
258 .map_err(|e| StoreError::invalid(e.to_string()))?;
259 let inner = self.lock();
260 let evaluator = QueryEvaluator::new();
261 let results = evaluator
262 .prepare(&query)
263 .execute(&inner.dataset)
264 .map_err(StoreError::backend)?;
265 collect_results(results)
266 }
267}