1mod persistence;
8mod storage;
9mod traversal;
10
11use std::{
12 any::Any,
13 collections::{BTreeMap, HashMap, HashSet},
14 path::Path,
15 sync::{
16 RwLock,
17 atomic::{AtomicUsize, Ordering},
18 },
19};
20
21use async_trait::async_trait;
22use serde::{Deserialize, Serialize};
23
24use crate::{
25 Result,
26 backend::{
27 BackendImpl, InstanceMetadata, InstanceSecrets, RecordMutations, RecordPage, RecordRange,
28 RecordView, StagingToken, StoreStateLifecycle, StoreStateRequest, VerificationStatus,
29 errors::BackendError,
30 },
31 entry::{Entry, ID},
32 snapshot::Snapshot,
33};
34
35use crate::backend::database::sorting;
36
37#[derive(Debug, Clone, Default, Serialize, Deserialize)]
39pub(crate) struct TreeTipsCache {
40 pub(crate) tree_tips: HashSet<ID>,
41 pub(crate) subtree_tips: HashMap<String, HashSet<ID>>,
42}
43
44#[derive(Debug)]
50pub(crate) struct InMemoryInner {
51 pub(crate) entries: HashMap<ID, Entry>,
52 pub(crate) store_state_namespaces: HashMap<String, RecordNamespace>,
53 pub(crate) verification_status: HashMap<ID, VerificationStatus>,
54 pub(crate) instance_metadata: Option<InstanceMetadata>,
59 pub(crate) instance_secrets: Option<InstanceSecrets>,
65 pub(crate) tips: HashMap<ID, TreeTipsCache>,
67}
68
69#[derive(Debug)]
70pub(crate) struct RecordNamespace {
71 request: StoreStateRequest,
72 ready: bool,
73 unlinked: bool,
76 records: BTreeMap<Vec<u8>, Option<Vec<u8>>>,
77}
78
79#[derive(Debug)]
92pub struct InMemory {
93 pub(crate) inner: RwLock<InMemoryInner>,
97 store_state_point_reads: AtomicUsize,
98 store_state_scan_reads: AtomicUsize,
99 #[cfg(feature = "testing")]
100 store_history_reads: AtomicUsize,
101}
102
103impl InMemory {
104 #[cfg(feature = "testing")]
105 pub fn store_state_record_count(&self, database: &ID, store: &str) -> usize {
106 self.inner
107 .read()
108 .unwrap()
109 .store_state_namespaces
110 .values()
111 .filter(|record_set| {
112 record_set.ready
113 && record_set.request.database == *database
114 && record_set.request.store == store
115 && matches!(
116 record_set.request.projection.name.as_str(),
117 "eidetica/table/rows" | "eidetica/password/eidetica/table/rows"
118 )
119 })
120 .map(|record_set| record_set.records.len())
121 .max()
122 .unwrap_or(0)
123 }
124
125 #[cfg(feature = "testing")]
126 pub fn store_state_records(
127 &self,
128 database: &ID,
129 store: &str,
130 ) -> Option<crate::backend::RecordMutations> {
131 self.inner
132 .read()
133 .unwrap()
134 .store_state_namespaces
135 .values()
136 .find(|namespace| {
137 namespace.ready
138 && namespace.request.database == *database
139 && namespace.request.store == store
140 })
141 .map(|namespace| namespace.records.clone())
142 }
143
144 #[cfg(feature = "testing")]
145 pub fn store_state_read_counts(&self) -> (usize, usize) {
146 (
147 self.store_state_point_reads.load(Ordering::Relaxed),
148 self.store_state_scan_reads.load(Ordering::Relaxed),
149 )
150 }
151
152 #[cfg(feature = "testing")]
153 pub fn store_history_read_count(&self) -> usize {
154 self.store_history_reads.load(Ordering::Relaxed)
155 }
156}
157
158impl InMemory {
159 pub fn new() -> Self {
161 Self {
162 inner: RwLock::new(InMemoryInner {
163 entries: HashMap::new(),
164 store_state_namespaces: HashMap::new(),
165 verification_status: HashMap::new(),
166 instance_metadata: None,
167 instance_secrets: None,
168 tips: HashMap::new(),
169 }),
170 store_state_point_reads: AtomicUsize::new(0),
171 store_state_scan_reads: AtomicUsize::new(0),
172 #[cfg(feature = "testing")]
173 store_history_reads: AtomicUsize::new(0),
174 }
175 }
176
177 pub async fn all_ids(&self) -> Vec<ID> {
179 let inner = self.inner.read().unwrap();
180 inner.entries.keys().cloned().collect()
181 }
182
183 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
200 persistence::save_to_file(self, path)
201 }
202
203 pub async fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
215 persistence::load_from_file(path)
216 }
217
218 pub async fn try_load_from_file<P: AsRef<Path>>(path: P) -> Result<Option<Self>> {
224 persistence::try_load_from_file(path)
225 }
226
227 pub fn sort_entries_by_height(&self, _tree: &ID, entries: &mut [Entry]) {
235 sorting::sort_entries_by_height(entries)
236 }
237
238 pub fn sort_entries_by_subtree_height(&self, _tree: &ID, subtree: &str, entries: &mut [Entry]) {
247 sorting::sort_entries_by_store_height(subtree, entries)
248 }
249
250 pub async fn is_tip(&self, tree: &ID, entry_id: &ID) -> bool {
261 let inner = self.inner.read().unwrap();
262 storage::is_tip(&inner.entries, tree, entry_id)
263 }
264}
265
266impl Default for InMemory {
267 fn default() -> Self {
268 Self::new()
269 }
270}
271
272#[async_trait]
273impl BackendImpl for InMemory {
274 async fn resolve_store_state(&self, request: &StoreStateRequest) -> Result<Option<RecordView>> {
275 let inner = self.inner.read().unwrap();
276 Ok(inner
277 .store_state_namespaces
278 .iter()
279 .find(|(_, namespace)| {
280 namespace.ready && !namespace.unlinked && namespace.request == *request
281 })
282 .map(|(namespace_id, _)| RecordView {
283 namespace_id: namespace_id.clone(),
284 }))
285 }
286
287 async fn begin_store_state_staging(
288 &self,
289 mut request: StoreStateRequest,
290 ) -> Result<StagingToken> {
291 if request.lifecycle == StoreStateLifecycle::Staging {
292 return Err(BackendError::InvalidStoreStateStagingToken.into());
293 }
294 let target = request.clone();
295 request.lifecycle = StoreStateLifecycle::Staging;
296 let namespace_id = uuid::Uuid::new_v4().to_string();
297 self.inner.write().unwrap().store_state_namespaces.insert(
298 namespace_id.clone(),
299 RecordNamespace {
300 request,
301 ready: false,
302 unlinked: false,
303 records: BTreeMap::new(),
304 },
305 );
306 Ok(StagingToken {
307 namespace_id,
308 target,
309 })
310 }
311
312 async fn stage_store_state_records(
313 &self,
314 token: &StagingToken,
315 records: RecordMutations,
316 ) -> Result<()> {
317 let mut inner = self.inner.write().unwrap();
318 let namespace = inner
319 .store_state_namespaces
320 .get_mut(&token.namespace_id)
321 .ok_or(BackendError::InvalidStoreStateStagingToken)?;
322 if namespace.ready || namespace.request.lifecycle != StoreStateLifecycle::Staging {
323 return Err(BackendError::StoreStateNamespaceImmutable.into());
324 }
325 namespace.records.extend(records);
326 Ok(())
327 }
328
329 async fn publish_store_state(&self, token: StagingToken) -> Result<RecordView> {
330 let mut inner = self.inner.write().unwrap();
331 if inner
335 .store_state_namespaces
336 .get(&token.namespace_id)
337 .is_some_and(|namespace| namespace.ready && namespace.request == token.target)
338 {
339 return Ok(RecordView {
340 namespace_id: token.namespace_id,
341 });
342 }
343 let staged = inner.store_state_namespaces.remove(&token.namespace_id);
344 if let Some((winner_id, _)) = inner
348 .store_state_namespaces
349 .iter()
350 .find(|(_, ready)| ready.ready && !ready.unlinked && ready.request == token.target)
351 {
352 return Ok(RecordView {
353 namespace_id: winner_id.clone(),
354 });
355 }
356 let mut namespace = staged.ok_or(BackendError::InvalidStoreStateStagingToken)?;
357 if namespace.ready {
361 inner
362 .store_state_namespaces
363 .insert(token.namespace_id.clone(), namespace);
364 return Err(BackendError::InvalidStoreStateStagingToken.into());
365 }
366 if namespace.request.lifecycle != StoreStateLifecycle::Staging {
367 return Err(BackendError::InvalidStoreStateStagingToken.into());
368 }
369 if namespace.records.values().any(Option::is_none) {
370 return Err(BackendError::InvalidStoreStateStagingToken.into());
371 }
372 namespace.request = token.target;
373 namespace.ready = true;
374 inner
375 .store_state_namespaces
376 .insert(token.namespace_id.clone(), namespace);
377 Ok(RecordView {
378 namespace_id: token.namespace_id,
379 })
380 }
381
382 async fn abort_store_state(&self, token: StagingToken) -> Result<()> {
383 let mut inner = self.inner.write().unwrap();
384 if inner
385 .store_state_namespaces
386 .get(&token.namespace_id)
387 .is_some_and(|namespace| !namespace.ready)
388 {
389 inner.store_state_namespaces.remove(&token.namespace_id);
390 }
391 Ok(())
392 }
393
394 async fn store_state_record_get(
395 &self,
396 view: &RecordView,
397 key: &[u8],
398 ) -> Result<Option<Vec<u8>>> {
399 self.store_state_point_reads.fetch_add(1, Ordering::Relaxed);
400 let inner = self.inner.read().unwrap();
401 let namespace = inner
402 .store_state_namespaces
403 .get(&view.namespace_id)
404 .filter(|namespace| namespace.ready)
405 .ok_or(BackendError::InvalidStoreStateView)?;
406 Ok(namespace.records.get(key).and_then(Clone::clone))
407 }
408
409 async fn store_state_record_scan(
410 &self,
411 view: &RecordView,
412 range: &RecordRange,
413 after: Option<&[u8]>,
414 limit: usize,
415 ) -> Result<RecordPage> {
416 let inner = self.inner.read().unwrap();
417 let namespace = inner
418 .store_state_namespaces
419 .get(&view.namespace_id)
420 .filter(|namespace| namespace.ready)
421 .ok_or(BackendError::InvalidStoreStateView)?;
422 if limit == 0 {
423 return Ok(RecordPage::default());
424 }
425 self.store_state_scan_reads.fetch_add(1, Ordering::Relaxed);
426 let mut records = namespace
427 .records
428 .iter()
429 .filter(|(key, value)| {
430 value.is_some()
431 && range
432 .start
433 .as_deref()
434 .is_none_or(|start| key.as_slice() >= start)
435 && range.end.as_deref().is_none_or(|end| key.as_slice() < end)
436 && after.is_none_or(|after| key.as_slice() > after)
437 })
438 .take(limit.saturating_add(1))
439 .filter_map(|(key, value)| value.clone().map(|value| (key.clone(), value)))
440 .collect::<Vec<_>>();
441 let has_more = records.len() > limit;
442 records.truncate(limit);
443 let next = if has_more {
444 records.last().map(|record| record.0.clone())
445 } else {
446 None
447 };
448 Ok(RecordPage { records, next })
449 }
450
451 async fn reset_local_verification(&self) -> Result<()> {
452 let mut inner = self.inner.write().unwrap();
453 for status in inner.verification_status.values_mut() {
454 *status = VerificationStatus::Unverified;
455 }
456 inner.store_state_namespaces.retain(|_, namespace| {
460 namespace.request.lifecycle == StoreStateLifecycle::Authoritative
461 });
462 Ok(())
463 }
464
465 async fn clear_derived_store_state(&self) -> Result<()> {
474 let mut inner = self.inner.write().unwrap();
475 inner.store_state_namespaces.retain(|_, namespace| {
476 !(namespace.unlinked && namespace.request.lifecycle == StoreStateLifecycle::Derived)
477 });
478 for namespace in inner.store_state_namespaces.values_mut() {
479 if namespace.ready && namespace.request.lifecycle == StoreStateLifecycle::Derived {
480 namespace.unlinked = true;
481 }
482 }
483 Ok(())
484 }
485
486 async fn get(&self, id: &ID) -> Result<Entry> {
495 let inner = self.inner.read().unwrap();
496 storage::get(&inner, id)
497 }
498
499 async fn get_verification_status(&self, id: &ID) -> Result<VerificationStatus> {
507 let inner = self.inner.read().unwrap();
508 inner
509 .verification_status
510 .get(id)
511 .copied()
512 .ok_or_else(|| BackendError::VerificationStatusNotFound { id: id.clone() }.into())
513 }
514
515 async fn put(&self, entry: Entry) -> Result<()> {
516 entry.validate()?;
518 let mut inner = self.inner.write().unwrap();
519 storage::put(&mut inner, entry)
520 }
521
522 async fn update_verification_status(
534 &self,
535 id: &ID,
536 verification_status: VerificationStatus,
537 ) -> Result<()> {
538 let mut inner = self.inner.write().unwrap();
539 if inner.verification_status.contains_key(id) {
540 inner
541 .verification_status
542 .insert(id.clone(), verification_status);
543 Ok(())
544 } else {
545 Err(BackendError::EntryNotFound { id: id.clone() }.into())
546 }
547 }
548
549 async fn get_entries_by_verification_status(
560 &self,
561 status: VerificationStatus,
562 ) -> Result<Vec<ID>> {
563 let inner = self.inner.read().unwrap();
564 let ids = inner
565 .verification_status
566 .iter()
567 .filter(|&(_, entry_status)| *entry_status == status)
568 .map(|(id, _)| id.clone())
569 .collect();
570 Ok(ids)
571 }
572
573 async fn snapshot(&self, tree: &ID) -> Result<Snapshot> {
574 {
576 let inner = self.inner.read().unwrap();
577 if let Some(cache) = inner.tips.get(tree) {
578 return Ok(Snapshot::new(cache.tree_tips.iter().cloned().collect()));
579 }
580 }
581 let mut inner = self.inner.write().unwrap();
583 traversal::snapshot(&mut inner, tree).map(Snapshot::new)
584 }
585
586 async fn store_snapshot(&self, tree: &ID, subtree: &str) -> Result<Snapshot> {
587 {
589 let inner = self.inner.read().unwrap();
590 if let Some(cache) = inner.tips.get(tree)
591 && let Some(subtree_tips) = cache.subtree_tips.get(subtree)
592 {
593 return Ok(Snapshot::new(subtree_tips.iter().cloned().collect()));
594 }
595 }
596 let mut inner = self.inner.write().unwrap();
598 traversal::store_snapshot(&mut inner, tree, subtree).map(Snapshot::new)
599 }
600
601 async fn store_snapshot_at(
602 &self,
603 tree: &ID,
604 subtree: &str,
605 main_snapshot: &Snapshot,
606 ) -> Result<Snapshot> {
607 let mut inner = self.inner.write().unwrap();
608 traversal::store_snapshot_at(&mut inner, tree, subtree, main_snapshot.tips())
609 .map(Snapshot::new)
610 }
611
612 async fn all_roots(&self) -> Result<Vec<ID>> {
622 let inner = self.inner.read().unwrap();
623 let roots: Vec<ID> = inner
624 .entries
625 .values()
626 .filter(|entry| entry.is_root())
627 .map(|entry| entry.id())
628 .collect();
629 Ok(roots)
630 }
631
632 async fn find_merge_base(
633 &self,
634 tree: &ID,
635 subtree: &str,
636 entry_ids: &[ID],
637 ) -> Result<Option<ID>> {
638 let inner = self.inner.read().unwrap();
639 traversal::find_merge_base(&inner, tree, subtree, entry_ids)
640 }
641
642 fn as_any(&self) -> &dyn Any {
643 self
644 }
645
646 async fn get_tree(&self, tree: &ID) -> Result<Vec<Entry>> {
647 let inner = self.inner.read().unwrap();
648 storage::get_tree(&inner, tree)
649 }
650
651 async fn get_store(&self, tree: &ID, subtree: &str) -> Result<Vec<Entry>> {
652 let inner = self.inner.read().unwrap();
653 storage::get_store(&inner, tree, subtree)
654 }
655
656 async fn get_tree_from_tips(&self, tree: &ID, tips: &[ID]) -> Result<Vec<Entry>> {
657 let inner = self.inner.read().unwrap();
658 storage::get_tree_from_tips(&inner, tree, tips)
659 }
660
661 async fn store_at(&self, tree: &ID, subtree: &str, snapshot: &Snapshot) -> Result<Vec<Entry>> {
662 #[cfg(feature = "testing")]
663 self.store_history_reads.fetch_add(1, Ordering::Relaxed);
664 let inner = self.inner.read().unwrap();
665 storage::store_at(&inner, tree, subtree, snapshot.tips())
666 }
667
668 async fn get_instance_metadata(&self) -> Result<Option<InstanceMetadata>> {
669 let inner = self.inner.read().unwrap();
670 Ok(inner.instance_metadata.clone())
671 }
672
673 async fn set_instance_metadata(&self, metadata: &InstanceMetadata) -> Result<()> {
674 let mut inner = self.inner.write().unwrap();
675 inner.instance_metadata = Some(metadata.clone());
676 Ok(())
677 }
678
679 async fn get_instance_secrets(&self) -> Result<Option<InstanceSecrets>> {
680 let inner = self.inner.read().unwrap();
681 Ok(inner.instance_secrets.clone())
682 }
683
684 async fn set_instance_secrets(&self, secrets: &InstanceSecrets) -> Result<()> {
685 let mut inner = self.inner.write().unwrap();
686 inner.instance_secrets = Some(secrets.clone());
687 Ok(())
688 }
689
690 async fn get_sorted_store_parents(
691 &self,
692 tree_id: &ID,
693 entry_id: &ID,
694 subtree: &str,
695 ) -> Result<Vec<ID>> {
696 let inner = self.inner.read().unwrap();
697 traversal::get_sorted_store_parents(&inner, tree_id, entry_id, subtree)
698 }
699
700 async fn get_path_from_to(
701 &self,
702 tree_id: &ID,
703 subtree: &str,
704 from_id: Option<&ID>,
705 to_ids: &[ID],
706 ) -> Result<Vec<ID>> {
707 let inner = self.inner.read().unwrap();
708 traversal::get_path_from_to(&inner, tree_id, subtree, from_id, to_ids)
709 }
710}
711
712#[cfg(test)]
721mod store_state_token_tests {
722 use std::collections::BTreeMap;
723
724 use super::InMemory;
725 use crate::backend::{
726 BackendImpl, CacheScope, ProjectionDescriptor, StagingToken, StoreStateLifecycle,
727 StoreStateRequest,
728 };
729 use crate::entry::ID;
730
731 fn request(store: &str) -> StoreStateRequest {
732 StoreStateRequest {
733 database: ID::from_bytes("db"),
734 store: store.to_string(),
735 lifecycle: StoreStateLifecycle::Derived,
736 scope: CacheScope::Shared,
737 projection: ProjectionDescriptor {
738 name: "test/opaque".to_string(),
739 version: 0,
740 },
741 source_key: b"snapshot".to_vec(),
742 }
743 }
744
745 #[tokio::test]
746 async fn mismatched_target_publish_preserves_ready_namespace() {
747 let backend = InMemory::new();
748
749 let request_a = request("store-a");
751 let token_a = backend
752 .begin_store_state_staging(request_a.clone())
753 .await
754 .unwrap();
755 backend
756 .stage_store_state_records(
757 &token_a,
758 BTreeMap::from([(b"key".to_vec(), Some(b"value-a".to_vec()))]),
759 )
760 .await
761 .unwrap();
762 let view_a = backend.publish_store_state(token_a).await.unwrap();
763
764 let request_b = request("store-b");
766 let token_b = backend
767 .begin_store_state_staging(request_b.clone())
768 .await
769 .unwrap();
770 backend
771 .stage_store_state_records(
772 &token_b,
773 BTreeMap::from([(b"key".to_vec(), Some(b"value-b".to_vec()))]),
774 )
775 .await
776 .unwrap();
777
778 let bad = StagingToken {
781 namespace_id: token_b.namespace_id.clone(),
782 target: request_a.clone(),
783 };
784 let adopted = backend.publish_store_state(bad).await.unwrap();
785 assert_eq!(adopted, view_a);
786 assert_eq!(
787 backend
788 .store_state_record_get(&view_a, b"key")
789 .await
790 .unwrap(),
791 Some(b"value-a".to_vec())
792 );
793 assert_eq!(backend.resolve_store_state(&request_b).await.unwrap(), None);
794
795 let nowhere = request("store-nowhere");
799 let bad_ready = StagingToken {
800 namespace_id: view_a.namespace_id.clone(),
801 target: nowhere.clone(),
802 };
803 assert!(backend.publish_store_state(bad_ready).await.is_err());
804 assert_eq!(
805 backend.resolve_store_state(&request_a).await.unwrap(),
806 Some(view_a.clone())
807 );
808 assert_eq!(
809 backend
810 .store_state_record_get(&view_a, b"key")
811 .await
812 .unwrap(),
813 Some(b"value-a".to_vec())
814 );
815 assert_eq!(backend.resolve_store_state(&nowhere).await.unwrap(), None);
816 }
817}