ruvector_collections/
manager.rs1use dashmap::DashMap;
4use parking_lot::RwLock;
5use std::collections::HashMap;
6use std::path::PathBuf;
7use std::sync::Arc;
8
9use crate::collection::{Collection, CollectionConfig, CollectionStats};
10use crate::error::{CollectionError, Result};
11
12#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
14struct CollectionMetadata {
15 name: String,
16 config: CollectionConfig,
17 created_at: i64,
18 updated_at: i64,
19}
20
21#[derive(Debug)]
23pub struct CollectionManager {
24 collections: DashMap<String, Arc<RwLock<Collection>>>,
26
27 aliases: DashMap<String, String>,
29
30 base_path: PathBuf,
32}
33
34impl CollectionManager {
35 pub fn new(base_path: PathBuf) -> Result<Self> {
50 std::fs::create_dir_all(&base_path)?;
52
53 let manager = Self {
54 collections: DashMap::new(),
55 aliases: DashMap::new(),
56 base_path,
57 };
58
59 manager.load_collections()?;
61
62 Ok(manager)
63 }
64
65 pub fn create_collection(&self, name: &str, config: CollectionConfig) -> Result<()> {
76 Self::validate_name(name)?;
78
79 if self.collections.contains_key(name) {
81 return Err(CollectionError::CollectionAlreadyExists {
82 name: name.to_string(),
83 });
84 }
85
86 if self.aliases.contains_key(name) {
88 return Err(CollectionError::InvalidName {
89 name: name.to_string(),
90 reason: "An alias with this name already exists".to_string(),
91 });
92 }
93
94 let storage_path = self.base_path.join(name);
96 std::fs::create_dir_all(&storage_path)?;
97
98 let db_path = storage_path
99 .join("vectors.db")
100 .to_string_lossy()
101 .to_string();
102
103 let collection = Collection::new(name.to_string(), config, db_path)?;
105
106 self.save_collection_metadata(&collection)?;
108
109 self.collections
111 .insert(name.to_string(), Arc::new(RwLock::new(collection)));
112
113 Ok(())
114 }
115
116 pub fn delete_collection(&self, name: &str) -> Result<()> {
127 if !self.collections.contains_key(name) {
129 return Err(CollectionError::CollectionNotFound {
130 name: name.to_string(),
131 });
132 }
133
134 let active_aliases: Vec<String> = self
136 .aliases
137 .iter()
138 .filter(|entry| entry.value() == name)
139 .map(|entry| entry.key().clone())
140 .collect();
141
142 if !active_aliases.is_empty() {
143 return Err(CollectionError::CollectionHasAliases {
144 collection: name.to_string(),
145 aliases: active_aliases,
146 });
147 }
148
149 self.collections.remove(name);
151
152 let collection_path = self.base_path.join(name);
154 if collection_path.exists() {
155 std::fs::remove_dir_all(&collection_path)?;
156 }
157
158 Ok(())
159 }
160
161 pub fn get_collection(&self, name: &str) -> Option<Arc<RwLock<Collection>>> {
167 let collection_name = self.resolve_alias(name).unwrap_or_else(|| name.to_string());
169
170 self.collections
171 .get(&collection_name)
172 .map(|entry| entry.value().clone())
173 }
174
175 pub fn list_collections(&self) -> Vec<String> {
177 self.collections
178 .iter()
179 .map(|entry| entry.key().clone())
180 .collect()
181 }
182
183 pub fn collection_exists(&self, name: &str) -> bool {
189 self.collections.contains_key(name)
190 }
191
192 pub fn collection_stats(&self, name: &str) -> Result<CollectionStats> {
194 let collection =
195 self.get_collection(name)
196 .ok_or_else(|| CollectionError::CollectionNotFound {
197 name: name.to_string(),
198 })?;
199
200 let guard = collection.read();
201 guard.stats()
202 }
203
204 pub fn create_alias(&self, alias: &str, collection: &str) -> Result<()> {
218 Self::validate_name(alias)?;
220
221 if self.aliases.contains_key(alias) {
223 return Err(CollectionError::AliasAlreadyExists {
224 alias: alias.to_string(),
225 });
226 }
227
228 if self.collections.contains_key(alias) {
230 return Err(CollectionError::InvalidName {
231 name: alias.to_string(),
232 reason: "A collection with this name already exists".to_string(),
233 });
234 }
235
236 if !self.collections.contains_key(collection) {
238 return Err(CollectionError::CollectionNotFound {
239 name: collection.to_string(),
240 });
241 }
242
243 self.aliases
245 .insert(alias.to_string(), collection.to_string());
246
247 self.save_aliases()?;
249
250 Ok(())
251 }
252
253 pub fn delete_alias(&self, alias: &str) -> Result<()> {
263 if self.aliases.remove(alias).is_none() {
264 return Err(CollectionError::AliasNotFound {
265 alias: alias.to_string(),
266 });
267 }
268
269 self.save_aliases()?;
271
272 Ok(())
273 }
274
275 pub fn switch_alias(&self, alias: &str, new_collection: &str) -> Result<()> {
287 if !self.aliases.contains_key(alias) {
289 return Err(CollectionError::AliasNotFound {
290 alias: alias.to_string(),
291 });
292 }
293
294 if !self.collections.contains_key(new_collection) {
296 return Err(CollectionError::CollectionNotFound {
297 name: new_collection.to_string(),
298 });
299 }
300
301 self.aliases
303 .insert(alias.to_string(), new_collection.to_string());
304
305 self.save_aliases()?;
307
308 Ok(())
309 }
310
311 pub fn resolve_alias(&self, name_or_alias: &str) -> Option<String> {
321 self.aliases
322 .get(name_or_alias)
323 .map(|entry| entry.value().clone())
324 }
325
326 pub fn list_aliases(&self) -> Vec<(String, String)> {
328 self.aliases
329 .iter()
330 .map(|entry| (entry.key().clone(), entry.value().clone()))
331 .collect()
332 }
333
334 pub fn is_alias(&self, name: &str) -> bool {
336 self.aliases.contains_key(name)
337 }
338
339 fn validate_name(name: &str) -> Result<()> {
343 if name.is_empty() {
344 return Err(CollectionError::InvalidName {
345 name: name.to_string(),
346 reason: "Name cannot be empty".to_string(),
347 });
348 }
349
350 if name.len() > 255 {
351 return Err(CollectionError::InvalidName {
352 name: name.to_string(),
353 reason: "Name too long (max 255 characters)".to_string(),
354 });
355 }
356
357 if !name
359 .chars()
360 .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
361 {
362 return Err(CollectionError::InvalidName {
363 name: name.to_string(),
364 reason: "Name can only contain letters, numbers, hyphens, and underscores"
365 .to_string(),
366 });
367 }
368
369 Ok(())
370 }
371
372 fn load_collections(&self) -> Result<()> {
374 if !self.base_path.exists() {
375 return Ok(());
376 }
377
378 self.load_aliases()?;
380
381 for entry in std::fs::read_dir(&self.base_path)? {
383 let entry = entry?;
384 let path = entry.path();
385
386 if path.is_dir() {
387 let name = path
388 .file_name()
389 .and_then(|n| n.to_str())
390 .unwrap_or("")
391 .to_string();
392
393 if name.starts_with('.') || name == "aliases.json" {
395 continue;
396 }
397
398 if let Ok(metadata) = self.load_collection_metadata(&name) {
400 let db_path = path.join("vectors.db").to_string_lossy().to_string();
401
402 if let Ok(mut collection) =
404 Collection::new(metadata.name.clone(), metadata.config, db_path)
405 {
406 collection.created_at = metadata.created_at;
407 collection.updated_at = metadata.updated_at;
408
409 self.collections
410 .insert(name.clone(), Arc::new(RwLock::new(collection)));
411 }
412 }
413 }
414 }
415
416 Ok(())
417 }
418
419 fn save_collection_metadata(&self, collection: &Collection) -> Result<()> {
421 let metadata = CollectionMetadata {
422 name: collection.name.clone(),
423 config: collection.config.clone(),
424 created_at: collection.created_at,
425 updated_at: collection.updated_at,
426 };
427
428 let metadata_path = self.base_path.join(&collection.name).join("metadata.json");
429
430 let json = serde_json::to_string_pretty(&metadata)?;
431 std::fs::write(metadata_path, json)?;
432
433 Ok(())
434 }
435
436 fn load_collection_metadata(&self, name: &str) -> Result<CollectionMetadata> {
438 let metadata_path = self.base_path.join(name).join("metadata.json");
439 let json = std::fs::read_to_string(metadata_path)?;
440 let metadata: CollectionMetadata = serde_json::from_str(&json)?;
441 Ok(metadata)
442 }
443
444 fn save_aliases(&self) -> Result<()> {
446 let aliases: HashMap<String, String> = self
447 .aliases
448 .iter()
449 .map(|entry| (entry.key().clone(), entry.value().clone()))
450 .collect();
451
452 let aliases_path = self.base_path.join("aliases.json");
453 let json = serde_json::to_string_pretty(&aliases)?;
454 std::fs::write(aliases_path, json)?;
455
456 Ok(())
457 }
458
459 fn load_aliases(&self) -> Result<()> {
461 let aliases_path = self.base_path.join("aliases.json");
462
463 if !aliases_path.exists() {
464 return Ok(());
465 }
466
467 let json = std::fs::read_to_string(aliases_path)?;
468 let aliases: HashMap<String, String> = serde_json::from_str(&json)?;
469
470 for (alias, collection) in aliases {
471 self.aliases.insert(alias, collection);
472 }
473
474 Ok(())
475 }
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481
482 #[test]
483 fn test_validate_name() {
484 assert!(CollectionManager::validate_name("valid-name_123").is_ok());
485 assert!(CollectionManager::validate_name("").is_err());
486 assert!(CollectionManager::validate_name("invalid name").is_err());
487 assert!(CollectionManager::validate_name("invalid/name").is_err());
488 }
489
490 #[test]
491 fn test_collection_manager() -> Result<()> {
492 let temp_dir = std::env::temp_dir().join("ruvector_test_collections");
493 let _ = std::fs::remove_dir_all(&temp_dir);
494
495 let manager = CollectionManager::new(temp_dir.clone())?;
496
497 let config = CollectionConfig::with_dimensions(128);
499 manager.create_collection("test", config)?;
500
501 assert!(manager.collection_exists("test"));
502 assert_eq!(manager.list_collections().len(), 1);
503
504 manager.create_alias("test_alias", "test")?;
506 assert!(manager.is_alias("test_alias"));
507 assert_eq!(
508 manager.resolve_alias("test_alias"),
509 Some("test".to_string())
510 );
511
512 assert!(manager.get_collection("test_alias").is_some());
514
515 manager.delete_alias("test_alias")?;
517 manager.delete_collection("test")?;
518 let _ = std::fs::remove_dir_all(&temp_dir);
519
520 Ok(())
521 }
522}