Skip to main content

mentedb_index/
temporal.rs

1//! BTreeMap-based temporal index for timestamp range queries.
2
3use std::collections::BTreeMap;
4
5use ahash::HashMap;
6use parking_lot::RwLock;
7use serde::{Deserialize, Serialize};
8
9use mentedb_core::error::{MenteError, MenteResult};
10use mentedb_core::types::{MemoryId, Timestamp};
11
12/// Temporal index using a BTreeMap for efficient range queries on timestamps.
13pub struct TemporalIndex {
14    inner: RwLock<TemporalInner>,
15}
16
17struct TemporalInner {
18    /// Timestamp → set of MemoryIds at that timestamp.
19    tree: BTreeMap<Timestamp, Vec<MemoryId>>,
20    /// Reverse lookup for fast removal.
21    id_to_ts: HashMap<MemoryId, Timestamp>,
22}
23
24impl TemporalIndex {
25    /// Creates a new empty temporal index.
26    pub fn new() -> Self {
27        Self {
28            inner: RwLock::new(TemporalInner {
29                tree: BTreeMap::new(),
30                id_to_ts: HashMap::default(),
31            }),
32        }
33    }
34
35    /// Insert a memory with its timestamp.
36    pub fn insert(&self, id: MemoryId, timestamp: Timestamp) {
37        let mut inner = self.inner.write();
38        inner.tree.entry(timestamp).or_default().push(id);
39        inner.id_to_ts.insert(id, timestamp);
40    }
41
42    /// Query memories within [start, end] inclusive.
43    pub fn range(&self, start: Timestamp, end: Timestamp) -> Vec<MemoryId> {
44        let inner = self.inner.read();
45        inner
46            .tree
47            .range(start..=end)
48            .flat_map(|(_, ids)| ids.iter().copied())
49            .collect()
50    }
51
52    /// Get the `n` most recent memories (by timestamp, descending).
53    pub fn latest(&self, n: usize) -> Vec<MemoryId> {
54        let inner = self.inner.read();
55        let mut results = Vec::with_capacity(n);
56        for (_, ids) in inner.tree.iter().rev() {
57            for &id in ids.iter().rev() {
58                results.push(id);
59                if results.len() >= n {
60                    return results;
61                }
62            }
63        }
64        results
65    }
66
67    /// Remove a memory by id and its known timestamp.
68    pub fn remove(&self, id: MemoryId, timestamp: Timestamp) {
69        let mut inner = self.inner.write();
70        if let Some(ids) = inner.tree.get_mut(&timestamp) {
71            ids.retain(|&i| i != id);
72            if ids.is_empty() {
73                inner.tree.remove(&timestamp);
74            }
75        }
76        inner.id_to_ts.remove(&id);
77    }
78
79    /// Remove a memory by id (looks up the timestamp internally).
80    pub fn remove_by_id(&self, id: MemoryId) {
81        let mut inner = self.inner.write();
82        if let Some(ts) = inner.id_to_ts.remove(&id)
83            && let Some(ids) = inner.tree.get_mut(&ts)
84        {
85            ids.retain(|&i| i != id);
86            if ids.is_empty() {
87                inner.tree.remove(&ts);
88            }
89        }
90    }
91
92    /// Get the timestamp for a given memory id (if indexed).
93    pub fn get_timestamp(&self, id: MemoryId) -> Option<Timestamp> {
94        let inner = self.inner.read();
95        inner.id_to_ts.get(&id).copied()
96    }
97}
98
99/// Serializable snapshot of the temporal index data.
100#[derive(Serialize, Deserialize)]
101struct TemporalSnapshot {
102    tree: Vec<(Timestamp, Vec<MemoryId>)>,
103    id_to_ts: Vec<(MemoryId, Timestamp)>,
104}
105
106impl TemporalIndex {
107    /// Save the temporal index to a JSON file.
108    pub fn save(&self, path: &std::path::Path) -> MenteResult<()> {
109        let inner = self.inner.read();
110        let snapshot = TemporalSnapshot {
111            tree: inner.tree.iter().map(|(&k, v)| (k, v.clone())).collect(),
112            id_to_ts: inner.id_to_ts.iter().map(|(&k, &v)| (k, v)).collect(),
113        };
114        let data =
115            serde_json::to_vec(&snapshot).map_err(|e| MenteError::Serialization(e.to_string()))?;
116        std::fs::write(path, data)?;
117        Ok(())
118    }
119
120    /// Load the temporal index from a JSON file.
121    pub fn load(path: &std::path::Path) -> MenteResult<Self> {
122        let data = std::fs::read(path)?;
123        let snapshot: TemporalSnapshot =
124            serde_json::from_slice(&data).map_err(|e| MenteError::Serialization(e.to_string()))?;
125
126        let mut tree = BTreeMap::new();
127        for (ts, ids) in snapshot.tree {
128            tree.insert(ts, ids);
129        }
130
131        let mut id_to_ts = HashMap::default();
132        for (id, ts) in snapshot.id_to_ts {
133            id_to_ts.insert(id, ts);
134        }
135
136        Ok(Self {
137            inner: RwLock::new(TemporalInner { tree, id_to_ts }),
138        })
139    }
140}
141
142impl Default for TemporalIndex {
143    fn default() -> Self {
144        Self::new()
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn test_insert_and_range() {
154        let idx = TemporalIndex::new();
155        let a = MemoryId::new();
156        let b = MemoryId::new();
157        let c = MemoryId::new();
158
159        idx.insert(a, 100);
160        idx.insert(b, 200);
161        idx.insert(c, 300);
162
163        let result = idx.range(100, 200);
164        assert_eq!(result.len(), 2);
165        assert!(result.contains(&a));
166        assert!(result.contains(&b));
167    }
168
169    #[test]
170    fn test_latest() {
171        let idx = TemporalIndex::new();
172        let a = MemoryId::new();
173        let b = MemoryId::new();
174        let c = MemoryId::new();
175
176        idx.insert(a, 100);
177        idx.insert(b, 200);
178        idx.insert(c, 300);
179
180        let latest = idx.latest(2);
181        assert_eq!(latest.len(), 2);
182        assert_eq!(latest[0], c);
183        assert_eq!(latest[1], b);
184    }
185
186    #[test]
187    fn test_remove() {
188        let idx = TemporalIndex::new();
189        let a = MemoryId::new();
190        idx.insert(a, 100);
191        idx.remove(a, 100);
192
193        assert!(idx.range(0, 1000).is_empty());
194    }
195
196    #[test]
197    fn test_remove_by_id() {
198        let idx = TemporalIndex::new();
199        let a = MemoryId::new();
200        idx.insert(a, 500);
201        idx.remove_by_id(a);
202
203        assert!(idx.range(0, 1000).is_empty());
204    }
205
206    #[test]
207    fn test_empty_range() {
208        let idx = TemporalIndex::new();
209        assert!(idx.range(0, 100).is_empty());
210    }
211}