Skip to main content

arms/adapters/storage/
memory.rs

1//! # Memory Storage Adapter
2//!
3//! In-memory storage using HashMap.
4//! Fast, but volatile (data lost on shutdown).
5//!
6//! Good for:
7//! - Testing
8//! - Hot tier storage
9//! - Small datasets
10
11use std::collections::HashMap;
12
13use crate::core::{Blob, Id, PlacedPoint, Point};
14use crate::ports::{Place, PlaceError, PlaceResult};
15
16/// In-memory storage adapter
17pub struct MemoryStorage {
18    /// The stored points
19    points: HashMap<Id, PlacedPoint>,
20
21    /// Expected dimensionality
22    dimensionality: usize,
23
24    /// Maximum capacity in bytes (0 = unlimited)
25    capacity: usize,
26
27    /// Current size in bytes
28    current_size: usize,
29}
30
31impl MemoryStorage {
32    /// Create a new memory storage with specified dimensionality
33    pub fn new(dimensionality: usize) -> Self {
34        Self {
35            points: HashMap::new(),
36            dimensionality,
37            capacity: 0,
38            current_size: 0,
39        }
40    }
41
42    /// Create with a capacity limit
43    pub fn with_capacity(dimensionality: usize, capacity: usize) -> Self {
44        Self {
45            points: HashMap::new(),
46            dimensionality,
47            capacity,
48            current_size: 0,
49        }
50    }
51
52    /// Calculate size of a placed point in bytes
53    fn point_size(point: &PlacedPoint) -> usize {
54        // Id: 16 bytes
55        // Point: dims.len() * 4 bytes (f32)
56        // Blob: data.len() bytes
57        // Overhead: ~48 bytes for struct padding and HashMap entry
58        16 + (point.point.dimensionality() * 4) + point.blob.size() + 48
59    }
60}
61
62impl Place for MemoryStorage {
63    fn place(&mut self, point: Point, blob: Blob) -> PlaceResult<Id> {
64        // Check dimensionality
65        if point.dimensionality() != self.dimensionality {
66            return Err(PlaceError::DimensionalityMismatch {
67                expected: self.dimensionality,
68                got: point.dimensionality(),
69            });
70        }
71
72        let id = Id::now();
73        let placed = PlacedPoint::new(id, point, blob);
74
75        // Check capacity
76        let size = Self::point_size(&placed);
77        if self.capacity > 0 && self.current_size + size > self.capacity {
78            return Err(PlaceError::CapacityExceeded);
79        }
80
81        self.current_size += size;
82        self.points.insert(id, placed);
83
84        Ok(id)
85    }
86
87    fn place_with_id(&mut self, id: Id, point: Point, blob: Blob) -> PlaceResult<()> {
88        // Check dimensionality
89        if point.dimensionality() != self.dimensionality {
90            return Err(PlaceError::DimensionalityMismatch {
91                expected: self.dimensionality,
92                got: point.dimensionality(),
93            });
94        }
95
96        // Check for duplicates
97        if self.points.contains_key(&id) {
98            return Err(PlaceError::DuplicateId(id));
99        }
100
101        let placed = PlacedPoint::new(id, point, blob);
102
103        // Check capacity
104        let size = Self::point_size(&placed);
105        if self.capacity > 0 && self.current_size + size > self.capacity {
106            return Err(PlaceError::CapacityExceeded);
107        }
108
109        self.current_size += size;
110        self.points.insert(id, placed);
111
112        Ok(())
113    }
114
115    fn remove(&mut self, id: Id) -> Option<PlacedPoint> {
116        if let Some(placed) = self.points.remove(&id) {
117            self.current_size -= Self::point_size(&placed);
118            Some(placed)
119        } else {
120            None
121        }
122    }
123
124    fn get(&self, id: Id) -> Option<&PlacedPoint> {
125        self.points.get(&id)
126    }
127
128    fn len(&self) -> usize {
129        self.points.len()
130    }
131
132    fn iter(&self) -> Box<dyn Iterator<Item = &PlacedPoint> + '_> {
133        Box::new(self.points.values())
134    }
135
136    fn size_bytes(&self) -> usize {
137        self.current_size
138    }
139
140    fn clear(&mut self) {
141        self.points.clear();
142        self.current_size = 0;
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_memory_storage_place() {
152        let mut storage = MemoryStorage::new(3);
153
154        let point = Point::new(vec![1.0, 2.0, 3.0]);
155        let blob = Blob::from_str("test");
156
157        let id = storage.place(point, blob).unwrap();
158
159        assert_eq!(storage.len(), 1);
160        assert!(storage.contains(id));
161    }
162
163    #[test]
164    fn test_memory_storage_get() {
165        let mut storage = MemoryStorage::new(3);
166
167        let point = Point::new(vec![1.0, 2.0, 3.0]);
168        let blob = Blob::from_str("hello");
169
170        let id = storage.place(point, blob).unwrap();
171
172        let retrieved = storage.get(id).unwrap();
173        assert_eq!(retrieved.blob.as_str(), Some("hello"));
174    }
175
176    #[test]
177    fn test_memory_storage_remove() {
178        let mut storage = MemoryStorage::new(3);
179
180        let point = Point::new(vec![1.0, 2.0, 3.0]);
181        let id = storage.place(point, Blob::empty()).unwrap();
182
183        assert_eq!(storage.len(), 1);
184
185        let removed = storage.remove(id);
186        assert!(removed.is_some());
187        assert_eq!(storage.len(), 0);
188        assert!(!storage.contains(id));
189    }
190
191    #[test]
192    fn test_memory_storage_dimensionality_check() {
193        let mut storage = MemoryStorage::new(3);
194
195        let wrong_dims = Point::new(vec![1.0, 2.0]); // 2 dims, expected 3
196
197        let result = storage.place(wrong_dims, Blob::empty());
198
199        match result {
200            Err(PlaceError::DimensionalityMismatch { expected, got }) => {
201                assert_eq!(expected, 3);
202                assert_eq!(got, 2);
203            }
204            _ => panic!("Expected DimensionalityMismatch error"),
205        }
206    }
207
208    #[test]
209    fn test_memory_storage_capacity() {
210        // Small capacity - enough for one point but not two
211        // Point size: 16 (id) + 12 (3 f32s) + 10 (blob) + 48 (overhead) = 86 bytes
212        let mut storage = MemoryStorage::with_capacity(3, 150);
213
214        let point = Point::new(vec![1.0, 2.0, 3.0]);
215        let blob = Blob::new(vec![0u8; 10]); // Small blob
216
217        // First one should succeed
218        storage.place(point.clone(), blob.clone()).unwrap();
219
220        // Second should fail due to capacity
221        let result = storage.place(point, blob);
222        assert!(matches!(result, Err(PlaceError::CapacityExceeded)));
223    }
224
225    #[test]
226    fn test_memory_storage_clear() {
227        let mut storage = MemoryStorage::new(3);
228
229        for i in 0..10 {
230            let point = Point::new(vec![i as f32, 0.0, 0.0]);
231            storage.place(point, Blob::empty()).unwrap();
232        }
233
234        assert_eq!(storage.len(), 10);
235        assert!(storage.size_bytes() > 0);
236
237        storage.clear();
238
239        assert_eq!(storage.len(), 0);
240        assert_eq!(storage.size_bytes(), 0);
241    }
242
243    #[test]
244    fn test_memory_storage_iter() {
245        let mut storage = MemoryStorage::new(2);
246
247        storage.place(Point::new(vec![1.0, 0.0]), Blob::empty()).unwrap();
248        storage.place(Point::new(vec![0.0, 1.0]), Blob::empty()).unwrap();
249
250        let points: Vec<_> = storage.iter().collect();
251        assert_eq!(points.len(), 2);
252    }
253}