Skip to main content

memoir_core/graph/
memory.rs

1use std::collections::HashMap;
2use std::sync::Mutex;
3
4use super::{GraphError, GraphParam, GraphRows, GraphStore};
5
6/// In-memory [`GraphStore`] for tests and benchmarks, with no live backend.
7///
8/// This is the trait's test/benchmark boundary, letting callers exercise the
9/// graph seam without a running FalkorDB. It does not interpret Cypher — there
10/// is no graph engine here — so [`GraphStore::query`] returns whatever rows
11/// were staged via [`InMemoryGraphStore::stage_rows`].
12///
13/// # Examples
14///
15/// ```
16/// use memoir_core::graph::{GraphStore, InMemoryGraphStore};
17///
18/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
19/// let store = InMemoryGraphStore::new();
20/// store.ensure_graph().await?;
21/// store.stage_rows(vec![vec![("n".to_string(), "Alice".to_string())]]);
22/// let rows = store.query("MATCH (n) RETURN n", &Default::default()).await?;
23/// assert_eq!(rows[0][0].1, "Alice");
24/// # Ok(())
25/// # }
26/// ```
27#[derive(Default)]
28pub struct InMemoryGraphStore {
29    rows: Mutex<GraphRows>,
30}
31
32impl InMemoryGraphStore {
33    /// Creates an empty in-memory graph store.
34    pub fn new() -> Self {
35        Self::default()
36    }
37
38    /// Stages the rows that the next [`GraphStore::query`] call returns.
39    ///
40    /// A test affordance: since there is no Cypher engine, this lets a test
41    /// arrange the result a query observes. Replaces any previously staged rows.
42    pub fn stage_rows(&self, rows: GraphRows) {
43        *self.rows.lock().expect("graph store mutex poisoned") = rows;
44    }
45}
46
47impl GraphStore for InMemoryGraphStore {
48    async fn ensure_graph(&self) -> Result<(), GraphError> {
49        Ok(())
50    }
51
52    async fn query(&self, _cypher: &str, _params: &HashMap<String, GraphParam>) -> Result<GraphRows, GraphError> {
53        Ok(self.rows.lock().expect("graph store mutex poisoned").clone())
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[tokio::test(flavor = "current_thread")]
62    async fn should_return_staged_rows_from_query() {
63        let store = InMemoryGraphStore::new();
64        store.ensure_graph().await.unwrap();
65        store.stage_rows(vec![vec![("n".to_string(), "Alice".to_string())]]);
66
67        let rows = store.query("MATCH (n) RETURN n", &HashMap::new()).await.unwrap();
68
69        assert_eq!(rows.len(), 1);
70        assert_eq!(rows[0][0], ("n".to_string(), "Alice".to_string()));
71    }
72
73    #[tokio::test(flavor = "current_thread")]
74    async fn should_return_empty_when_nothing_staged() {
75        let store = InMemoryGraphStore::new();
76        let rows = store.query("MATCH (n) RETURN n", &HashMap::new()).await.unwrap();
77        assert!(rows.is_empty());
78    }
79}