ruvector_snapshot/
manager.rs1use crate::error::{Result, SnapshotError};
2use crate::snapshot::{Snapshot, SnapshotData};
3use crate::storage::SnapshotStorage;
4
5pub struct SnapshotManager {
7 storage: Box<dyn SnapshotStorage>,
8}
9
10impl SnapshotManager {
11 pub fn new(storage: Box<dyn SnapshotStorage>) -> Self {
13 Self { storage }
14 }
15
16 pub async fn create_snapshot(&self, snapshot_data: SnapshotData) -> Result<Snapshot> {
24 if snapshot_data.vectors.is_empty() {
26 return Err(SnapshotError::storage(
27 "Cannot create snapshot of empty collection",
28 ));
29 }
30
31 let expected_dim = snapshot_data.config.dimension;
33 for (idx, vector) in snapshot_data.vectors.iter().enumerate() {
34 if vector.vector.len() != expected_dim {
35 return Err(SnapshotError::storage(format!(
36 "Vector {} has dimension {} but expected {}",
37 idx,
38 vector.vector.len(),
39 expected_dim
40 )));
41 }
42 }
43
44 self.storage.save(&snapshot_data).await
46 }
47
48 pub async fn restore_snapshot(&self, id: &str) -> Result<SnapshotData> {
56 if id.is_empty() {
57 return Err(SnapshotError::storage("Snapshot ID cannot be empty"));
58 }
59
60 self.storage.load(id).await
61 }
62
63 pub async fn list_snapshots(&self) -> Result<Vec<Snapshot>> {
68 self.storage.list().await
69 }
70
71 pub async fn list_snapshots_for_collection(
79 &self,
80 collection_name: &str,
81 ) -> Result<Vec<Snapshot>> {
82 let all_snapshots = self.storage.list().await?;
83 Ok(all_snapshots
84 .into_iter()
85 .filter(|s| s.collection_name == collection_name)
86 .collect())
87 }
88
89 pub async fn delete_snapshot(&self, id: &str) -> Result<()> {
94 if id.is_empty() {
95 return Err(SnapshotError::storage("Snapshot ID cannot be empty"));
96 }
97
98 self.storage.delete(id).await
99 }
100
101 pub async fn get_snapshot_info(&self, id: &str) -> Result<Snapshot> {
109 let snapshots = self.storage.list().await?;
110 snapshots
111 .into_iter()
112 .find(|s| s.id == id)
113 .ok_or_else(|| SnapshotError::SnapshotNotFound(id.to_string()))
114 }
115
116 pub async fn cleanup_old_snapshots(
125 &self,
126 collection_name: &str,
127 keep_count: usize,
128 ) -> Result<usize> {
129 let snapshots = self.list_snapshots_for_collection(collection_name).await?;
130
131 if snapshots.len() <= keep_count {
132 return Ok(0);
133 }
134
135 let to_delete = &snapshots[keep_count..];
136 let mut deleted = 0;
137
138 for snapshot in to_delete {
139 if self.storage.delete(&snapshot.id).await.is_ok() {
140 deleted += 1;
141 }
142 }
143
144 Ok(deleted)
145 }
146
147 pub async fn total_size(&self) -> Result<u64> {
149 let snapshots = self.storage.list().await?;
150 Ok(snapshots.iter().map(|s| s.size_bytes).sum())
151 }
152
153 pub async fn collection_size(&self, collection_name: &str) -> Result<u64> {
155 let snapshots = self.list_snapshots_for_collection(collection_name).await?;
156 Ok(snapshots.iter().map(|s| s.size_bytes).sum())
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use crate::snapshot::{CollectionConfig, DistanceMetric, VectorRecord};
164 use crate::storage::LocalStorage;
165 use std::path::PathBuf;
166
167 fn create_test_snapshot_data(name: &str, vector_count: usize) -> SnapshotData {
168 let config = CollectionConfig {
169 dimension: 3,
170 metric: DistanceMetric::Cosine,
171 hnsw_config: None,
172 };
173
174 let vectors = (0..vector_count)
175 .map(|i| {
176 VectorRecord::new(
177 format!("v{}", i),
178 vec![i as f32, (i + 1) as f32, (i + 2) as f32],
179 None,
180 )
181 })
182 .collect();
183
184 SnapshotData::new(name.to_string(), config, vectors)
185 }
186
187 #[tokio::test]
188 async fn test_create_and_restore_snapshot() {
189 let temp_dir = std::env::temp_dir().join("ruvector-manager-test");
190 let storage = Box::new(LocalStorage::new(temp_dir.clone()));
191 let manager = SnapshotManager::new(storage);
192
193 let snapshot_data = create_test_snapshot_data("test-collection", 5);
194 let id = snapshot_data.id().to_string();
195
196 let snapshot = manager.create_snapshot(snapshot_data).await.unwrap();
198 assert_eq!(snapshot.id, id);
199 assert_eq!(snapshot.vectors_count, 5);
200
201 let restored = manager.restore_snapshot(&id).await.unwrap();
203 assert_eq!(restored.id(), id);
204 assert_eq!(restored.vectors_count(), 5);
205
206 let _ = manager.delete_snapshot(&id).await;
208 let _ = std::fs::remove_dir_all(temp_dir);
209 }
210
211 #[tokio::test]
212 async fn test_list_snapshots() {
213 let temp_dir = std::env::temp_dir().join("ruvector-list-test");
214 let storage = Box::new(LocalStorage::new(temp_dir.clone()));
215 let manager = SnapshotManager::new(storage);
216
217 let snapshot1 = create_test_snapshot_data("collection-1", 3);
219 let snapshot2 = create_test_snapshot_data("collection-2", 5);
220
221 let id1 = snapshot1.id().to_string();
222 let id2 = snapshot2.id().to_string();
223
224 manager.create_snapshot(snapshot1).await.unwrap();
225 manager.create_snapshot(snapshot2).await.unwrap();
226
227 let all_snapshots = manager.list_snapshots().await.unwrap();
229 assert!(all_snapshots.len() >= 2);
230
231 let collection1_snapshots = manager
233 .list_snapshots_for_collection("collection-1")
234 .await
235 .unwrap();
236 assert_eq!(collection1_snapshots.len(), 1);
237
238 let _ = manager.delete_snapshot(&id1).await;
240 let _ = manager.delete_snapshot(&id2).await;
241 let _ = std::fs::remove_dir_all(temp_dir);
242 }
243
244 #[tokio::test]
245 async fn test_cleanup_old_snapshots() {
246 let temp_dir = std::env::temp_dir().join("ruvector-cleanup-test");
247 let storage = Box::new(LocalStorage::new(temp_dir.clone()));
248 let manager = SnapshotManager::new(storage);
249
250 for i in 0..5 {
252 let snapshot_data = create_test_snapshot_data("test-collection", i + 1);
253 manager.create_snapshot(snapshot_data).await.unwrap();
254 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
255 }
256
257 let deleted = manager
259 .cleanup_old_snapshots("test-collection", 2)
260 .await
261 .unwrap();
262 assert_eq!(deleted, 3);
263
264 let remaining = manager
266 .list_snapshots_for_collection("test-collection")
267 .await
268 .unwrap();
269 assert_eq!(remaining.len(), 2);
270
271 let _ = std::fs::remove_dir_all(temp_dir);
273 }
274
275 #[tokio::test]
276 async fn test_snapshot_validation() {
277 let temp_dir = std::env::temp_dir().join("ruvector-validation-test");
278 let storage = Box::new(LocalStorage::new(temp_dir.clone()));
279 let manager = SnapshotManager::new(storage);
280
281 let config = CollectionConfig {
283 dimension: 3,
284 metric: DistanceMetric::Cosine,
285 hnsw_config: None,
286 };
287 let empty_data = SnapshotData::new("empty".to_string(), config, vec![]);
288 let result = manager.create_snapshot(empty_data).await;
289 assert!(result.is_err());
290
291 let _ = std::fs::remove_dir_all(temp_dir);
293 }
294}