Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica

eidetica/backend/database/in_memory/
mod.rs

1//! In-memory database backend implementation
2//!
3//! This module provides an in-memory implementation of the Database trait,
4//! suitable for testing, development, or scenarios where data persistence
5//! is not strictly required or is handled externally.
6
7mod cache;
8mod persistence;
9mod storage;
10mod traversal;
11
12use std::{
13    any::Any,
14    collections::{HashMap, HashSet},
15    path::Path,
16    sync::RwLock,
17};
18
19use async_trait::async_trait;
20use serde::{Deserialize, Serialize};
21
22use crate::{
23    Result,
24    backend::{BackendImpl, InstanceMetadata, VerificationStatus, errors::BackendError},
25    entry::{Entry, ID},
26};
27
28/// Grouped tree tips cache: (tree_tips, subtree_name -> subtree_tips)
29#[derive(Debug, Clone, Default, Serialize, Deserialize)]
30pub(crate) struct TreeTipsCache {
31    pub(crate) tree_tips: HashSet<ID>,
32    pub(crate) subtree_tips: HashMap<String, HashSet<ID>>,
33}
34
35/// Core data protected by a single lock.
36///
37/// All fields that participate in entry storage and tip tracking are grouped
38/// together to eliminate lock ordering concerns. A single `RwLock` on the
39/// outer `InMemory` struct protects all fields atomically.
40#[derive(Debug)]
41pub(crate) struct InMemoryInner {
42    pub(crate) entries: HashMap<ID, Entry>,
43    pub(crate) verification_status: HashMap<ID, VerificationStatus>,
44    /// Instance metadata containing device key and system database IDs.
45    ///
46    /// When `None`, the backend is uninitialized. When `Some`, contains the
47    /// device key and root IDs for system databases.
48    ///
49    /// **Security Warning**: The device key is stored in memory without encryption.
50    /// This is suitable for development/testing only. Production systems should use
51    /// proper key management with encryption at rest.
52    pub(crate) instance_metadata: Option<InstanceMetadata>,
53    /// Cached tips grouped by tree: tree_id -> (tree_tips, subtree_name -> subtree_tips)
54    pub(crate) tips: HashMap<ID, TreeTipsCache>,
55}
56
57/// A simple in-memory database implementation using a `HashMap` for storage.
58///
59/// This database is suitable for testing, development, or scenarios where
60/// data persistence is not strictly required or is handled externally
61/// (e.g., by saving/loading the entire state to/from a file).
62///
63/// It provides basic persistence capabilities via `save_to_file` and
64/// `load_from_file`, serializing the `HashMap` to JSON.
65///
66/// **Security Note**: The device key is stored in memory in plaintext in this implementation.
67/// This is acceptable for development and testing but should not be used in production
68/// without proper encryption or hardware security module integration.
69#[derive(Debug)]
70pub struct InMemory {
71    /// Core data protected by a single lock for atomic access and
72    /// to eliminate lock ordering concerns between entries, verification
73    /// status, and tips.
74    pub(crate) inner: RwLock<InMemoryInner>,
75    /// CRDT state cache, independent of core data.
76    /// Kept separate because it has no coupling to entries/tips lifecycle.
77    pub(crate) crdt_cache: RwLock<HashMap<String, String>>,
78}
79
80impl InMemory {
81    /// Creates a new, empty `InMemory` database.
82    pub fn new() -> Self {
83        Self {
84            inner: RwLock::new(InMemoryInner {
85                entries: HashMap::new(),
86                verification_status: HashMap::new(),
87                instance_metadata: None,
88                tips: HashMap::new(),
89            }),
90            crdt_cache: RwLock::new(HashMap::new()),
91        }
92    }
93
94    /// Returns a vector containing the IDs of all entries currently stored in the database.
95    pub async fn all_ids(&self) -> Vec<ID> {
96        let inner = self.inner.read().unwrap();
97        inner.entries.keys().cloned().collect()
98    }
99
100    /// Saves the entire database state (all entries) to a specified file as JSON.
101    ///
102    /// # Arguments
103    /// * `path` - The path to the file where the state should be saved.
104    ///
105    /// # Returns
106    /// A `Result` indicating success or an I/O or serialization error.
107    pub async fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
108        persistence::save_to_file(self, path)
109    }
110
111    /// Loads the database state from a specified JSON file.
112    ///
113    /// If the file does not exist, a new, empty `InMemory` database is returned.
114    ///
115    /// # Arguments
116    /// * `path` - The path to the file from which to load the state.
117    ///
118    /// # Returns
119    /// A `Result` containing the loaded `InMemory` database or an I/O or deserialization error.
120    pub async fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
121        persistence::load_from_file(path)
122    }
123
124    /// Sort entries by their height within a tree (exposed for testing)
125    ///
126    /// Heights are stored directly in entries, so this just reads and sorts.
127    ///
128    /// # Arguments
129    /// * `_tree` - The ID of the tree context (unused, kept for API compatibility)
130    /// * `entries` - The vector of entries to be sorted in place
131    pub fn sort_entries_by_height(&self, _tree: &ID, entries: &mut [Entry]) {
132        cache::sort_entries_by_height(entries)
133    }
134
135    /// Sort entries by their height within a subtree (exposed for testing)
136    ///
137    /// Heights are stored directly in entries, so this just reads and sorts.
138    ///
139    /// # Arguments
140    /// * `_tree` - The ID of the tree context (unused, kept for API compatibility)
141    /// * `subtree` - The name of the subtree context
142    /// * `entries` - The vector of entries to be sorted in place
143    pub fn sort_entries_by_subtree_height(&self, _tree: &ID, subtree: &str, entries: &mut [Entry]) {
144        cache::sort_entries_by_subtree_height(subtree, entries)
145    }
146
147    /// Check if an entry is a tip within its tree (exposed for benchmarks)
148    ///
149    /// An entry is a tip if no other entry in the same tree lists it as a parent.
150    ///
151    /// # Arguments
152    /// * `tree` - The ID of the tree to check within
153    /// * `entry_id` - The ID of the entry to check
154    ///
155    /// # Returns
156    /// `true` if the entry is a tip, `false` otherwise
157    pub async fn is_tip(&self, tree: &ID, entry_id: &ID) -> bool {
158        let inner = self.inner.read().unwrap();
159        storage::is_tip(&inner.entries, tree, entry_id)
160    }
161}
162
163impl Default for InMemory {
164    fn default() -> Self {
165        Self::new()
166    }
167}
168
169#[async_trait]
170impl BackendImpl for InMemory {
171    /// Retrieves an entry by its unique content-addressable ID.
172    ///
173    /// # Arguments
174    /// * `id` - The ID of the entry to retrieve.
175    ///
176    /// # Returns
177    /// A `Result` containing the `Entry` if found, or a `DatabaseError::EntryNotFound` otherwise.
178    /// Returns an owned copy to support concurrent access with internal synchronization.
179    async fn get(&self, id: &ID) -> Result<Entry> {
180        let inner = self.inner.read().unwrap();
181        storage::get(&inner, id)
182    }
183
184    /// Gets the verification status of an entry.
185    ///
186    /// # Arguments
187    /// * `id` - The ID of the entry to check.
188    ///
189    /// # Returns
190    /// A `Result` containing the `VerificationStatus` if the entry exists, or a `DatabaseError::VerificationStatusNotFound` otherwise.
191    async fn get_verification_status(&self, id: &ID) -> Result<VerificationStatus> {
192        let inner = self.inner.read().unwrap();
193        inner
194            .verification_status
195            .get(id)
196            .copied()
197            .ok_or_else(|| BackendError::VerificationStatusNotFound { id: id.clone() }.into())
198    }
199
200    async fn put(&self, verification_status: VerificationStatus, entry: Entry) -> Result<()> {
201        // Validate before acquiring write lock to fail fast
202        entry.validate()?;
203        let mut inner = self.inner.write().unwrap();
204        storage::put(&mut inner, verification_status, entry)
205    }
206
207    /// Updates the verification status of an existing entry.
208    ///
209    /// This allows the authentication system to mark entries as verified or failed
210    /// after they have been stored. Useful for batch verification operations.
211    ///
212    /// # Arguments
213    /// * `id` - The ID of the entry to update
214    /// * `verification_status` - The new verification status
215    ///
216    /// # Returns
217    /// A `Result` indicating success or `DatabaseError::EntryNotFound` if the entry doesn't exist.
218    async fn update_verification_status(
219        &self,
220        id: &ID,
221        verification_status: VerificationStatus,
222    ) -> Result<()> {
223        let mut inner = self.inner.write().unwrap();
224        if inner.verification_status.contains_key(id) {
225            inner
226                .verification_status
227                .insert(id.clone(), verification_status);
228            Ok(())
229        } else {
230            Err(BackendError::EntryNotFound { id: id.clone() }.into())
231        }
232    }
233
234    /// Gets all entries with a specific verification status.
235    ///
236    /// This is useful for finding unverified entries that need authentication
237    /// or for security audits.
238    ///
239    /// # Arguments
240    /// * `status` - The verification status to filter by
241    ///
242    /// # Returns
243    /// A `Result` containing a vector of entry IDs with the specified status.
244    async fn get_entries_by_verification_status(
245        &self,
246        status: VerificationStatus,
247    ) -> Result<Vec<ID>> {
248        let inner = self.inner.read().unwrap();
249        let ids = inner
250            .verification_status
251            .iter()
252            .filter(|&(_, entry_status)| *entry_status == status)
253            .map(|(id, _)| id.clone())
254            .collect();
255        Ok(ids)
256    }
257
258    async fn get_tips(&self, tree: &ID) -> Result<Vec<ID>> {
259        // Fast path: check cache with read lock
260        {
261            let inner = self.inner.read().unwrap();
262            if let Some(cache) = inner.tips.get(tree) {
263                return Ok(cache.tree_tips.iter().cloned().collect());
264            }
265        }
266        // Slow path: compute and cache with write lock
267        let mut inner = self.inner.write().unwrap();
268        traversal::get_tips(&mut inner, tree)
269    }
270
271    async fn get_store_tips(&self, tree: &ID, subtree: &str) -> Result<Vec<ID>> {
272        // Fast path: check cache with read lock
273        {
274            let inner = self.inner.read().unwrap();
275            if let Some(cache) = inner.tips.get(tree)
276                && let Some(subtree_tips) = cache.subtree_tips.get(subtree)
277            {
278                return Ok(subtree_tips.iter().cloned().collect());
279            }
280        }
281        // Slow path: compute and cache with write lock
282        let mut inner = self.inner.write().unwrap();
283        traversal::get_store_tips(&mut inner, tree, subtree)
284    }
285
286    async fn get_store_tips_up_to_entries(
287        &self,
288        tree: &ID,
289        subtree: &str,
290        main_entries: &[ID],
291    ) -> Result<Vec<ID>> {
292        let mut inner = self.inner.write().unwrap();
293        traversal::get_store_tips_up_to_entries(&mut inner, tree, subtree, main_entries)
294    }
295
296    /// Retrieves the IDs of all top-level root entries stored in the database.
297    ///
298    /// Top-level roots are entries that are themselves roots of a tree
299    /// (i.e., `entry.is_root()` is true) and are not part of a larger tree structure
300    /// tracked by the backend. These represent the starting points
301    /// of distinct trees managed by the database.
302    ///
303    /// # Returns
304    /// A `Result` containing a vector of top-level root entry IDs or an error.
305    async fn all_roots(&self) -> Result<Vec<ID>> {
306        let inner = self.inner.read().unwrap();
307        let roots: Vec<ID> = inner
308            .entries
309            .values()
310            .filter(|entry| entry.is_root())
311            .map(|entry| entry.id())
312            .collect();
313        Ok(roots)
314    }
315
316    async fn find_merge_base(&self, tree: &ID, subtree: &str, entry_ids: &[ID]) -> Result<ID> {
317        let inner = self.inner.read().unwrap();
318        traversal::find_merge_base(&inner, tree, subtree, entry_ids)
319    }
320
321    async fn collect_root_to_target(
322        &self,
323        tree: &ID,
324        subtree: &str,
325        target_entry: &ID,
326    ) -> Result<Vec<ID>> {
327        let inner = self.inner.read().unwrap();
328        traversal::collect_root_to_target(&inner, tree, subtree, target_entry)
329    }
330
331    fn as_any(&self) -> &dyn Any {
332        self
333    }
334
335    async fn get_tree(&self, tree: &ID) -> Result<Vec<Entry>> {
336        let inner = self.inner.read().unwrap();
337        storage::get_tree(&inner, tree)
338    }
339
340    async fn get_store(&self, tree: &ID, subtree: &str) -> Result<Vec<Entry>> {
341        let inner = self.inner.read().unwrap();
342        storage::get_store(&inner, tree, subtree)
343    }
344
345    async fn get_tree_from_tips(&self, tree: &ID, tips: &[ID]) -> Result<Vec<Entry>> {
346        let inner = self.inner.read().unwrap();
347        storage::get_tree_from_tips(&inner, tree, tips)
348    }
349
350    async fn get_store_from_tips(
351        &self,
352        tree: &ID,
353        subtree: &str,
354        tips: &[ID],
355    ) -> Result<Vec<Entry>> {
356        let inner = self.inner.read().unwrap();
357        storage::get_store_from_tips(&inner, tree, subtree, tips)
358    }
359
360    async fn get_instance_metadata(&self) -> Result<Option<InstanceMetadata>> {
361        let inner = self.inner.read().unwrap();
362        Ok(inner.instance_metadata.clone())
363    }
364
365    async fn set_instance_metadata(&self, metadata: &InstanceMetadata) -> Result<()> {
366        let mut inner = self.inner.write().unwrap();
367        inner.instance_metadata = Some(metadata.clone());
368        Ok(())
369    }
370
371    async fn get_cached_crdt_state(&self, entry_id: &ID, subtree: &str) -> Result<Option<String>> {
372        cache::get_cached_crdt_state(self, entry_id, subtree)
373    }
374
375    async fn cache_crdt_state(&self, entry_id: &ID, subtree: &str, state: String) -> Result<()> {
376        cache::cache_crdt_state(self, entry_id, subtree, state)
377    }
378
379    async fn clear_crdt_cache(&self) -> Result<()> {
380        cache::clear_crdt_cache(self)
381    }
382
383    async fn get_sorted_store_parents(
384        &self,
385        tree_id: &ID,
386        entry_id: &ID,
387        subtree: &str,
388    ) -> Result<Vec<ID>> {
389        let inner = self.inner.read().unwrap();
390        traversal::get_sorted_store_parents(&inner, tree_id, entry_id, subtree)
391    }
392
393    async fn get_path_from_to(
394        &self,
395        tree_id: &ID,
396        subtree: &str,
397        from_id: &ID,
398        to_ids: &[ID],
399    ) -> Result<Vec<ID>> {
400        let inner = self.inner.read().unwrap();
401        traversal::get_path_from_to(&inner, tree_id, subtree, from_id, to_ids)
402    }
403}