1#[cfg(feature = "storage")]
7use crate::error::{Result, RuvectorError};
8#[cfg(feature = "storage")]
9use crate::types::{DbOptions, VectorEntry, VectorId};
10#[cfg(feature = "storage")]
11use bincode::config;
12#[cfg(feature = "storage")]
13use once_cell::sync::Lazy;
14#[cfg(feature = "storage")]
15use parking_lot::Mutex;
16#[cfg(feature = "storage")]
17use redb::{Database, ReadableTable, ReadableTableMetadata, TableDefinition};
18#[cfg(feature = "storage")]
19use serde_json;
20#[cfg(feature = "storage")]
21use std::collections::HashMap;
22#[cfg(feature = "storage")]
23use std::path::{Path, PathBuf};
24#[cfg(feature = "storage")]
25use std::sync::{Arc, Weak};
26
27#[cfg(feature = "storage")]
28const VECTORS_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("vectors");
29const METADATA_TABLE: TableDefinition<&str, &str> = TableDefinition::new("metadata");
30const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config");
31
32const DB_CONFIG_KEY: &str = "__ruvector_db_config__";
34
35type PathSlot = Mutex<Option<Weak<Database>>>;
44
45static DB_POOL: Lazy<Mutex<HashMap<PathBuf, Arc<PathSlot>>>> =
50 Lazy::new(|| Mutex::new(HashMap::new()));
51
52struct Pooled {
54 db: Arc<Database>,
55 slot: Arc<PathSlot>,
56}
57
58pub struct VectorStorage {
60 pooled: Option<Pooled>,
62 path: PathBuf,
63 dimensions: usize,
64}
65
66impl VectorStorage {
67 pub fn new<P: AsRef<Path>>(path: P, dimensions: usize) -> Result<Self> {
73 let path_ref = path.as_ref();
75
76 if let Some(parent) = path_ref.parent() {
78 if !parent.as_os_str().is_empty() && !parent.exists() {
79 std::fs::create_dir_all(parent).map_err(|e| {
80 RuvectorError::InvalidPath(format!("Failed to create directory: {}", e))
81 })?;
82 }
83 }
84
85 let path_buf = if path_ref.is_absolute() {
87 path_ref.to_path_buf()
88 } else {
89 std::env::current_dir()
90 .map_err(|e| RuvectorError::InvalidPath(format!("Failed to get cwd: {}", e)))?
91 .join(path_ref)
92 };
93
94 let path_str = path_ref.to_string_lossy();
97 if path_str.contains("..") {
98 if !path_ref.is_absolute() {
102 if let Ok(cwd) = std::env::current_dir() {
103 let mut normalized = cwd.clone();
105 for component in path_ref.components() {
106 match component {
107 std::path::Component::ParentDir => {
108 if !normalized.pop() || !normalized.starts_with(&cwd) {
109 return Err(RuvectorError::InvalidPath(
110 "Path traversal attempt detected".to_string(),
111 ));
112 }
113 }
114 std::path::Component::Normal(c) => normalized.push(c),
115 _ => {}
116 }
117 }
118 }
119 }
120 }
121
122 let slot = {
126 let mut pool = DB_POOL.lock();
127 Arc::clone(pool.entry(path_buf.clone()).or_default())
128 };
129
130 let db = match Self::open_pooled(&slot, &path_buf) {
131 Ok(db) => db,
132 Err(e) => {
133 Self::release_slot_if_unused(&path_buf, slot);
135 return Err(e);
136 }
137 };
138
139 Ok(Self {
140 pooled: Some(Pooled { db, slot }),
141 path: path_buf,
142 dimensions,
143 })
144 }
145
146 fn open_pooled(slot: &Arc<PathSlot>, path: &Path) -> Result<Arc<Database>> {
148 let mut guard = slot.lock();
149
150 if let Some(existing_db) = guard.as_ref().and_then(Weak::upgrade) {
151 return Ok(existing_db);
153 }
154
155 let new_db = Arc::new(Database::create(path)?);
159
160 let write_txn = new_db.begin_write()?;
162 {
163 let _ = write_txn.open_table(VECTORS_TABLE)?;
164 let _ = write_txn.open_table(METADATA_TABLE)?;
165 let _ = write_txn.open_table(CONFIG_TABLE)?;
166 }
167 write_txn.commit()?;
168
169 *guard = Some(Arc::downgrade(&new_db));
170 Ok(new_db)
171 }
172
173 fn release_slot_if_unused(path: &Path, slot: Arc<PathSlot>) {
181 let mut pool = DB_POOL.lock();
182 let is_ours = matches!(pool.get(path), Some(pooled) if Arc::ptr_eq(pooled, &slot));
183 drop(slot);
184
185 if is_ours && matches!(pool.get(path), Some(pooled) if Arc::strong_count(pooled) == 1) {
186 pool.remove(path);
187 }
188 }
189
190 #[inline]
196 fn db(&self) -> &Database {
197 &self
198 .pooled
199 .as_ref()
200 .expect("database handle used after drop")
201 .db
202 }
203
204 pub fn insert(&self, entry: &VectorEntry) -> Result<VectorId> {
206 if entry.vector.len() != self.dimensions {
207 return Err(RuvectorError::DimensionMismatch {
208 expected: self.dimensions,
209 actual: entry.vector.len(),
210 });
211 }
212
213 let id = entry
214 .id
215 .clone()
216 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
217
218 let write_txn = self.db().begin_write()?;
219 {
220 let mut table = write_txn.open_table(VECTORS_TABLE)?;
221
222 let vector_data = bincode::encode_to_vec(&entry.vector, config::standard())
224 .map_err(|e| RuvectorError::SerializationError(e.to_string()))?;
225
226 table.insert(id.as_str(), vector_data.as_slice())?;
227
228 if let Some(metadata) = &entry.metadata {
230 let mut meta_table = write_txn.open_table(METADATA_TABLE)?;
231 let metadata_json = serde_json::to_string(metadata)
232 .map_err(|e| RuvectorError::SerializationError(e.to_string()))?;
233 meta_table.insert(id.as_str(), metadata_json.as_str())?;
234 }
235 }
236 write_txn.commit()?;
237
238 Ok(id)
239 }
240
241 pub fn insert_batch(&self, entries: &[VectorEntry]) -> Result<Vec<VectorId>> {
243 let write_txn = self.db().begin_write()?;
244 let mut ids = Vec::with_capacity(entries.len());
245
246 {
247 let mut table = write_txn.open_table(VECTORS_TABLE)?;
248 let mut meta_table = write_txn.open_table(METADATA_TABLE)?;
249
250 for entry in entries {
251 if entry.vector.len() != self.dimensions {
252 return Err(RuvectorError::DimensionMismatch {
253 expected: self.dimensions,
254 actual: entry.vector.len(),
255 });
256 }
257
258 let id = entry
259 .id
260 .clone()
261 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
262
263 let vector_data = bincode::encode_to_vec(&entry.vector, config::standard())
265 .map_err(|e| RuvectorError::SerializationError(e.to_string()))?;
266 table.insert(id.as_str(), vector_data.as_slice())?;
267
268 if let Some(metadata) = &entry.metadata {
270 let metadata_json = serde_json::to_string(metadata)
271 .map_err(|e| RuvectorError::SerializationError(e.to_string()))?;
272 meta_table.insert(id.as_str(), metadata_json.as_str())?;
273 }
274
275 ids.push(id);
276 }
277 }
278
279 write_txn.commit()?;
280 Ok(ids)
281 }
282
283 pub fn get(&self, id: &str) -> Result<Option<VectorEntry>> {
285 let read_txn = self.db().begin_read()?;
286 let table = read_txn.open_table(VECTORS_TABLE)?;
287
288 let Some(vector_data) = table.get(id)? else {
289 return Ok(None);
290 };
291
292 let (vector, _): (Vec<f32>, usize) =
293 bincode::decode_from_slice(vector_data.value(), config::standard())
294 .map_err(|e| RuvectorError::SerializationError(e.to_string()))?;
295
296 let meta_table = read_txn.open_table(METADATA_TABLE)?;
298 let metadata = if let Some(meta_data) = meta_table.get(id)? {
299 let meta_str = meta_data.value();
300 Some(
301 serde_json::from_str(meta_str)
302 .map_err(|e| RuvectorError::SerializationError(e.to_string()))?,
303 )
304 } else {
305 None
306 };
307
308 Ok(Some(VectorEntry {
309 id: Some(id.to_string()),
310 vector,
311 metadata,
312 }))
313 }
314
315 pub fn delete(&self, id: &str) -> Result<bool> {
317 let write_txn = self.db().begin_write()?;
318 let deleted;
319
320 {
321 let mut table = write_txn.open_table(VECTORS_TABLE)?;
322 deleted = table.remove(id)?.is_some();
323
324 let mut meta_table = write_txn.open_table(METADATA_TABLE)?;
325 let _ = meta_table.remove(id)?;
326 }
327
328 write_txn.commit()?;
329 Ok(deleted)
330 }
331
332 pub fn len(&self) -> Result<usize> {
334 let read_txn = self.db().begin_read()?;
335 let table = read_txn.open_table(VECTORS_TABLE)?;
336 Ok(table.len()? as usize)
337 }
338
339 pub fn is_empty(&self) -> Result<bool> {
341 Ok(self.len()? == 0)
342 }
343
344 pub fn all_ids(&self) -> Result<Vec<VectorId>> {
346 let read_txn = self.db().begin_read()?;
347 let table = read_txn.open_table(VECTORS_TABLE)?;
348
349 let mut ids = Vec::new();
350 let iter = table.iter()?;
351 for item in iter {
352 let (key, _) = item?;
353 ids.push(key.value().to_string());
354 }
355
356 Ok(ids)
357 }
358
359 pub fn save_config(&self, options: &DbOptions) -> Result<()> {
361 let config_json = serde_json::to_string(options)
362 .map_err(|e| RuvectorError::SerializationError(e.to_string()))?;
363
364 let write_txn = self.db().begin_write()?;
365 {
366 let mut table = write_txn.open_table(CONFIG_TABLE)?;
367 table.insert(DB_CONFIG_KEY, config_json.as_str())?;
368 }
369 write_txn.commit()?;
370
371 Ok(())
372 }
373
374 pub fn load_config(&self) -> Result<Option<DbOptions>> {
376 let read_txn = self.db().begin_read()?;
377
378 let table = match read_txn.open_table(CONFIG_TABLE) {
380 Ok(t) => t,
381 Err(_) => return Ok(None),
382 };
383
384 let Some(config_data) = table.get(DB_CONFIG_KEY)? else {
385 return Ok(None);
386 };
387
388 let config: DbOptions = serde_json::from_str(config_data.value())
389 .map_err(|e| RuvectorError::SerializationError(e.to_string()))?;
390
391 Ok(Some(config))
392 }
393
394 pub fn save_config_value(&self, key: &str, value: &str) -> Result<()> {
400 let write_txn = self.db().begin_write()?;
401 {
402 let mut table = write_txn.open_table(CONFIG_TABLE)?;
403 table.insert(key, value)?;
404 }
405 write_txn.commit()?;
406 Ok(())
407 }
408
409 pub fn load_config_value(&self, key: &str) -> Result<Option<String>> {
412 let read_txn = self.db().begin_read()?;
413 let table = match read_txn.open_table(CONFIG_TABLE) {
414 Ok(t) => t,
415 Err(_) => return Ok(None),
416 };
417 Ok(table.get(key)?.map(|v| v.value().to_string()))
418 }
419
420 pub fn dimensions(&self) -> usize {
422 self.dimensions
423 }
424}
425
426impl Drop for VectorStorage {
427 fn drop(&mut self) {
428 let Some(Pooled { db, slot }) = self.pooled.take() else {
429 return;
430 };
431
432 {
433 let mut guard = slot.lock();
434
435 if Arc::strong_count(&db) == 1 {
441 *guard = None;
442 }
443 drop(db);
444 }
445
446 Self::release_slot_if_unused(&self.path, slot);
447 }
448}
449
450use uuid;
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456 use tempfile::tempdir;
457
458 #[test]
459 fn test_insert_and_get() -> Result<()> {
460 let dir = tempdir().unwrap();
461 let storage = VectorStorage::new(dir.path().join("test.db"), 3)?;
462
463 let entry = VectorEntry {
464 id: Some("test1".to_string()),
465 vector: vec![1.0, 2.0, 3.0],
466 metadata: None,
467 };
468
469 let id = storage.insert(&entry)?;
470 assert_eq!(id, "test1");
471
472 let retrieved = storage.get("test1")?;
473 assert!(retrieved.is_some());
474 let retrieved = retrieved.unwrap();
475 assert_eq!(retrieved.vector, vec![1.0, 2.0, 3.0]);
476
477 Ok(())
478 }
479
480 #[test]
481 fn test_batch_insert() -> Result<()> {
482 let dir = tempdir().unwrap();
483 let storage = VectorStorage::new(dir.path().join("test.db"), 3)?;
484
485 let entries = vec![
486 VectorEntry {
487 id: None,
488 vector: vec![1.0, 2.0, 3.0],
489 metadata: None,
490 },
491 VectorEntry {
492 id: None,
493 vector: vec![4.0, 5.0, 6.0],
494 metadata: None,
495 },
496 ];
497
498 let ids = storage.insert_batch(&entries)?;
499 assert_eq!(ids.len(), 2);
500 assert_eq!(storage.len()?, 2);
501
502 Ok(())
503 }
504
505 #[test]
506 fn test_delete() -> Result<()> {
507 let dir = tempdir().unwrap();
508 let storage = VectorStorage::new(dir.path().join("test.db"), 3)?;
509
510 let entry = VectorEntry {
511 id: Some("test1".to_string()),
512 vector: vec![1.0, 2.0, 3.0],
513 metadata: None,
514 };
515
516 storage.insert(&entry)?;
517 assert_eq!(storage.len()?, 1);
518
519 let deleted = storage.delete("test1")?;
520 assert!(deleted);
521 assert_eq!(storage.len()?, 0);
522
523 Ok(())
524 }
525
526 #[test]
527 fn test_multiple_instances_same_path() -> Result<()> {
528 let dir = tempdir().unwrap();
531 let db_path = dir.path().join("shared.db");
532
533 let storage1 = VectorStorage::new(&db_path, 3)?;
535
536 storage1.insert(&VectorEntry {
538 id: Some("test1".to_string()),
539 vector: vec![1.0, 2.0, 3.0],
540 metadata: None,
541 })?;
542
543 let storage2 = VectorStorage::new(&db_path, 3)?;
545
546 assert_eq!(storage1.len()?, 1);
548 assert_eq!(storage2.len()?, 1);
549
550 storage2.insert(&VectorEntry {
552 id: Some("test2".to_string()),
553 vector: vec![4.0, 5.0, 6.0],
554 metadata: None,
555 })?;
556
557 assert_eq!(storage1.len()?, 2);
559 assert_eq!(storage2.len()?, 2);
560
561 let retrieved1 = storage1.get("test1")?;
563 assert!(retrieved1.is_some());
564
565 let retrieved2 = storage2.get("test2")?;
566 assert!(retrieved2.is_some());
567
568 Ok(())
569 }
570
571 #[test]
572 fn reopening_after_the_last_handle_drops_preserves_data() -> Result<()> {
573 let dir = tempdir().unwrap();
574 let db_path = dir.path().join("reopen.db");
575
576 {
577 let storage = VectorStorage::new(&db_path, 3)?;
578 storage.insert(&VectorEntry {
579 id: Some("kept".to_string()),
580 vector: vec![1.0, 2.0, 3.0],
581 metadata: None,
582 })?;
583 }
584
585 assert!(
588 !DB_POOL.lock().contains_key(&db_path),
589 "pool kept a dead entry for {}",
590 db_path.display()
591 );
592
593 let reopened = VectorStorage::new(&db_path, 3)?;
594 assert_eq!(reopened.len()?, 1);
595 assert_eq!(
596 reopened.get("kept")?.expect("entry survived reopen").vector,
597 vec![1.0, 2.0, 3.0]
598 );
599
600 Ok(())
601 }
602
603 #[test]
604 fn concurrently_dropping_two_handles_still_reaps_the_pool_entry() {
605 use std::sync::Barrier;
606
607 let dir = tempdir().unwrap();
608 let db_path = dir.path().join("double-drop.db");
609
610 for _ in 0..50 {
611 let first = VectorStorage::new(&db_path, 3).expect("first open");
612 let second = VectorStorage::new(&db_path, 3).expect("second open");
613 let barrier = Arc::new(Barrier::new(2));
614
615 let threads: Vec<_> = [first, second]
616 .into_iter()
617 .map(|handle| {
618 let barrier = Arc::clone(&barrier);
619 std::thread::spawn(move || {
620 barrier.wait();
621 drop(handle);
622 })
623 })
624 .collect();
625
626 for thread in threads {
627 thread.join().unwrap();
628 }
629
630 assert!(
631 !DB_POOL.lock().contains_key(&db_path),
632 "pool kept a dead entry after both handles dropped"
633 );
634 }
635 }
636
637 #[test]
638 fn concurrent_drop_and_open_never_race_the_file_lock() {
639 use std::sync::atomic::{AtomicUsize, Ordering};
640 use std::sync::Barrier;
641
642 const ITERATIONS: usize = 40;
649
650 let dir = tempdir().unwrap();
651 let db_path = dir.path().join("race.db");
652 let failures = Arc::new(AtomicUsize::new(0));
653 let last_error = Arc::new(Mutex::new(None::<String>));
654
655 for _ in 0..ITERATIONS {
656 let holder = VectorStorage::new(&db_path, 3).expect("initial open");
659 let barrier = Arc::new(Barrier::new(2));
660
661 let dropper = {
662 let barrier = Arc::clone(&barrier);
663 std::thread::spawn(move || {
664 barrier.wait();
665 drop(holder);
666 })
667 };
668
669 let opener = {
670 let barrier = Arc::clone(&barrier);
671 let path = db_path.clone();
672 let failures = Arc::clone(&failures);
673 let last_error = Arc::clone(&last_error);
674 std::thread::spawn(move || {
675 barrier.wait();
676 match VectorStorage::new(&path, 3) {
677 Ok(storage) => drop(storage),
678 Err(e) => {
679 failures.fetch_add(1, Ordering::Relaxed);
680 *last_error.lock() = Some(e.to_string());
681 }
682 }
683 })
684 };
685
686 dropper.join().unwrap();
687 opener.join().unwrap();
688 }
689
690 let failed = failures.load(Ordering::Relaxed);
691 assert_eq!(
692 failed,
693 0,
694 "{failed}/{ITERATIONS} concurrent opens raced the drop of the last \
695 handle; last error: {:?}",
696 last_error.lock().as_deref()
697 );
698 }
699}