1use std::{
4 collections::{BTreeMap, BTreeSet, HashMap},
5 ops::{Bound, ControlFlow},
6 sync::{Arc, PoisonError, RwLock},
7 time::{Duration, SystemTime, UNIX_EPOCH},
8};
9
10use hidane_proto::ordered::Fields;
11
12use super::{
13 Change, Commit, ListItem, ListOptions, ReadTime, Store, StoreError, StoredDocument, Visit,
14 WriteBatch,
15};
16use crate::{
17 key::{encode_escaped, encode_path, encode_path_prefix, path_subtree_end},
18 path::ResourcePath,
19};
20
21type Clock = Box<dyn Fn() -> i64 + Send + Sync>;
22
23pub const DEFAULT_RETENTION: Duration = Duration::from_secs(60 * 60);
25
26pub struct MemoryStore {
27 databases: RwLock<HashMap<String, Arc<RwLock<Database>>>>,
28 clock: Clock,
29 retention_micros: i64,
30}
31
32#[derive(Default)]
33struct Database {
34 last_commit: i64,
36 documents: BTreeMap<Vec<u8>, Record>,
38 groups: BTreeSet<Vec<u8>>,
41}
42
43impl Database {
44 fn subtree_has_live(&self, prefix: &[u8], time: i64) -> bool {
46 self.documents
47 .range(prefix.to_vec()..path_subtree_end(prefix))
48 .any(|(_, record)| record.at(time).is_some())
49 }
50}
51
52struct Record {
53 path: Arc<ResourcePath>,
54 versions: Vec<(i64, Option<Arc<StoredDocument>>)>,
56}
57
58impl Record {
59 fn at(&self, time: i64) -> Option<&Arc<StoredDocument>> {
60 let newer = self.versions.partition_point(|(t, _)| *t <= time);
61 newer
62 .checked_sub(1)
63 .and_then(|i| self.versions[i].1.as_ref())
64 }
65
66 fn latest(&self) -> Option<&Arc<StoredDocument>> {
67 self.versions.last().and_then(|(_, doc)| doc.as_ref())
68 }
69
70 fn prune(&mut self, cutoff: i64) {
73 let reachable_from = self.versions.partition_point(|(t, _)| *t <= cutoff);
74 if reachable_from > 1 {
75 self.versions.drain(..reachable_from - 1);
76 }
77 }
78}
79
80impl Default for MemoryStore {
81 fn default() -> Self {
82 Self::new()
83 }
84}
85
86impl MemoryStore {
87 pub fn new() -> Self {
88 Self::with_clock(system_micros)
89 }
90
91 pub fn with_clock(clock: impl Fn() -> i64 + Send + Sync + 'static) -> Self {
94 Self {
95 databases: RwLock::default(),
96 clock: Box::new(clock),
97 retention_micros: micros(DEFAULT_RETENTION),
98 }
99 }
100
101 #[must_use]
103 pub fn with_retention(mut self, retention: Duration) -> Self {
104 self.retention_micros = micros(retention);
105 self
106 }
107
108 fn existing(&self, database: &str) -> Option<Arc<RwLock<Database>>> {
109 self.databases
110 .read()
111 .unwrap_or_else(PoisonError::into_inner)
112 .get(database)
113 .cloned()
114 }
115
116 fn get_or_create(&self, database: &str) -> Arc<RwLock<Database>> {
117 if let Some(db) = self.existing(database) {
118 return db;
119 }
120 self.databases
121 .write()
122 .unwrap_or_else(PoisonError::into_inner)
123 .entry(database.to_owned())
124 .or_default()
125 .clone()
126 }
127
128 fn read<R>(&self, database: &str, empty: R, f: impl FnOnce(&Database) -> R) -> R {
129 match self.existing(database) {
130 Some(db) => f(&db.read().unwrap_or_else(PoisonError::into_inner)),
131 None => empty,
132 }
133 }
134}
135
136fn system_micros() -> i64 {
137 SystemTime::now()
138 .duration_since(UNIX_EPOCH)
139 .map_or(0, |d| i64::try_from(d.as_micros()).unwrap_or(i64::MAX))
140}
141
142fn micros(d: Duration) -> i64 {
143 i64::try_from(d.as_micros()).unwrap_or(i64::MAX)
144}
145
146impl Store for MemoryStore {
147 fn latest_read_time(&self, database: &str) -> ReadTime {
148 let last = self.read(database, 0, |db| db.last_commit);
149 ReadTime((self.clock)().max(last))
150 }
151
152 fn earliest_read_time(&self, database: &str) -> ReadTime {
153 ReadTime(
154 self.latest_read_time(database)
155 .0
156 .saturating_sub(self.retention_micros),
157 )
158 }
159
160 fn get(
161 &self,
162 database: &str,
163 path: &ResourcePath,
164 at: ReadTime,
165 ) -> Option<Arc<StoredDocument>> {
166 self.read(database, None, |db| {
167 db.documents
168 .get(&encode_path(path.segments()))
169 .and_then(|record| record.at(at.0))
170 .cloned()
171 })
172 }
173
174 fn list_collection(
175 &self,
176 database: &str,
177 collection: &ResourcePath,
178 at: ReadTime,
179 options: ListOptions<'_>,
180 visit: &mut dyn FnMut(ListItem<'_>) -> ControlFlow<()>,
181 ) {
182 self.read(database, (), |db| {
183 let prefix = encode_path_prefix(collection.segments());
184 let end = Bound::Excluded(path_subtree_end(&prefix));
185 let depth = collection.len() + 1;
186 let mut start = match options.after {
187 Some(after) => {
188 Bound::Included(path_subtree_end(&encode_path_prefix(after.segments())))
189 }
190 None => Bound::Included(prefix),
191 };
192 'scan: loop {
193 let Some((_, record)) = db.documents.range((start.clone(), end.clone())).next()
195 else {
196 return;
197 };
198 let child = encode_path_prefix(record.path.segments().take(depth));
199 let live = (record.path.len() == depth)
200 .then(|| record.at(at.0))
201 .flatten();
202 let flow = match live {
203 Some(doc) => visit(ListItem::Document(doc)),
204 None if options.include_missing && db.subtree_has_live(&child, at.0) => {
205 let path = ResourcePath::from_segments(record.path.segments().take(depth));
206 visit(ListItem::Missing(&path))
207 }
208 None => ControlFlow::Continue(()),
209 };
210 if flow.is_break() {
211 return;
212 }
213 start = Bound::Included(path_subtree_end(&child));
214 continue 'scan;
215 }
216 });
217 }
218
219 fn scan_collection_group(
220 &self,
221 database: &str,
222 parent: &ResourcePath,
223 collection_id: &str,
224 at: ReadTime,
225 visit: &mut Visit<'_>,
226 ) {
227 self.read(database, (), |db| {
228 let mut prefix = encode_escaped(collection_id.as_bytes());
229 let group_len = prefix.len();
230 prefix.extend(encode_path_prefix(parent.segments()));
231 let end = path_subtree_end(&prefix);
232 for entry in db.groups.range(prefix..end) {
233 let doc = db
234 .documents
235 .get(&entry[group_len..])
236 .filter(|record| record.path.len() > parent.len())
238 .and_then(|record| record.at(at.0));
239 if let Some(doc) = doc
240 && visit(doc).is_break()
241 {
242 return;
243 }
244 }
245 });
246 }
247
248 fn scan_descendants(
249 &self,
250 database: &str,
251 parent: &ResourcePath,
252 at: ReadTime,
253 visit: &mut Visit<'_>,
254 ) {
255 self.read(database, (), |db| {
256 let start = Bound::Excluded(encode_path(parent.segments()));
257 let end = Bound::Excluded(path_subtree_end(&encode_path_prefix(parent.segments())));
258 for (_, record) in db.documents.range((start, end)) {
259 if let Some(doc) = record.at(at.0)
260 && visit(doc).is_break()
261 {
262 return;
263 }
264 }
265 });
266 }
267
268 fn list_collection_ids(
269 &self,
270 database: &str,
271 parent: &ResourcePath,
272 at: ReadTime,
273 ) -> Vec<String> {
274 self.read(database, Vec::new(), |db| {
275 let prefix = encode_path_prefix(parent.segments());
276 let end = Bound::Excluded(path_subtree_end(&prefix));
277 let mut start = Bound::Excluded(encode_path(parent.segments()));
278 let mut ids = Vec::new();
279 'scan: loop {
280 for (_, record) in db.documents.range((start.clone(), end.clone())) {
281 if record.at(at.0).is_none() {
282 continue;
283 }
284 let collection = record.path.segments().take(parent.len() + 1);
287 if let Some(id) = collection.clone().last() {
288 ids.push(id.to_owned());
289 }
290 let collection_prefix = encode_path_prefix(collection);
291 start = Bound::Included(path_subtree_end(&collection_prefix));
292 continue 'scan;
293 }
294 return ids;
295 }
296 })
297 }
298
299 fn commit(
300 &self,
301 database: &str,
302 write: &mut dyn FnMut(&mut dyn WriteBatch) -> Result<(), StoreError>,
303 ) -> Result<Commit, StoreError> {
304 let db = self.get_or_create(database);
305 let mut db = db.write().unwrap_or_else(PoisonError::into_inner);
306 let commit_time = (self.clock)().max(db.last_commit + 1);
307
308 let mut batch = MemoryBatch {
309 db: &db,
310 commit_time,
311 pending: BTreeMap::new(),
312 };
313 write(&mut batch)?;
314 let pending = batch.pending;
315
316 let cutoff = commit_time.saturating_sub(self.retention_micros);
317 let mut changes = Vec::with_capacity(pending.len());
318 for (key, (path, after)) in pending {
319 let before = db.documents.get(&key).and_then(Record::latest).cloned();
320 if before.is_none() && after.is_none() {
321 continue; }
323 if !db.documents.contains_key(&key)
324 && let Some(collection_id) = path.collection_id()
325 {
326 let mut group_key = encode_escaped(collection_id.as_bytes());
327 group_key.extend_from_slice(&key);
328 db.groups.insert(group_key);
329 }
330 let record = db.documents.entry(key).or_insert_with(|| Record {
331 path: Arc::clone(&path),
332 versions: Vec::with_capacity(1),
334 });
335 record.versions.push((commit_time, after.clone()));
336 record.prune(cutoff);
337 changes.push(Change {
338 path,
339 before,
340 after,
341 });
342 }
343 db.last_commit = commit_time;
344 Ok(Commit {
345 commit_time: ReadTime(commit_time),
346 changes,
347 })
348 }
349
350 fn clear_database(&self, database: &str) -> ReadTime {
351 let db = self.get_or_create(database);
352 let mut db = db.write().unwrap_or_else(PoisonError::into_inner);
353 let at = (self.clock)().max(db.last_commit + 1);
354 db.documents.clear();
355 db.groups.clear();
356 db.last_commit = at;
357 ReadTime(at)
358 }
359
360 fn database_names(&self) -> Vec<String> {
361 self.databases
362 .read()
363 .unwrap_or_else(PoisonError::into_inner)
364 .keys()
365 .cloned()
366 .collect()
367 }
368
369 fn clear(&self) {
370 self.databases
371 .write()
372 .unwrap_or_else(PoisonError::into_inner)
373 .clear();
374 }
375}
376
377type PendingWrite = (Arc<ResourcePath>, Option<Arc<StoredDocument>>);
379
380struct MemoryBatch<'a> {
381 db: &'a Database,
382 commit_time: i64,
383 pending: BTreeMap<Vec<u8>, PendingWrite>,
385}
386
387impl MemoryBatch<'_> {
388 fn shared_path(&self, key: &[u8], path: &ResourcePath) -> Arc<ResourcePath> {
390 self.pending
391 .get(key)
392 .map(|(p, _)| Arc::clone(p))
393 .or_else(|| self.db.documents.get(key).map(|r| Arc::clone(&r.path)))
394 .unwrap_or_else(|| Arc::new(path.clone()))
395 }
396}
397
398impl WriteBatch for MemoryBatch<'_> {
399 fn commit_time(&self) -> ReadTime {
400 ReadTime(self.commit_time)
401 }
402
403 fn get(&self, path: &ResourcePath) -> Option<Arc<StoredDocument>> {
404 let key = encode_path(path.segments());
405 match self.pending.get(&key) {
406 Some((_, pending)) => pending.clone(),
407 None => self
408 .db
409 .documents
410 .get(&key)
411 .and_then(Record::latest)
412 .cloned(),
413 }
414 }
415
416 fn set(&mut self, path: &ResourcePath, fields: Fields) {
417 let now = ReadTime(self.commit_time).to_timestamp();
418 let create_time = self
419 .get(path)
420 .map_or_else(|| now, |existing| existing.create_time);
421 let key = encode_path(path.segments());
422 let shared = self.shared_path(&key, path);
423 let document = StoredDocument::new(Arc::clone(&shared), fields, create_time, now);
424 self.pending.insert(key, (shared, Some(Arc::new(document))));
425 }
426
427 fn delete(&mut self, path: &ResourcePath) {
428 let key = encode_path(path.segments());
429 let shared = self.shared_path(&key, path);
430 self.pending.insert(key, (shared, None));
431 }
432}
433
434#[cfg(test)]
435mod tests;