1use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
15pub struct VectorEntry {
16 pub id: String,
18 pub namespace: String,
20 pub vector: Vec<f32>,
22 pub metadata: HashMap<String, String>,
24 pub created_at: u64,
26}
27
28#[derive(Debug, Clone)]
30pub struct SearchResult {
31 pub id: String,
33 pub namespace: String,
35 pub score: f32,
37 pub metadata: HashMap<String, String>,
39}
40
41#[derive(Debug, Clone)]
43pub struct VectorStoreStats {
44 pub total_vectors: usize,
46 pub namespace_count: usize,
48 pub dimension: Option<usize>,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum StoreError {
56 DimensionMismatch { expected: usize, got: usize },
58 EmptyVector,
60}
61
62impl std::fmt::Display for StoreError {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 StoreError::DimensionMismatch { expected, got } => {
66 write!(f, "dimension mismatch: expected {expected}, got {got}")
67 }
68 StoreError::EmptyVector => write!(f, "vector must not be empty"),
69 }
70 }
71}
72
73impl std::error::Error for StoreError {}
74
75pub struct VectorStore {
81 namespaces: HashMap<String, Vec<VectorEntry>>,
83 dimension: Option<usize>,
85}
86
87fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
93 let mut dot = 0.0_f32;
94 let mut norm_a = 0.0_f32;
95 let mut norm_b = 0.0_f32;
96 for (&x, &y) in a.iter().zip(b.iter()) {
97 dot += x * y;
98 norm_a += x * x;
99 norm_b += y * y;
100 }
101 let denom = norm_a.sqrt() * norm_b.sqrt();
102 if denom == 0.0 {
103 0.0
104 } else {
105 (dot / denom).clamp(-1.0, 1.0)
106 }
107}
108
109impl Default for VectorStore {
110 fn default() -> Self {
111 Self::new()
112 }
113}
114
115impl VectorStore {
116 pub fn new() -> Self {
118 Self {
119 namespaces: HashMap::new(),
120 dimension: None,
121 }
122 }
123
124 pub fn with_dimension(dim: usize) -> Self {
126 Self {
127 namespaces: HashMap::new(),
128 dimension: Some(dim),
129 }
130 }
131
132 fn validate_vector(&self, vector: &[f32]) -> Result<(), StoreError> {
135 if vector.is_empty() {
136 return Err(StoreError::EmptyVector);
137 }
138 if let Some(dim) = self.dimension {
139 if vector.len() != dim {
140 return Err(StoreError::DimensionMismatch {
141 expected: dim,
142 got: vector.len(),
143 });
144 }
145 }
146 Ok(())
147 }
148
149 pub fn upsert(&mut self, entry: VectorEntry) -> Result<bool, StoreError> {
159 self.validate_vector(&entry.vector)?;
160 let entries = self.namespaces.entry(entry.namespace.clone()).or_default();
161 if let Some(existing) = entries.iter_mut().find(|e| e.id == entry.id) {
162 *existing = entry;
163 return Ok(false);
164 }
165 entries.push(entry);
166 Ok(true)
167 }
168
169 pub fn delete(&mut self, namespace: &str, id: &str) -> bool {
173 match self.namespaces.get_mut(namespace) {
174 Some(entries) => {
175 let before = entries.len();
176 entries.retain(|e| e.id != id);
177 entries.len() < before
178 }
179 None => false,
180 }
181 }
182
183 pub fn get(&self, namespace: &str, id: &str) -> Option<&VectorEntry> {
185 self.namespaces
186 .get(namespace)
187 .and_then(|entries| entries.iter().find(|e| e.id == id))
188 }
189
190 pub fn contains(&self, namespace: &str, id: &str) -> bool {
192 self.get(namespace, id).is_some()
193 }
194
195 pub fn search(&self, namespace: &str, query: &[f32], top_k: usize) -> Vec<SearchResult> {
200 let entries = match self.namespaces.get(namespace) {
201 Some(e) => e,
202 None => return Vec::new(),
203 };
204 let mut scored: Vec<(f32, &VectorEntry)> = entries
205 .iter()
206 .filter(|e| e.vector.len() == query.len())
207 .map(|e| (cosine_similarity(&e.vector, query), e))
208 .collect();
209 scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
210 scored
211 .into_iter()
212 .take(top_k)
213 .map(|(score, entry)| SearchResult {
214 id: entry.id.clone(),
215 namespace: entry.namespace.clone(),
216 score,
217 metadata: entry.metadata.clone(),
218 })
219 .collect()
220 }
221
222 pub fn search_all_namespaces(&self, query: &[f32], top_k: usize) -> Vec<SearchResult> {
224 let mut scored: Vec<(f32, &VectorEntry)> = self
225 .namespaces
226 .values()
227 .flat_map(|entries| entries.iter())
228 .filter(|e| e.vector.len() == query.len())
229 .map(|e| (cosine_similarity(&e.vector, query), e))
230 .collect();
231 scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
232 scored
233 .into_iter()
234 .take(top_k)
235 .map(|(score, entry)| SearchResult {
236 id: entry.id.clone(),
237 namespace: entry.namespace.clone(),
238 score,
239 metadata: entry.metadata.clone(),
240 })
241 .collect()
242 }
243
244 pub fn list(&self, namespace: &str) -> Vec<&VectorEntry> {
249 match self.namespaces.get(namespace) {
250 Some(entries) => entries.iter().collect(),
251 None => Vec::new(),
252 }
253 }
254
255 pub fn delete_namespace(&mut self, namespace: &str) -> usize {
261 match self.namespaces.remove(namespace) {
262 Some(entries) => entries.len(),
263 None => 0,
264 }
265 }
266
267 pub fn stats(&self) -> VectorStoreStats {
271 let total_vectors: usize = self.namespaces.values().map(|v| v.len()).sum();
272 let namespace_count = self.namespaces.values().filter(|v| !v.is_empty()).count();
273 let dimension = self.dimension.or_else(|| {
275 self.namespaces
276 .values()
277 .flat_map(|v| v.iter())
278 .next()
279 .map(|e| e.vector.len())
280 });
281 VectorStoreStats {
282 total_vectors,
283 namespace_count,
284 dimension,
285 }
286 }
287}
288
289#[cfg(test)]
294mod tests {
295 use super::*;
296
297 fn entry(id: &str, ns: &str, v: Vec<f32>) -> VectorEntry {
298 VectorEntry {
299 id: id.to_string(),
300 namespace: ns.to_string(),
301 vector: v,
302 metadata: HashMap::new(),
303 created_at: 0,
304 }
305 }
306
307 fn unit_vec(dim: usize, one_at: usize) -> Vec<f32> {
308 let mut v = vec![0.0_f32; dim];
309 v[one_at] = 1.0;
310 v
311 }
312
313 #[test]
316 fn test_upsert_new_returns_true() {
317 let mut store = VectorStore::new();
318 let is_new = store
319 .upsert(entry("e1", "ns", vec![1.0, 0.0]))
320 .expect("should succeed");
321 assert!(is_new);
322 }
323
324 #[test]
325 fn test_upsert_update_returns_false() {
326 let mut store = VectorStore::new();
327 store
328 .upsert(entry("e1", "ns", vec![1.0, 0.0]))
329 .expect("should succeed");
330 let is_new = store
331 .upsert(entry("e1", "ns", vec![0.0, 1.0]))
332 .expect("should succeed");
333 assert!(!is_new);
334 }
335
336 #[test]
337 fn test_upsert_update_replaces_vector() {
338 let mut store = VectorStore::new();
339 store
340 .upsert(entry("e1", "ns", vec![1.0, 0.0]))
341 .expect("should succeed");
342 store
343 .upsert(entry("e1", "ns", vec![0.0, 1.0]))
344 .expect("should succeed");
345 let got = store.get("ns", "e1").expect("should succeed");
346 assert_eq!(got.vector, vec![0.0, 1.0]);
347 }
348
349 #[test]
350 fn test_upsert_empty_vector_errors() {
351 let mut store = VectorStore::new();
352 let res = store.upsert(entry("e1", "ns", vec![]));
353 assert_eq!(res, Err(StoreError::EmptyVector));
354 }
355
356 #[test]
357 fn test_upsert_dimension_mismatch_errors() {
358 let mut store = VectorStore::with_dimension(3);
359 let res = store.upsert(entry("e1", "ns", vec![1.0, 2.0]));
360 assert_eq!(
361 res,
362 Err(StoreError::DimensionMismatch {
363 expected: 3,
364 got: 2
365 })
366 );
367 }
368
369 #[test]
370 fn test_upsert_correct_dimension_ok() {
371 let mut store = VectorStore::with_dimension(2);
372 assert!(store.upsert(entry("e1", "ns", vec![1.0, 0.0])).is_ok());
373 }
374
375 #[test]
378 fn test_delete_existing() {
379 let mut store = VectorStore::new();
380 store
381 .upsert(entry("e1", "ns", vec![1.0]))
382 .expect("should succeed");
383 assert!(store.delete("ns", "e1"));
384 assert!(store.get("ns", "e1").is_none());
385 }
386
387 #[test]
388 fn test_delete_nonexistent_returns_false() {
389 let mut store = VectorStore::new();
390 assert!(!store.delete("ns", "ghost"));
391 }
392
393 #[test]
396 fn test_get_existing() {
397 let mut store = VectorStore::new();
398 store
399 .upsert(entry("e1", "ns", vec![1.0, 2.0]))
400 .expect("should succeed");
401 assert!(store.get("ns", "e1").is_some());
402 }
403
404 #[test]
405 fn test_get_nonexistent_none() {
406 let store = VectorStore::new();
407 assert!(store.get("ns", "missing").is_none());
408 }
409
410 #[test]
413 fn test_contains_true() {
414 let mut store = VectorStore::new();
415 store
416 .upsert(entry("x", "ns", vec![1.0]))
417 .expect("should succeed");
418 assert!(store.contains("ns", "x"));
419 }
420
421 #[test]
422 fn test_contains_false() {
423 let store = VectorStore::new();
424 assert!(!store.contains("ns", "x"));
425 }
426
427 #[test]
430 fn test_search_sorted_by_score_descending() {
431 let mut store = VectorStore::new();
432 store
434 .upsert(entry("e1", "ns", unit_vec(3, 0)))
435 .expect("should succeed");
436 store
438 .upsert(entry("e2", "ns", unit_vec(3, 1)))
439 .expect("should succeed");
440 let query = unit_vec(3, 0);
441 let results = store.search("ns", &query, 2);
442 assert_eq!(results.len(), 2);
443 assert!(results[0].score >= results[1].score);
444 assert_eq!(results[0].id, "e1");
445 }
446
447 #[test]
448 fn test_search_top_k_limit() {
449 let mut store = VectorStore::new();
450 for i in 0..10 {
451 store
452 .upsert(entry(&i.to_string(), "ns", vec![i as f32]))
453 .expect("should succeed");
454 }
455 let results = store.search("ns", &[5.0_f32], 3);
456 assert_eq!(results.len(), 3);
457 }
458
459 #[test]
460 fn test_search_same_vector_max_score() {
461 let mut store = VectorStore::new();
462 let v = vec![1.0_f32, 1.0, 1.0];
463 store
464 .upsert(entry("e", "ns", v.clone()))
465 .expect("should succeed");
466 let results = store.search("ns", &v, 1);
467 assert_eq!(results.len(), 1);
468 assert!((results[0].score - 1.0).abs() < 1e-6);
469 }
470
471 #[test]
472 fn test_search_empty_namespace_returns_empty() {
473 let store = VectorStore::new();
474 let results = store.search("missing-ns", &[1.0_f32], 5);
475 assert!(results.is_empty());
476 }
477
478 #[test]
481 fn test_search_all_namespaces_cross_namespace() {
482 let mut store = VectorStore::new();
483 store
484 .upsert(entry("a", "ns1", vec![1.0_f32, 0.0]))
485 .expect("should succeed");
486 store
487 .upsert(entry("b", "ns2", vec![0.0_f32, 1.0]))
488 .expect("should succeed");
489 let results = store.search_all_namespaces(&[1.0_f32, 0.0], 2);
490 assert_eq!(results.len(), 2);
491 assert_eq!(results[0].id, "a"); }
493
494 #[test]
495 fn test_search_all_namespaces_top_k() {
496 let mut store = VectorStore::new();
497 for i in 0..5 {
498 store
499 .upsert(entry(&format!("e{i}"), &format!("ns{i}"), vec![i as f32]))
500 .expect("should succeed");
501 }
502 let results = store.search_all_namespaces(&[2.0_f32], 2);
503 assert_eq!(results.len(), 2);
504 }
505
506 #[test]
509 fn test_list_all_in_namespace() {
510 let mut store = VectorStore::new();
511 store
512 .upsert(entry("a", "ns", vec![1.0]))
513 .expect("should succeed");
514 store
515 .upsert(entry("b", "ns", vec![2.0]))
516 .expect("should succeed");
517 let listed = store.list("ns");
518 assert_eq!(listed.len(), 2);
519 }
520
521 #[test]
522 fn test_list_nonexistent_namespace_empty() {
523 let store = VectorStore::new();
524 assert!(store.list("ghost").is_empty());
525 }
526
527 #[test]
530 fn test_delete_namespace_returns_count() {
531 let mut store = VectorStore::new();
532 store
533 .upsert(entry("a", "ns", vec![1.0]))
534 .expect("should succeed");
535 store
536 .upsert(entry("b", "ns", vec![2.0]))
537 .expect("should succeed");
538 assert_eq!(store.delete_namespace("ns"), 2);
539 }
540
541 #[test]
542 fn test_delete_namespace_nonexistent_returns_zero() {
543 let mut store = VectorStore::new();
544 assert_eq!(store.delete_namespace("ghost"), 0);
545 }
546
547 #[test]
548 fn test_delete_namespace_removes_entries() {
549 let mut store = VectorStore::new();
550 store
551 .upsert(entry("a", "ns", vec![1.0]))
552 .expect("should succeed");
553 store.delete_namespace("ns");
554 assert!(store.list("ns").is_empty());
555 }
556
557 #[test]
560 fn test_stats_empty_store() {
561 let store = VectorStore::new();
562 let s = store.stats();
563 assert_eq!(s.total_vectors, 0);
564 assert_eq!(s.namespace_count, 0);
565 assert!(s.dimension.is_none());
566 }
567
568 #[test]
569 fn test_stats_counts_correctly() {
570 let mut store = VectorStore::new();
571 store
572 .upsert(entry("a", "ns1", vec![1.0, 2.0]))
573 .expect("should succeed");
574 store
575 .upsert(entry("b", "ns1", vec![3.0, 4.0]))
576 .expect("should succeed");
577 store
578 .upsert(entry("c", "ns2", vec![5.0, 6.0]))
579 .expect("should succeed");
580 let s = store.stats();
581 assert_eq!(s.total_vectors, 3);
582 assert_eq!(s.namespace_count, 2);
583 assert_eq!(s.dimension, Some(2));
584 }
585
586 #[test]
587 fn test_stats_with_configured_dimension() {
588 let store = VectorStore::with_dimension(128);
589 let s = store.stats();
590 assert_eq!(s.dimension, Some(128));
591 }
592
593 #[test]
596 fn test_upsert_multiple_namespaces() {
597 let mut store = VectorStore::new();
598 store
599 .upsert(entry("a", "ns1", vec![1.0]))
600 .expect("should succeed");
601 store
602 .upsert(entry("b", "ns2", vec![2.0]))
603 .expect("should succeed");
604 assert!(store.contains("ns1", "a"));
605 assert!(store.contains("ns2", "b"));
606 }
607
608 #[test]
609 fn test_delete_from_wrong_namespace() {
610 let mut store = VectorStore::new();
611 store
612 .upsert(entry("a", "ns1", vec![1.0]))
613 .expect("should succeed");
614 assert!(!store.delete("ns2", "a")); assert!(store.contains("ns1", "a")); }
617
618 #[test]
619 fn test_search_returns_correct_namespace() {
620 let mut store = VectorStore::new();
621 store
622 .upsert(entry("a", "ns1", vec![1.0_f32, 0.0]))
623 .expect("should succeed");
624 let results = store.search("ns1", &[1.0_f32, 0.0], 1);
625 assert_eq!(results[0].namespace, "ns1");
626 }
627
628 #[test]
629 fn test_search_all_namespaces_empty_store() {
630 let store = VectorStore::new();
631 assert!(store.search_all_namespaces(&[1.0_f32], 5).is_empty());
632 }
633
634 #[test]
635 fn test_metadata_stored_and_retrieved() {
636 let mut store = VectorStore::new();
637 let mut meta = HashMap::new();
638 meta.insert("source".into(), "test".into());
639 let mut e = entry("e1", "ns", vec![1.0]);
640 e.metadata = meta;
641 store.upsert(e).expect("should succeed");
642 let got = store.get("ns", "e1").expect("should succeed");
643 assert_eq!(got.metadata.get("source").map(|s| s.as_str()), Some("test"));
644 }
645
646 #[test]
647 fn test_created_at_stored() {
648 let mut store = VectorStore::new();
649 let mut e = entry("e1", "ns", vec![1.0]);
650 e.created_at = 12345678;
651 store.upsert(e).expect("should succeed");
652 assert_eq!(
653 store.get("ns", "e1").expect("should succeed").created_at,
654 12345678
655 );
656 }
657
658 #[test]
659 fn test_store_error_display_empty_vector() {
660 let e = StoreError::EmptyVector;
661 assert!(!e.to_string().is_empty());
662 }
663
664 #[test]
665 fn test_store_error_display_dimension_mismatch() {
666 let e = StoreError::DimensionMismatch {
667 expected: 3,
668 got: 5,
669 };
670 let s = e.to_string();
671 assert!(s.contains("3") && s.contains("5"));
672 }
673
674 #[test]
675 fn test_stats_namespace_count_after_delete() {
676 let mut store = VectorStore::new();
677 store
678 .upsert(entry("a", "ns1", vec![1.0]))
679 .expect("should succeed");
680 store
681 .upsert(entry("b", "ns2", vec![1.0]))
682 .expect("should succeed");
683 store.delete_namespace("ns1");
684 let s = store.stats();
685 assert_eq!(s.namespace_count, 1);
686 }
687
688 #[test]
689 fn test_search_scores_in_range() {
690 let mut store = VectorStore::new();
691 store
692 .upsert(entry("a", "ns", vec![1.0_f32, 0.0]))
693 .expect("should succeed");
694 store
695 .upsert(entry("b", "ns", vec![0.0_f32, 1.0]))
696 .expect("should succeed");
697 let results = store.search("ns", &[0.7_f32, 0.7], 2);
698 for r in &results {
699 assert!(r.score >= -1.0 && r.score <= 1.0);
700 }
701 }
702
703 #[test]
704 fn test_search_returns_metadata() {
705 let mut store = VectorStore::new();
706 let mut e = entry("e1", "ns", vec![1.0_f32, 0.0]);
707 e.metadata.insert("key".into(), "val".into());
708 store.upsert(e).expect("should succeed");
709 let results = store.search("ns", &[1.0_f32, 0.0], 1);
710 assert_eq!(
711 results[0].metadata.get("key").map(|s| s.as_str()),
712 Some("val")
713 );
714 }
715
716 #[test]
717 fn test_upsert_different_ids_same_namespace() {
718 let mut store = VectorStore::new();
719 store
720 .upsert(entry("a", "ns", vec![1.0]))
721 .expect("should succeed");
722 store
723 .upsert(entry("b", "ns", vec![2.0]))
724 .expect("should succeed");
725 store
726 .upsert(entry("c", "ns", vec![3.0]))
727 .expect("should succeed");
728 assert_eq!(store.list("ns").len(), 3);
729 }
730
731 #[test]
732 fn test_delete_reduces_list_count() {
733 let mut store = VectorStore::new();
734 store
735 .upsert(entry("a", "ns", vec![1.0]))
736 .expect("should succeed");
737 store
738 .upsert(entry("b", "ns", vec![2.0]))
739 .expect("should succeed");
740 store.delete("ns", "a");
741 assert_eq!(store.list("ns").len(), 1);
742 }
743
744 #[test]
745 fn test_search_all_namespaces_result_ids_correct() {
746 let mut store = VectorStore::new();
747 store
748 .upsert(entry("best", "ns1", vec![1.0_f32, 0.0]))
749 .expect("should succeed");
750 store
751 .upsert(entry("other", "ns2", vec![0.0_f32, 1.0]))
752 .expect("should succeed");
753 let results = store.search_all_namespaces(&[1.0_f32, 0.0], 1);
754 assert_eq!(results[0].id, "best");
755 }
756
757 #[test]
758 fn test_cosine_similarity_opposite_vectors() {
759 let mut store = VectorStore::new();
760 store
761 .upsert(entry("a", "ns", vec![1.0_f32, 0.0]))
762 .expect("should succeed");
763 store
764 .upsert(entry("b", "ns", vec![-1.0_f32, 0.0]))
765 .expect("should succeed");
766 let results = store.search("ns", &[1.0_f32, 0.0], 2);
767 assert_eq!(results[0].id, "a");
769 assert!(results[0].score > results[1].score);
770 }
771
772 #[test]
773 fn test_with_dimension_rejects_extra_dims() {
774 let mut store = VectorStore::with_dimension(2);
775 let res = store.upsert(entry("e", "ns", vec![1.0, 2.0, 3.0]));
776 assert!(matches!(
777 res,
778 Err(StoreError::DimensionMismatch {
779 expected: 2,
780 got: 3
781 })
782 ));
783 }
784
785 #[test]
786 fn test_upsert_returns_new_flag_consistently() {
787 let mut store = VectorStore::new();
788 let r1 = store
789 .upsert(entry("e", "ns", vec![1.0]))
790 .expect("should succeed");
791 let r2 = store
792 .upsert(entry("e", "ns", vec![2.0]))
793 .expect("should succeed");
794 assert!(r1); assert!(!r2); }
797
798 #[test]
799 fn test_stats_total_includes_all_namespaces() {
800 let mut store = VectorStore::new();
801 for i in 0..5 {
802 store
803 .upsert(entry(&i.to_string(), &format!("ns{i}"), vec![i as f32]))
804 .expect("should succeed");
805 }
806 assert_eq!(store.stats().total_vectors, 5);
807 }
808
809 #[test]
810 fn test_get_after_update_returns_new_vector() {
811 let mut store = VectorStore::new();
812 store
813 .upsert(entry("e", "ns", vec![1.0, 0.0]))
814 .expect("should succeed");
815 store
816 .upsert(entry("e", "ns", vec![0.0, 1.0]))
817 .expect("should succeed");
818 let got = store.get("ns", "e").expect("should succeed");
819 assert_eq!(got.vector, vec![0.0_f32, 1.0]);
820 }
821
822 #[test]
823 fn test_search_zero_top_k() {
824 let mut store = VectorStore::new();
825 store
826 .upsert(entry("a", "ns", vec![1.0_f32]))
827 .expect("should succeed");
828 let results = store.search("ns", &[1.0_f32], 0);
829 assert!(results.is_empty());
830 }
831}