1use std::collections::HashMap;
14use std::path::{Path, PathBuf};
15use std::sync::atomic::{AtomicUsize, Ordering};
16use std::sync::{Arc, Mutex};
17
18use crate::blob_store::BlobStore;
19use crate::index::{SparseIndex, SparseVector};
20use crate::mmap_index::{self, MmapPostingData};
21use crate::wand::Postings;
22
23const MMAP_FILE: &str = "sparse.mmap";
24const VECTORS_FILE: &str = "sparse_vectors.bin";
25const DIMS_FILE: &str = "sparse_dims.bin";
26const LEGACY_FILE: &str = "sparse.bin";
28
29const INDEX_FILES: &[&str] = &[MMAP_FILE, VECTORS_FILE, DIMS_FILE];
31
32const BLOB_PREFIX: &str = "Sparse_";
34
35static CACHE_SEQ: AtomicUsize = AtomicUsize::new(0);
37
38enum StorageBackend {
40 Filesystem,
42 Store {
44 store: Arc<dyn BlobStore>,
45 index_name: String,
46 },
47}
48
49struct Inner {
50 index: SparseIndex,
51 mmap: Option<MmapPostingData>,
52 postings_loaded: bool,
54 vectors_loaded: bool,
56 num_vectors: usize,
58 dirty: bool,
59}
60
61pub struct SparseHandle {
62 inner: Mutex<Inner>,
63 path: PathBuf,
64 backend: StorageBackend,
65}
66
67impl SparseHandle {
68 pub fn create(path: &str) -> Result<Self, String> {
74 std::fs::create_dir_all(Path::new(path))
75 .map_err(|e| format!("cannot create directory {path}: {e}"))?;
76 let handle = Self {
77 inner: Mutex::new(Inner {
78 index: SparseIndex::new(),
79 mmap: None,
80 postings_loaded: true,
81 vectors_loaded: true,
82 num_vectors: 0,
83 dirty: false,
84 }),
85 path: PathBuf::from(path),
86 backend: StorageBackend::Filesystem,
87 };
88 handle.commit_inner()?;
89 Ok(handle)
90 }
91
92 pub fn open(path: &str) -> Result<Self, String> {
95 let base = Path::new(path);
96 let mmap_path = base.join(MMAP_FILE);
97
98 if mmap_path.exists() {
99 Self::open_mmap(base, StorageBackend::Filesystem)
100 } else {
101 Self::open_legacy(base)
102 }
103 }
104
105 pub fn create_with_store(
115 store: Arc<dyn BlobStore>,
116 index_name: &str,
117 cache_base: &Path,
118 ) -> Result<Self, String> {
119 let blob_name = format!("{BLOB_PREFIX}{index_name}");
120 let cache_dir = Self::make_cache_dir(cache_base, &blob_name)?;
121
122 let handle = Self {
123 inner: Mutex::new(Inner {
124 index: SparseIndex::new(),
125 mmap: None,
126 postings_loaded: true,
127 vectors_loaded: true,
128 num_vectors: 0,
129 dirty: false,
130 }),
131 path: cache_dir,
132 backend: StorageBackend::Store {
133 store,
134 index_name: blob_name,
135 },
136 };
137 handle.commit_inner()?;
138 Ok(handle)
139 }
140
141 pub fn open_with_store(
146 store: Arc<dyn BlobStore>,
147 index_name: &str,
148 cache_base: &Path,
149 ) -> Result<Self, String> {
150 let blob_name = format!("{BLOB_PREFIX}{index_name}");
151 let cache_dir = Self::make_cache_dir(cache_base, &blob_name)?;
152
153 let files = store
155 .list(&blob_name)
156 .map_err(|e| format!("cannot list blobs for {blob_name}: {e}"))?;
157
158 for file_name in &files {
159 let data = store
160 .load(&blob_name, file_name)
161 .map_err(|e| format!("cannot load {blob_name}/{file_name}: {e}"))?;
162 std::fs::write(cache_dir.join(file_name), data)
163 .map_err(|e| format!("cannot write cache {file_name}: {e}"))?;
164 }
165
166 let backend = StorageBackend::Store {
167 store,
168 index_name: blob_name,
169 };
170
171 if cache_dir.join(MMAP_FILE).exists() {
173 Self::open_mmap(&cache_dir, backend)
174 } else {
175 let handle = Self {
177 inner: Mutex::new(Inner {
178 index: SparseIndex::new(),
179 mmap: None,
180 postings_loaded: true,
181 vectors_loaded: true,
182 num_vectors: 0,
183 dirty: false,
184 }),
185 path: cache_dir,
186 backend,
187 };
188 handle.commit_inner()?;
189 Ok(handle)
190 }
191 }
192
193 fn make_cache_dir(base: &Path, index_name: &str) -> Result<PathBuf, String> {
199 let seq = CACHE_SEQ.fetch_add(1, Ordering::Relaxed);
200 let pid = std::process::id();
201 let dir = base
202 .join(format!("{pid}"))
203 .join(format!("{index_name}_{seq}"));
204 std::fs::create_dir_all(&dir)
205 .map_err(|e| format!("cannot create cache dir {}: {e}", dir.display()))?;
206 Ok(dir)
207 }
208
209 fn open_mmap(base: &Path, backend: StorageBackend) -> Result<Self, String> {
216 let mmap = MmapPostingData::open(&base.join(MMAP_FILE))?;
217
218 let dims_data = std::fs::read(base.join(DIMS_FILE))
220 .map_err(|e| format!("cannot read {DIMS_FILE}: {e}"))?;
221 let (dim_map, dim_reverse): (HashMap<u32, usize>, Vec<u32>) =
222 bincode::deserialize(&dims_data)
223 .map_err(|e| format!("cannot deserialize dims: {e}"))?;
224
225 let num_dims = mmap.num_dims();
226 let num_vectors = mmap.num_vectors();
227 let empty_postings: Vec<Postings> = (0..num_dims).map(|_| Postings::new()).collect();
228 let index =
230 SparseIndex::from_parts(dim_map, dim_reverse, empty_postings, HashMap::new());
231
232 Ok(Self {
233 inner: Mutex::new(Inner {
234 index,
235 mmap: Some(mmap),
236 postings_loaded: false,
237 vectors_loaded: false,
238 num_vectors,
239 dirty: false,
240 }),
241 path: base.to_path_buf(),
242 backend,
243 })
244 }
245
246 fn open_legacy(base: &Path) -> Result<Self, String> {
248 let data_path = base.join(LEGACY_FILE);
249 let data = std::fs::read(&data_path)
250 .map_err(|e| format!("cannot read {}: {e}", data_path.display()))?;
251 let index: SparseIndex = bincode::deserialize(&data)
252 .map_err(|e| format!("cannot deserialize sparse index: {e}"))?;
253 let num_vectors = index.len();
254 Ok(Self {
255 inner: Mutex::new(Inner {
256 index,
257 mmap: None,
258 postings_loaded: true,
259 vectors_loaded: true,
260 num_vectors,
261 dirty: false,
262 }),
263 path: base.to_path_buf(),
264 backend: StorageBackend::Filesystem,
265 })
266 }
267
268 fn ensure_postings_loaded(inner: &mut Inner) {
274 if inner.postings_loaded {
275 return;
276 }
277 if let Some(ref mmap) = inner.mmap {
278 let postings = inner.index.postings_mut();
279 for (i, pl) in postings.iter_mut().enumerate() {
280 *pl = mmap.load_postings(i);
281 }
282 }
283 inner.postings_loaded = true;
284 }
285
286 fn ensure_vectors_loaded(inner: &mut Inner, path: &Path) -> Result<(), String> {
288 if inner.vectors_loaded {
289 return Ok(());
290 }
291 let vectors_path = path.join(VECTORS_FILE);
292 let data = std::fs::read(&vectors_path)
293 .map_err(|e| format!("cannot read {}: {e}", vectors_path.display()))?;
294 let vectors: HashMap<u64, SparseVector> = bincode::deserialize(&data)
295 .map_err(|e| format!("cannot deserialize vectors: {e}"))?;
296 inner.index.set_vectors(vectors);
297 inner.vectors_loaded = true;
298 Ok(())
299 }
300
301 pub fn insert(&self, node_id: u64, vector: &SparseVector) -> Result<(), String> {
306 let mut inner = self.inner.lock().map_err(|_| "lock poisoned".to_string())?;
307 Self::ensure_vectors_loaded(&mut inner, &self.path)?;
308 Self::ensure_postings_loaded(&mut inner);
309 inner.index.insert(node_id, vector);
310 inner.num_vectors = inner.index.len();
311 inner.dirty = true;
312 Ok(())
313 }
314
315 pub fn remove(&self, node_id: u64) -> Result<bool, String> {
316 let mut inner = self.inner.lock().map_err(|_| "lock poisoned".to_string())?;
317 Self::ensure_vectors_loaded(&mut inner, &self.path)?;
318 Self::ensure_postings_loaded(&mut inner);
319 let removed = inner.index.remove(node_id);
320 if removed {
321 inner.num_vectors = inner.index.len();
322 inner.dirty = true;
323 }
324 Ok(removed)
325 }
326
327 pub fn search(&self, query: &SparseVector, limit: usize) -> Vec<(u64, f32)> {
328 let inner = self.inner.lock().unwrap();
329 if !inner.dirty {
330 if let Some(ref mmap) = inner.mmap {
331 return mmap_index::search_mmap(
332 mmap,
333 inner.index.dim_map(),
334 query,
335 limit,
336 &|_| true,
337 );
338 }
339 }
340 inner.index.search(query, limit)
341 }
342
343 pub fn search_filtered(
344 &self,
345 query: &SparseVector,
346 limit: usize,
347 allowed_ids: &[u64],
348 ) -> Vec<(u64, f32)> {
349 let inner = self.inner.lock().unwrap();
350 if !inner.dirty {
351 if let Some(ref mmap) = inner.mmap {
352 return mmap_index::search_mmap_allowed(
353 mmap,
354 inner.index.dim_map(),
355 query,
356 limit,
357 allowed_ids,
358 );
359 }
360 }
361 inner.index.search_filtered(query, limit, allowed_ids)
362 }
363
364 pub fn len(&self) -> usize {
365 self.inner.lock().unwrap().num_vectors
366 }
367
368 pub fn is_empty(&self) -> bool {
369 self.len() == 0
370 }
371
372 pub fn commit_inner(&self) -> Result<(), String> {
375 let mut inner = self.inner.lock().map_err(|_| "lock poisoned".to_string())?;
376
377 if !inner.postings_loaded && !inner.dirty && inner.mmap.is_some() {
380 return Ok(());
381 }
382
383 Self::ensure_postings_loaded(&mut inner);
384 Self::ensure_vectors_loaded(&mut inner, &self.path)?;
385
386 mmap_index::write_mmap_file(
388 &self.path.join(MMAP_FILE),
389 inner.index.postings(),
390 inner.num_vectors as u32,
391 )?;
392
393 let vectors_data = bincode::serialize(inner.index.vectors())
395 .map_err(|e| format!("cannot serialize vectors: {e}"))?;
396 std::fs::write(self.path.join(VECTORS_FILE), &vectors_data)
397 .map_err(|e| format!("cannot write {VECTORS_FILE}: {e}"))?;
398
399 let dims_data =
400 bincode::serialize(&(inner.index.dim_map(), inner.index.dim_reverse()))
401 .map_err(|e| format!("cannot serialize dims: {e}"))?;
402 std::fs::write(self.path.join(DIMS_FILE), &dims_data)
403 .map_err(|e| format!("cannot write {DIMS_FILE}: {e}"))?;
404
405 if let StorageBackend::Store { ref store, ref index_name } = self.backend {
407 for &file in INDEX_FILES {
408 let data = std::fs::read(self.path.join(file))
409 .map_err(|e| format!("cannot read cache {file}: {e}"))?;
410 store
411 .save(index_name, file, &data)
412 .map_err(|e| format!("cannot save {index_name}/{file} to store: {e}"))?;
413 }
414 }
415
416 let mmap = MmapPostingData::open(&self.path.join(MMAP_FILE))?;
418 inner.mmap = Some(mmap);
419 inner.dirty = false;
420 let legacy = self.path.join(LEGACY_FILE);
425 if legacy.exists() {
426 let _ = std::fs::remove_file(&legacy);
427 if let StorageBackend::Store { ref store, ref index_name } = self.backend {
429 let _ = store.delete(index_name, LEGACY_FILE);
430 }
431 }
432
433 Ok(())
434 }
435}
436
437impl Drop for SparseHandle {
438 fn drop(&mut self) {
439 if let StorageBackend::Store { .. } = &self.backend {
442 let _ = std::fs::remove_dir_all(&self.path);
443 }
444 }
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450 use crate::blob_store::MemBlobStore;
451
452 fn tmp_path(name: &str) -> PathBuf {
453 std::env::temp_dir().join(name)
454 }
455
456 fn cleanup(path: &Path) {
457 let _ = std::fs::remove_dir_all(path);
458 }
459
460 #[test]
465 fn create_writes_mmap_format() {
466 let p = tmp_path("sparse_mmap_create_test");
467 cleanup(&p);
468 let path = p.to_str().unwrap();
469
470 let _handle = SparseHandle::create(path).unwrap();
471 assert!(p.join(MMAP_FILE).exists());
472 assert!(p.join(VECTORS_FILE).exists());
473 assert!(p.join(DIMS_FILE).exists());
474
475 let handle2 = SparseHandle::open(path).unwrap();
476 assert_eq!(handle2.len(), 0);
477
478 cleanup(&p);
479 }
480
481 #[test]
482 fn persistence_roundtrip_mmap() {
483 let p = tmp_path("sparse_mmap_roundtrip_test");
484 cleanup(&p);
485 let path = p.to_str().unwrap();
486
487 let handle = SparseHandle::create(path).unwrap();
488 handle
489 .insert(42, &SparseVector::new(vec![1, 2], vec![0.5, 0.3]))
490 .unwrap();
491 handle
492 .insert(99, &SparseVector::new(vec![2, 3], vec![0.8, 0.2]))
493 .unwrap();
494 handle.commit_inner().unwrap();
495 drop(handle);
496
497 let handle2 = SparseHandle::open(path).unwrap();
499 assert_eq!(handle2.len(), 2);
500
501 let results = handle2.search(&SparseVector::new(vec![2], vec![1.0]), 10);
503 assert_eq!(results.len(), 2);
504 assert_eq!(results[0].0, 99);
505 assert!((results[0].1 - 0.8).abs() < 1e-6);
506 assert_eq!(results[1].0, 42);
507 assert!((results[1].1 - 0.3).abs() < 1e-6);
508
509 cleanup(&p);
510 }
511
512 #[test]
513 fn mmap_search_filtered() {
514 let p = tmp_path("sparse_mmap_filtered_test");
515 cleanup(&p);
516 let path = p.to_str().unwrap();
517
518 let handle = SparseHandle::create(path).unwrap();
519 handle
520 .insert(1, &SparseVector::new(vec![1, 2], vec![0.5, 0.3]))
521 .unwrap();
522 handle
523 .insert(2, &SparseVector::new(vec![1, 3], vec![0.9, 0.1]))
524 .unwrap();
525 handle
526 .insert(3, &SparseVector::new(vec![1], vec![0.7]))
527 .unwrap();
528 handle.commit_inner().unwrap();
529 drop(handle);
530
531 let handle2 = SparseHandle::open(path).unwrap();
532 let results = handle2.search_filtered(&SparseVector::new(vec![1], vec![1.0]), 10, &[1, 3]);
533 assert_eq!(results.len(), 2);
534 assert_eq!(results[0].0, 3); assert_eq!(results[1].0, 1); cleanup(&p);
538 }
539
540 #[test]
541 fn mutation_after_mmap_open() {
542 let p = tmp_path("sparse_mmap_mutation_test");
543 cleanup(&p);
544 let path = p.to_str().unwrap();
545
546 let handle = SparseHandle::create(path).unwrap();
547 handle
548 .insert(1, &SparseVector::new(vec![10], vec![1.0]))
549 .unwrap();
550 handle.commit_inner().unwrap();
551 drop(handle);
552
553 let handle2 = SparseHandle::open(path).unwrap();
555 handle2
556 .insert(2, &SparseVector::new(vec![10], vec![2.0]))
557 .unwrap();
558
559 let results = handle2.search(&SparseVector::new(vec![10], vec![1.0]), 10);
560 assert_eq!(results.len(), 2);
561 assert_eq!(results[0].0, 2); assert_eq!(results[1].0, 1); handle2.commit_inner().unwrap();
566 drop(handle2);
567
568 let handle3 = SparseHandle::open(path).unwrap();
569 let results = handle3.search(&SparseVector::new(vec![10], vec![1.0]), 10);
570 assert_eq!(results.len(), 2);
571 assert_eq!(results[0].0, 2);
572
573 cleanup(&p);
574 }
575
576 #[test]
577 fn legacy_fallback() {
578 let p = tmp_path("sparse_mmap_legacy_test");
579 cleanup(&p);
580 let path = p.to_str().unwrap();
581
582 std::fs::create_dir_all(&p).unwrap();
584 let mut index = SparseIndex::new();
585 index.insert(7, &SparseVector::new(vec![1], vec![0.42]));
586 let data = bincode::serialize(&index).unwrap();
587 std::fs::write(p.join(LEGACY_FILE), data).unwrap();
588
589 let handle = SparseHandle::open(path).unwrap();
591 assert_eq!(handle.len(), 1);
592 let results = handle.search(&SparseVector::new(vec![1], vec![1.0]), 10);
593 assert_eq!(results[0].0, 7);
594
595 handle.commit_inner().unwrap();
597 assert!(p.join(MMAP_FILE).exists());
598 assert!(!p.join(LEGACY_FILE).exists());
599
600 cleanup(&p);
601 }
602
603 #[test]
604 fn many_docs_mmap_roundtrip() {
605 let p = tmp_path("sparse_mmap_many_docs_test");
606 cleanup(&p);
607 let path = p.to_str().unwrap();
608
609 let handle = SparseHandle::create(path).unwrap();
610 for i in 0..500u64 {
611 let token = (i % 50) as u32;
612 let weight = (i as f32) / 500.0;
613 handle
614 .insert(
615 i,
616 &SparseVector::new(vec![token, token + 50], vec![weight, weight * 0.5]),
617 )
618 .unwrap();
619 }
620 handle.commit_inner().unwrap();
621 drop(handle);
622
623 let handle2 = SparseHandle::open(path).unwrap();
624 assert_eq!(handle2.len(), 500);
625
626 let results = handle2.search(&SparseVector::new(vec![0, 50], vec![1.0, 1.0]), 5);
627 assert_eq!(results.len(), 5);
628 assert_eq!(results[0].0, 450);
630
631 cleanup(&p);
632 }
633
634 fn test_cache_base() -> PathBuf {
639 std::env::temp_dir().join("sparse_test_cache")
640 }
641
642 #[test]
643 fn blob_store_create_and_search() {
644 let store = Arc::new(MemBlobStore::new());
645 let cb = test_cache_base();
646 let handle = SparseHandle::create_with_store(store.clone(), "test_idx", &cb).unwrap();
647
648 handle
649 .insert(42, &SparseVector::new(vec![1, 2], vec![0.5, 0.3]))
650 .unwrap();
651 handle
652 .insert(99, &SparseVector::new(vec![2, 3], vec![0.8, 0.2]))
653 .unwrap();
654 handle.commit_inner().unwrap();
655
656 assert!(store.exists("Sparse_test_idx", MMAP_FILE).unwrap());
658 assert!(store.exists("Sparse_test_idx", VECTORS_FILE).unwrap());
659 assert!(store.exists("Sparse_test_idx", DIMS_FILE).unwrap());
660 assert_eq!(store.list("Sparse_test_idx").unwrap().len(), 3);
661
662 let results = handle.search(&SparseVector::new(vec![2], vec![1.0]), 10);
664 assert_eq!(results.len(), 2);
665 assert_eq!(results[0].0, 99);
666 }
667
668 #[test]
669 fn blob_store_close_reopen() {
670 let store = Arc::new(MemBlobStore::new());
671 let cb = test_cache_base();
672
673 {
675 let handle = SparseHandle::create_with_store(store.clone(), "reopen_idx", &cb).unwrap();
676 handle
677 .insert(1, &SparseVector::new(vec![10], vec![1.0]))
678 .unwrap();
679 handle
680 .insert(2, &SparseVector::new(vec![10, 20], vec![0.5, 0.8]))
681 .unwrap();
682 handle.commit_inner().unwrap();
683 }
684 let handle2 = SparseHandle::open_with_store(store.clone(), "reopen_idx", &cb).unwrap();
688 assert_eq!(handle2.len(), 2);
689
690 let results = handle2.search(&SparseVector::new(vec![10], vec![1.0]), 10);
691 assert_eq!(results.len(), 2);
692 assert_eq!(results[0].0, 1); assert_eq!(results[1].0, 2); }
695
696 #[test]
697 fn blob_store_mutation_after_reopen() {
698 let store = Arc::new(MemBlobStore::new());
699 let cb = test_cache_base();
700
701 {
702 let handle = SparseHandle::create_with_store(store.clone(), "mut_idx", &cb).unwrap();
703 handle
704 .insert(1, &SparseVector::new(vec![5], vec![1.0]))
705 .unwrap();
706 handle.commit_inner().unwrap();
707 }
708
709 let handle2 = SparseHandle::open_with_store(store.clone(), "mut_idx", &cb).unwrap();
710 handle2
711 .insert(2, &SparseVector::new(vec![5], vec![2.0]))
712 .unwrap();
713 handle2.commit_inner().unwrap();
714
715 drop(handle2);
717 let handle3 = SparseHandle::open_with_store(store.clone(), "mut_idx", &cb).unwrap();
718 assert_eq!(handle3.len(), 2);
719
720 let results = handle3.search(&SparseVector::new(vec![5], vec![1.0]), 10);
721 assert_eq!(results.len(), 2);
722 assert_eq!(results[0].0, 2); assert_eq!(results[1].0, 1); }
725
726 #[test]
727 fn blob_store_delete_and_reopen() {
728 let store = Arc::new(MemBlobStore::new());
729 let cb = test_cache_base();
730
731 {
732 let handle = SparseHandle::create_with_store(store.clone(), "del_idx", &cb).unwrap();
733 handle
734 .insert(1, &SparseVector::new(vec![1], vec![1.0]))
735 .unwrap();
736 handle
737 .insert(2, &SparseVector::new(vec![1], vec![2.0]))
738 .unwrap();
739 handle.commit_inner().unwrap();
740 }
741
742 let handle2 = SparseHandle::open_with_store(store.clone(), "del_idx", &cb).unwrap();
744 assert_eq!(handle2.len(), 2);
745 handle2.remove(1).unwrap();
746 assert_eq!(handle2.len(), 1);
747 handle2.commit_inner().unwrap();
748 drop(handle2);
749
750 let handle3 = SparseHandle::open_with_store(store.clone(), "del_idx", &cb).unwrap();
752 assert_eq!(handle3.len(), 1);
753
754 let results = handle3.search(&SparseVector::new(vec![1], vec![1.0]), 10);
755 assert_eq!(results.len(), 1);
756 assert_eq!(results[0].0, 2);
757 }
758
759 #[test]
760 fn blob_store_multiple_indexes_isolated() {
761 let store = Arc::new(MemBlobStore::new());
762 let cb = test_cache_base();
763
764 let h1 = SparseHandle::create_with_store(store.clone(), "idx_a", &cb).unwrap();
765 let h2 = SparseHandle::create_with_store(store.clone(), "idx_b", &cb).unwrap();
766
767 h1.insert(1, &SparseVector::new(vec![1], vec![1.0]))
768 .unwrap();
769 h1.insert(2, &SparseVector::new(vec![1], vec![0.5]))
770 .unwrap();
771 h2.insert(10, &SparseVector::new(vec![1], vec![3.0]))
772 .unwrap();
773
774 h1.commit_inner().unwrap();
775 h2.commit_inner().unwrap();
776
777 assert_eq!(h1.len(), 2);
778 assert_eq!(h2.len(), 1);
779
780 assert_eq!(store.list("Sparse_idx_a").unwrap().len(), 3);
782 assert_eq!(store.list("Sparse_idx_b").unwrap().len(), 3);
783 }
784
785 #[test]
786 fn blob_store_survives_cache_cleanup() {
787 let store = Arc::new(MemBlobStore::new());
788 let cb = test_cache_base();
789
790 {
791 let handle = SparseHandle::create_with_store(store.clone(), "surv_idx", &cb).unwrap();
792 for i in 0..50u64 {
793 handle
794 .insert(i, &SparseVector::new(vec![(i % 10) as u32], vec![i as f32]))
795 .unwrap();
796 }
797 handle.commit_inner().unwrap();
798 }
799 let handle2 = SparseHandle::open_with_store(store.clone(), "surv_idx", &cb).unwrap();
803 assert_eq!(handle2.len(), 50);
804
805 let results = handle2.search(&SparseVector::new(vec![0], vec![1.0]), 5);
806 assert_eq!(results.len(), 4);
809 assert_eq!(results[0].0, 40);
810 }
811
812 #[test]
813 fn blob_store_search_filtered_after_reopen() {
814 let store = Arc::new(MemBlobStore::new());
815 let cb = test_cache_base();
816
817 {
818 let handle = SparseHandle::create_with_store(store.clone(), "filt_idx", &cb).unwrap();
819 handle
820 .insert(1, &SparseVector::new(vec![1, 2], vec![0.5, 0.3]))
821 .unwrap();
822 handle
823 .insert(2, &SparseVector::new(vec![1, 3], vec![0.9, 0.1]))
824 .unwrap();
825 handle
826 .insert(3, &SparseVector::new(vec![1], vec![0.7]))
827 .unwrap();
828 handle.commit_inner().unwrap();
829 }
830
831 let handle2 = SparseHandle::open_with_store(store.clone(), "filt_idx", &cb).unwrap();
832 let results =
833 handle2.search_filtered(&SparseVector::new(vec![1], vec![1.0]), 10, &[1, 3]);
834 assert_eq!(results.len(), 2);
835 assert_eq!(results[0].0, 3); assert_eq!(results[1].0, 1); }
838}