Skip to main content

memvdb/
db.rs

1//! # Database Module
2//!
3//! This module contains the core database functionality for MemVDB, including
4//! collection management, embedding storage, and similarity search operations.
5//!
6//! ## Key Components
7//!
8//! - [`CacheDB`]: The main database structure that manages collections
9//! - [`Collection`]: A container for embeddings with a specific dimensionality and distance metric
10//! - [`Embedding`]: Individual vector entries with optional metadata
11//! - [`Distance`]: Supported distance metrics for similarity calculations
12//! - [`Error`]: Error types for database operations
13
14use crate::similarity::{ScoreIndex, get_cache_attr, get_distance_fn, normalize};
15use anyhow::Result;
16use log::{debug, error, info};
17use rayon::prelude::*;
18use std::collections::HashSet;
19use std::collections::hash_map::DefaultHasher;
20use std::collections::{BinaryHeap, HashMap};
21use std::fs::File;
22use std::hash::{Hash, Hasher};
23use std::io::{BufReader, Write};
24
25use serde::{Deserialize, Serialize};
26
27/// The main in-memory vector database structure.
28///
29/// `CacheDB` manages multiple collections of embeddings, each with their own
30/// dimensionality and distance metric. It provides thread-safe operations
31/// for creating, updating, and querying vector collections.
32///
33/// # Examples
34///
35/// ```rust
36/// use memvdb::{CacheDB, Distance};
37///
38/// let mut db = CacheDB::new();
39/// db.create_collection("images".to_string(), 512, Distance::Cosine).unwrap();
40/// assert!(db.get_collection("images").is_some());
41/// ```
42#[derive(Debug, serde::Serialize, serde::Deserialize)]
43pub struct CacheDB {
44    /// HashMap containing all collections, indexed by collection name
45    pub collections: HashMap<String, Collection>,
46}
47
48/// Result of a similarity search operation.
49///
50/// Contains the similarity score and the corresponding embedding.
51/// Results are typically returned in order of similarity (best matches first).
52///
53/// # Fields
54///
55/// * `score` - The similarity score (interpretation depends on distance metric)
56/// * `embedding` - The matching embedding with its metadata
57#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
58pub struct SimilarityResult {
59    /// Similarity score - lower values indicate closer matches for distance metrics
60    pub score: f32,
61    /// The embedding that matched the query
62    pub embedding: Embedding,
63}
64
65/// A collection of embeddings with a specific dimensionality and distance metric.
66///
67/// Collections ensure that all embeddings have the same vector dimensionality
68/// and use a consistent distance metric for similarity calculations.
69///
70/// # Examples
71///
72/// ```rust
73/// use memvdb::{Collection, Distance};
74///
75/// let collection = Collection {
76///     dimension: 128,
77///     distance: Distance::Euclidean,
78///     embeddings: Vec::new(),
79/// };
80/// assert_eq!(collection.dimension, 128);
81/// ```
82#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
83pub struct Collection {
84    /// The required dimensionality for all vectors in this collection
85    pub dimension: usize,
86    /// The distance metric used for similarity calculations
87    pub distance: Distance,
88    /// Vector of all embeddings stored in this collection
89    #[serde(default)]
90    pub embeddings: Vec<Embedding>,
91}
92
93/// An individual embedding (vector) with associated metadata.
94///
95/// Embeddings consist of a unique identifier, the vector data, and optional metadata.
96/// The ID can be a composite key using multiple string fields for flexibility.
97///
98/// # Examples
99///
100/// ```rust
101/// use memvdb::Embedding;
102/// use std::collections::HashMap;
103///
104/// let mut id = HashMap::new();
105/// id.insert("doc_id".to_string(), "document_123".to_string());
106///
107/// let mut metadata = HashMap::new();
108/// metadata.insert("title".to_string(), "Example Document".to_string());
109///
110/// let embedding = Embedding {
111///     id,
112///     vector: vec![0.1, 0.2, 0.3],
113///     metadata: Some(metadata),
114/// };
115/// ```
116#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
117pub struct Embedding {
118    /// Unique identifier for this embedding (can be composite)
119    pub id: HashMap<String, String>,
120    /// The vector data as floating-point numbers
121    pub vector: Vec<f32>,
122    /// Optional metadata associated with this embedding
123    pub metadata: Option<HashMap<String, String>>,
124}
125
126/// Supported distance metrics for similarity calculations.
127///
128/// Each metric is optimized for different types of vector data and use cases:
129///
130/// - **Euclidean**: Traditional geometric distance, good for spatial data
131/// - **Cosine**: Measures angle between vectors, ideal for text embeddings
132/// - **Dot Product**: Efficient for normalized vectors and neural network outputs
133///
134/// # Examples
135///
136/// ```rust
137/// use memvdb::Distance;
138///
139/// let metric = Distance::Cosine;
140/// assert_eq!(metric, Distance::Cosine);
141/// assert_ne!(metric, Distance::Euclidean);
142/// ```
143#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
144pub enum Distance {
145    /// Euclidean (L2) distance - sqrt(sum((a_i - b_i)^2))
146    #[serde(rename = "euclidean")]
147    Euclidean,
148    /// Cosine similarity - measures angle between vectors
149    #[serde(rename = "cosine")]
150    Cosine,
151    /// Dot product - sum(a_i * b_i)
152    #[serde(rename = "dot")]
153    DotProduct,
154}
155
156/// Error types for database operations.
157///
158/// These errors cover all possible failure modes in the database operations,
159/// from validation errors to resource not found conditions.
160#[derive(Debug, thiserror::Error, PartialEq)]
161pub enum Error {
162    #[error("Collection already exists")]
163    UniqueViolation,
164
165    #[error("Embedding already exists")]
166    EmbeddingUniqueViolation,
167
168    #[error("Collection doesn't exist")]
169    NotFound,
170
171    #[error("The dimension of the vector doesn't match the dimension of the collection")]
172    DimensionMismatch,
173
174    #[error("Failed to initialize the logger")]
175    LoggerInitializationError,
176}
177
178#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
179/// Configuration for creating a new collection.
180///
181/// This struct encapsulates all the parameters needed to create a new collection
182/// in the database.
183///
184/// # Examples
185///
186/// ```rust
187/// use memvdb::{CreateCollectionStruct, Distance};
188///
189/// let config = CreateCollectionStruct {
190///     collection_name: "documents".to_string(),
191///     dimension: 768,
192///     distance: Distance::Cosine,
193/// };
194/// ```
195pub struct CreateCollectionStruct {
196    /// Name of the collection to create
197    pub collection_name: String,
198    /// Vector dimensionality for this collection
199    pub dimension: usize,
200    /// Distance metric to use for similarity calculations
201    pub distance: Distance,
202}
203
204#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
205
206/// Configuration for inserting a single embedding.
207///
208/// Contains the collection name and the embedding to insert.
209pub struct InsertEmbeddingStruct {
210    /// Name of the target collection
211    pub collection_name: String,
212    /// The embedding to insert
213    pub embedding: Embedding,
214}
215
216#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
217/// Configuration for collection operations.
218///
219/// Simple struct containing just the collection name for operations
220/// that only need to identify a collection.
221pub struct CollectionHandlerStruct {
222    /// Name of the target collection
223    pub collection_name: String,
224}
225
226#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
227/// Configuration for batch embedding operations.
228///
229/// Used for inserting or updating multiple embeddings at once,
230/// which is more efficient than individual operations.
231pub struct BatchInsertEmbeddingsStruct {
232    /// Name of the target collection
233    pub collection_name: String,
234    /// Vector of embeddings to insert/update
235    pub embeddings: Vec<Embedding>,
236}
237
238#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
239/// Configuration for similarity search operations.
240///
241/// Contains all parameters needed to perform a k-nearest neighbors search
242/// within a specific collection.
243///
244/// # Examples
245///
246/// ```rust
247/// use memvdb::GetSimilarityStruct;
248///
249/// let query = GetSimilarityStruct {
250///     collection_name: "documents".to_string(),
251///     query_vector: vec![0.1, 0.2, 0.3],
252///     k: 10,
253/// };
254/// ```
255pub struct GetSimilarityStruct {
256    /// Name of the collection to search
257    pub collection_name: String,
258    /// Query vector to find similarities for
259    pub query_vector: Vec<f32>,
260    /// Number of nearest neighbors to return
261    pub k: usize,
262}
263
264// Define a function to hash a HashMap<String, String>.
265// A custom hash function, you ensure that the hash value is based solely on the content of the HashMap
266pub fn hash_map_id(id: &HashMap<String, String>) -> u64 {
267    let mut hasher = DefaultHasher::new();
268    for (key, value) in id {
269        key.hash(&mut hasher);
270        value.hash(&mut hasher);
271    }
272    hasher.finish()
273}
274
275/// A collection that stores embeddings and handles similarity calculations.
276impl Collection {
277    /// Calculate similarity results for a given query and number of results (k).
278    ///
279    /// # Arguments
280    ///
281    /// * `query`: The query vector for which to calculate similarity.
282    /// * `k`: The number of top similar results to return.
283    ///
284    /// # Returns
285    ///
286    /// A vector of similarity results, sorted by their similarity scores.
287    pub fn get_similarity(&self, query: &[f32], k: usize) -> Vec<SimilarityResult> {
288        debug!(
289            "Starting similarity computation with query vector of length {} and top k = {}",
290            query.len(),
291            k
292        );
293
294        // Prepare cache attributes and distance function based on collection's distance metric.
295        let memo_attr = get_cache_attr(self.distance, query);
296        let distance_fn = get_distance_fn(self.distance);
297
298        debug!("Using distance function: {:?}", self.distance);
299        debug!("Memo attributes for distance function: {:?}", memo_attr);
300
301        // Calculate similarity scores for each embedding in parallel.
302        let scores = self
303            .embeddings
304            .par_iter()
305            .enumerate()
306            .map(|(index, embedding)| {
307                let score = distance_fn(&embedding.vector, query, memo_attr);
308                ScoreIndex { score, index }
309            })
310            .collect::<Vec<_>>();
311        debug!("Calculated {} similarity scores", scores.len());
312        // Use a binary heap to efficiently find the top k similarity results.
313        let mut heap = BinaryHeap::new();
314        for score_index in scores {
315            // Only keep top k results in the heap.
316            if heap.len() < k || score_index < *heap.peek().unwrap() {
317                heap.push(score_index);
318                if heap.len() > k {
319                    heap.pop();
320                }
321            }
322        }
323        debug!("Top k heap size: {}", heap.len());
324
325        // Convert the heap into a sorted vector and map each score to a SimilarityResult.
326        let result: Vec<SimilarityResult> = heap
327            .into_sorted_vec()
328            .into_iter()
329            .map(|ScoreIndex { score, index }| SimilarityResult {
330                score,
331                embedding: self.embeddings[index].clone(),
332            })
333            .collect();
334        info!(
335            "Similarity computed successfully'{}' ",
336            format!("{:?}", result)
337        );
338        result
339    }
340}
341
342/// Database management functionality for collections of embeddings.
343impl CacheDB {
344    /// Initialize a new CacheDB instance.
345    pub fn new() -> Self {
346        Self {
347            collections: HashMap::new(),
348        }
349    }
350    /// Create a new collection in the database.
351    ///
352    /// # Arguments
353    ///
354    /// * `name`: The name of the collection to create.
355    /// * `dimension`: The dimension of the embeddings in the collection.
356    /// * `distance`: The distance metric to use for similarity calculations.
357    ///
358    /// # Returns
359    ///
360    /// A result containing the new collection or an error if a collection with the same name already exists.
361    pub fn create_collection(
362        &mut self,
363        name: String,
364        dimension: usize,
365        distance: Distance,
366    ) -> Result<Collection, Error> {
367        // Check if a collection with the same name already exists.
368        if self.collections.contains_key(&name) {
369            error!("Collection: '{}', already exists", name);
370            return Err(Error::UniqueViolation);
371        }
372
373        // Create a new collection and add it to the database.
374        let collection = Collection {
375            dimension,
376            distance,
377            embeddings: Vec::new(),
378        };
379        self.collections.insert(name.clone(), collection.clone());
380
381        info!(
382            "Created new collection with name: '{}', dimension: '{}', distance: '{:?}'",
383            name, dimension, distance
384        );
385        Ok(collection)
386    }
387
388    /// Delete a collection from the database.
389    ///
390    /// # Arguments
391    ///
392    /// * `name`: The name of the collection to delete.
393    ///
394    /// # Returns
395    ///
396    /// A result indicating success or an error if the collection was not found.
397    pub fn delete_collection(&mut self, name: &str) -> Result<(), Error> {
398        // Check if the collection exists before attempting to delete it.
399        if !self.collections.contains_key(name) {
400            error!("Collection name: '{}', does not exist", name);
401            return Err(Error::NotFound);
402        }
403
404        // Remove the collection from the database.
405        self.collections.remove(name);
406
407        info!("Deleted collection: '{}'", name);
408        Ok(())
409    }
410
411    /// Insert a new embedding into a specified collection.
412    ///
413    /// # Arguments
414    ///
415    /// * `collection_name`: The name of the collection to insert the embedding into.
416    /// * `embedding`: The embedding to insert.
417    ///
418    /// # Returns
419    ///
420    /// A result indicating success or an error if the collection was not found, the embedding is a duplicate, or the embedding dimension does not match the collection.
421    pub fn insert_into_collection(
422        &mut self,
423        collection_name: &str,
424        mut embedding: Embedding,
425    ) -> Result<(), Error> {
426        // Get the collection to insert the embedding into.
427        let collection = self
428            .collections
429            .get_mut(collection_name)
430            .ok_or(Error::NotFound)?;
431
432        // Create a HashSet to track unique hashed IDs.
433        let mut unique_ids: HashSet<u64> = collection
434            .embeddings
435            .iter()
436            .map(|e| hash_map_id(&e.id))
437            .collect();
438
439        // Check for duplicate embeddings by hashed ID.
440        if !unique_ids.insert(hash_map_id(&embedding.id)) {
441            error!(
442                "Embedding with ID '{}' already exists in collection '{}'",
443                format!("{:?}", embedding.id),
444                collection_name
445            );
446            return Err(Error::EmbeddingUniqueViolation);
447        }
448
449        // Check if the embedding's dimension matches the collection's dimension.
450        if embedding.vector.len() != collection.dimension {
451            error!(
452                "Dimension mismatch: embedding vector length is '{}' but collection '{}' expects dimension '{}'",
453                embedding.vector.len(),
454                collection_name,
455                collection.dimension
456            );
457            return Err(Error::DimensionMismatch);
458        }
459
460        // Normalize the embedding vector if using cosine distance for more efficient calculations.
461        if collection.distance == Distance::Cosine {
462            embedding.vector = normalize(&embedding.vector);
463        }
464
465        // Add the embedding to the collection.
466        collection.embeddings.push(embedding.clone());
467
468        info!(
469            "Embedding: '{:?}', successfully inserted into collection '{}'",
470            embedding, collection_name
471        );
472        Ok(())
473    }
474
475    /// Update a collection with new embeddings.
476    ///
477    /// # Arguments
478    ///
479    /// * `collection_name`: The name of the collection to update.
480    /// * `new_embeddings`: A vector of new embeddings to add to the collection.
481    ///
482    /// # Returns
483    ///
484    /// A result indicating success or an error if the collection was not found, there are duplicate embeddings, or the embedding dimensions do not match the collection's dimension.
485    pub fn update_collection(
486        &mut self,
487        collection_name: &str,
488        mut new_embeddings: Vec<Embedding>,
489    ) -> Result<(), Error> {
490        // Get the collection to update.
491        let collection = self
492            .collections
493            .get_mut(collection_name)
494            .ok_or(Error::NotFound)?;
495
496        // Iterate through each new embedding.
497        for embedding in &mut new_embeddings {
498            // Create a HashSet to track unique hashed IDs.
499            let mut unique_ids: HashSet<u64> = collection
500                .embeddings
501                .iter()
502                .map(|e| hash_map_id(&e.id))
503                .collect();
504
505            // Check for duplicate embeddings by hashed ID.
506            if !unique_ids.insert(hash_map_id(&embedding.id)) {
507                error!(
508                    "Embedding with ID '{}' already exists in collection '{}'",
509                    format!("{:?}", embedding.id),
510                    collection_name
511                );
512                return Err(Error::UniqueViolation);
513            }
514
515            // Check if the embedding's dimension matches the collection's dimension.
516            if embedding.vector.len() != collection.dimension {
517                error!(
518                    "Dimension mismatch: embedding vector length is '{}' but collection '{}' expects dimension '{}'",
519                    embedding.vector.len(),
520                    collection_name,
521                    collection.dimension
522                );
523                return Err(Error::DimensionMismatch);
524            }
525
526            // Normalize the vector if using cosine distance for efficient calculations.
527            if collection.distance == Distance::Cosine {
528                embedding.vector = normalize(&embedding.vector);
529            }
530
531            // Add the embedding to the collection.
532            collection.embeddings.push(embedding.clone());
533        }
534
535        info!(
536            "Embedding: '{:?}' successfully updated to collection '{}'",
537            new_embeddings, collection_name
538        );
539        Ok(())
540    }
541
542    /// Retrieve a collection from the database.
543    ///
544    /// # Arguments
545    ///
546    /// * `collection_name`: The name of the collection to retrieve.
547    ///
548    /// # Returns
549    ///
550    /// An optional reference to the collection if found.
551    pub fn get_collection(&self, collection_name: &str) -> Option<&Collection> {
552        match self.collections.get(collection_name) {
553            Some(collection) => {
554                info!("Collection '{}' found", collection_name);
555                Some(collection)
556            }
557            None => {
558                error!("Collection '{}' not found", collection_name);
559                None
560            }
561        }
562    }
563
564    /// Retrieve embeddings from a collection in the database.
565    ///
566    /// # Arguments
567    ///
568    /// * `collection_name`: The name of the collection to retrieve.
569    ///
570    /// # Returns
571    ///
572    /// An optional reference to the embeddings if found.
573    pub fn get_embeddings(&self, collection_name: &str) -> Option<Vec<Embedding>> {
574        match self.collections.get(collection_name) {
575            Some(collection) => {
576                info!(
577                    "Successfully retrieved embeddings for collection '{}'",
578                    collection_name
579                );
580                Some(collection.embeddings.clone())
581            }
582            None => {
583                error!("Collection '{}' not found", collection_name);
584                None
585            }
586        }
587    }
588
589    /// Persist the database to disk as a JSON file.
590    ///
591    /// Serializes the entire CacheDB instance, including all collections and their embeddings,
592    /// to a JSON file. This allows for data persistence across application restarts.
593    ///
594    /// # Arguments
595    ///
596    /// * `filepath` - Optional path to the output file. If `None`, defaults to "./memvdb.json"
597    ///
598    /// # Returns
599    ///
600    /// Returns `Ok(())` on successful save, or an error if serialization or file operations fail.
601    ///
602    /// # Examples
603    ///
604    /// ```rust
605    /// use memvdb::{CacheDB, Distance};
606    /// use std::collections::HashMap;
607    ///
608    /// let mut db = CacheDB::new();
609    /// db.create_collection("documents".to_string(), 128, Distance::Cosine).unwrap();
610    ///
611    /// // Save to a specific file
612    /// db.save(Some("my_database.json")).unwrap();
613    ///
614    /// // Save to default location
615    /// db.save(None).unwrap();
616    /// ```
617    ///
618    /// # Errors
619    ///
620    /// This function will return an error if:
621    /// - The database cannot be serialized to JSON
622    /// - The file cannot be created or written to
623    /// - There are insufficient permissions to write to the specified path
624    pub fn save(&self, filepath: Option<&str>) -> Result<()> {
625        let file_content: String = serde_json::to_string_pretty(&self)?;
626
627        // Use a default path to the file if not supplied
628        let filepath: &str = if let Some(filepath) = filepath {
629            filepath
630        } else {
631            "memvdb.json"
632        };
633
634        // Write contents to the file
635        let mut file: File = File::create(filepath)?;
636        file.write_all(file_content.as_bytes())?;
637
638        info!("Database successfully saved to '{}'", filepath);
639        Ok(())
640    }
641
642    /// Load a database from a JSON file on disk.
643    ///
644    /// Deserializes a previously saved CacheDB instance from a JSON file,
645    /// restoring all collections, embeddings, and their associated metadata.
646    ///
647    /// # Arguments
648    ///
649    /// * `filepath` - Path to the JSON file containing the serialized database
650    ///
651    /// # Returns
652    ///
653    /// Returns the loaded `CacheDB` instance on success, or an error if the file
654    /// cannot be read or deserialized.
655    ///
656    /// # Examples
657    ///
658    /// ```rust
659    /// use memvdb::CacheDB;
660    ///
661    /// // Load a previously saved database
662    /// let db = CacheDB::load("my_database.json").unwrap();
663    ///
664    /// // Verify collections were loaded
665    /// if let Some(collection) = db.get_collection("documents") {
666    ///     println!("Loaded collection with {} embeddings", collection.embeddings.len());
667    /// }
668    /// ```
669    ///
670    /// # Errors
671    ///
672    /// This function will return an error if:
673    /// - The specified file does not exist or cannot be opened
674    /// - The file content is not valid JSON
675    /// - The JSON structure doesn't match the expected CacheDB format
676    /// - There are insufficient permissions to read the file
677    pub fn load(filepath: &str) -> Result<Self> {
678        let file: File = std::fs::OpenOptions::new().open(filepath)?;
679        let buffer: BufReader<File> = BufReader::new(file);
680
681        let db: CacheDB = serde_json::from_reader(buffer)?;
682        info!("Database successfully loaded from '{}'", filepath);
683        Ok(db)
684    }
685}
686
687#[cfg(test)]
688mod tests {
689    use super::*;
690
691    #[test]
692    fn test_create_collection_success_eucledean() {
693        let mut db = CacheDB::new();
694        let result = db.create_collection("test_collection".to_string(), 100, Distance::Euclidean);
695
696        assert!(result.is_ok());
697        let collection = result.unwrap();
698        assert_eq!(collection.dimension, 100);
699        assert_eq!(collection.distance, Distance::Euclidean);
700        assert!(db.collections.contains_key("test_collection"));
701    }
702
703    #[test]
704    fn test_create_collection_success_cosine() {
705        let mut db = CacheDB::new();
706        let result = db.create_collection("test_collection".to_string(), 100, Distance::Cosine);
707
708        assert!(result.is_ok());
709        let collection = result.unwrap();
710        assert_eq!(collection.dimension, 100);
711        assert_eq!(collection.distance, Distance::Cosine);
712        assert!(db.collections.contains_key("test_collection"));
713    }
714
715    #[test]
716    fn test_create_collection_success_dot_product() {
717        let mut db = CacheDB::new();
718        let result = db.create_collection("test_collection".to_string(), 100, Distance::DotProduct);
719
720        assert!(result.is_ok());
721        let collection = result.unwrap();
722        assert_eq!(collection.dimension, 100);
723        assert_eq!(collection.distance, Distance::DotProduct);
724        assert!(db.collections.contains_key("test_collection"));
725    }
726
727    #[test]
728    fn test_create_collection_already_exists() {
729        let mut db = CacheDB::new();
730        db.create_collection("test_collection".to_string(), 100, Distance::Euclidean)
731            .unwrap();
732
733        let result = db.create_collection("test_collection".to_string(), 200, Distance::Cosine);
734        assert!(result.is_err());
735    }
736
737    #[test]
738    fn test_insert_into_collection_success() {
739        let mut db = CacheDB::new();
740        let collection = Collection {
741            dimension: 3,
742            distance: Distance::Euclidean,
743            embeddings: Vec::new(),
744        };
745        db.collections
746            .insert("test_collection".to_string(), collection);
747        let mut metadata = HashMap::new();
748        metadata.insert("page".to_string(), "1".to_string());
749        metadata.insert(
750            "text".to_string(),
751            "This is a test metadata text".to_string(),
752        );
753
754        let mut id = HashMap::new();
755        id.insert("unique_id".to_string(), "1".to_string());
756
757        let embedding = Embedding {
758            id: id,
759            vector: vec![1.0, 2.0, 3.0],
760            metadata: Some(metadata),
761        };
762
763        let result = db.insert_into_collection("test_collection", embedding.clone());
764        assert!(result.is_ok());
765
766        // Check if the embedding is inserted into the collection
767        let collection = db.collections.get("test_collection").unwrap();
768        assert_eq!(collection.embeddings.len(), 1);
769        assert_eq!(collection.embeddings[0], embedding);
770    }
771
772    #[test]
773    fn test_update_collection_success() {
774        let mut db = CacheDB::new();
775
776        let mut metadata = HashMap::new();
777        metadata.insert("page".to_string(), "1".to_string());
778        metadata.insert(
779            "text".to_string(),
780            "This is a test metadata text".to_string(),
781        );
782
783        let mut id = HashMap::new();
784        id.insert("unique_id".to_string(), "0".to_string());
785
786        let collection = Collection {
787            dimension: 3,
788            distance: Distance::Euclidean,
789            embeddings: vec![Embedding {
790                id: id,
791                vector: vec![1.0, 2.0, 3.0],
792                metadata: Some(metadata.clone()),
793            }],
794        };
795
796        db.collections
797            .insert("test_collection".to_string(), collection);
798
799        let mut id_1 = HashMap::new();
800        id_1.insert("unique_id".to_string(), "1".to_string());
801        let mut id_2 = HashMap::new();
802        id_2.insert("unique_id".to_string(), "2".to_string());
803
804        let new_embeddings = vec![
805            Embedding {
806                id: id_1, // Duplicate ID
807                vector: vec![4.0, 5.0, 6.0],
808                metadata: Some(metadata.clone()),
809            },
810            Embedding {
811                id: id_2,
812                vector: vec![7.0, 8.0, 9.0],
813                metadata: Some(metadata.clone()),
814            },
815        ];
816
817        let result = db.update_collection("test_collection", new_embeddings.clone());
818        assert!(result.is_ok());
819
820        // Check if the new embeddings are added to the collection
821        let collection = db.collections.get("test_collection").unwrap();
822        assert_eq!(collection.embeddings.len(), 3);
823        assert_eq!(collection.embeddings[1..], new_embeddings[..]);
824    }
825
826    #[test]
827    fn test_update_collection_duplicate_embedding() {
828        let mut db = CacheDB::new();
829        let mut metadata = HashMap::new();
830        metadata.insert("page".to_string(), "1".to_string());
831        metadata.insert(
832            "text".to_string(),
833            "This is a test metadata text".to_string(),
834        );
835
836        let mut id = HashMap::new();
837        id.insert("unique_id".to_string(), "0".to_string());
838
839        let collection = Collection {
840            dimension: 3,
841            distance: Distance::Euclidean,
842            embeddings: vec![Embedding {
843                id: id.clone(),
844                vector: vec![1.0, 2.0, 3.0],
845                metadata: Some(metadata.clone()),
846            }],
847        };
848        db.collections
849            .insert("test_collection".to_string(), collection);
850
851        let mut id_1 = HashMap::new();
852        id_1.insert("unique_id".to_string(), "1".to_string());
853        let mut id_2 = HashMap::new();
854        id_2.insert("unique_id".to_string(), "2".to_string());
855
856        let new_embeddings = vec![
857            Embedding {
858                id: id, // Duplicate ID
859                vector: vec![4.0, 5.0, 6.0],
860                metadata: Some(metadata.clone()),
861            },
862            Embedding {
863                id: id_2,
864                vector: vec![7.0, 8.0, 9.0],
865                metadata: Some(metadata.clone()),
866            },
867        ];
868
869        let result = db.update_collection("test_collection", new_embeddings);
870        assert!(result.is_err());
871        assert_eq!(result.err(), Some(Error::UniqueViolation));
872    }
873
874    #[test]
875    fn test_update_collection_dimension_mismatch() {
876        let mut db = CacheDB::new();
877        let collection = Collection {
878            dimension: 3,
879            distance: Distance::Euclidean,
880            embeddings: Vec::new(),
881        };
882        db.collections
883            .insert("test_collection".to_string(), collection);
884
885        let mut metadata = HashMap::new();
886        metadata.insert("page".to_string(), "1".to_string());
887        metadata.insert(
888            "text".to_string(),
889            "This is a test metadata text".to_string(),
890        );
891
892        let mut id = HashMap::new();
893        id.insert("unique_id".to_string(), "0".to_string());
894
895        let new_embeddings = vec![Embedding {
896            id: id,
897            vector: vec![1.0, 2.0],
898            metadata: Some(metadata), // Dimension mismatch
899        }];
900
901        let result = db.update_collection("test_collection", new_embeddings);
902        assert!(result.is_err());
903        assert_eq!(result.err(), Some(Error::DimensionMismatch));
904    }
905
906    #[test]
907    fn test_delete_collection_success() {
908        let mut db = CacheDB::new();
909        db.collections.insert(
910            "test_collection".to_string(),
911            Collection {
912                dimension: 3,
913                distance: Distance::Euclidean,
914                embeddings: Vec::new(),
915            },
916        );
917
918        let result = db.delete_collection("test_collection");
919        assert!(result.is_ok());
920
921        // Check if the collection is removed from the database
922        assert!(!db.collections.contains_key("test_collection"));
923    }
924
925    #[test]
926    fn test_delete_collection_not_found() {
927        let mut db = CacheDB::new();
928
929        let result = db.delete_collection("non_existent_collection");
930        assert!(result.is_err());
931        assert_eq!(result.err(), Some(Error::NotFound));
932    }
933
934    #[test]
935    fn test_get_collection_success() {
936        let mut db = CacheDB::new();
937        let collection = Collection {
938            dimension: 3,
939            distance: Distance::Euclidean,
940            embeddings: Vec::new(),
941        };
942        db.collections
943            .insert("test_collection".to_string(), collection.clone());
944
945        let result = db.get_collection("test_collection");
946        assert!(result.is_some());
947
948        // Check if the retrieved collection is the same as the original one
949        assert_eq!(result.unwrap(), &collection);
950    }
951
952    #[test]
953    fn test_get_collection_not_found() {
954        let db = CacheDB::new();
955
956        let result = db.get_collection("non_existent_collection");
957        assert!(result.is_none());
958    }
959
960    #[test]
961    fn test_get_embedding_success() {
962        let mut db = CacheDB::new();
963
964        let mut id = HashMap::new();
965        id.insert("unique_id".to_string(), "0".to_string());
966
967        let mut id_1 = HashMap::new();
968        id_1.insert("unique_id".to_string(), "1".to_string());
969
970        let mut id_2 = HashMap::new();
971        id_2.insert("unique_id".to_string(), "2".to_string());
972
973        let collection = Collection {
974            dimension: 3,
975            distance: Distance::Euclidean,
976            embeddings: vec![
977                Embedding {
978                    id: id,
979                    vector: vec![1.0, 1.0, 1.0],
980                    metadata: None,
981                },
982                Embedding {
983                    id: id_1,
984                    vector: vec![2.0, 2.0, 2.0],
985                    metadata: None,
986                },
987                Embedding {
988                    id: id_2,
989                    vector: vec![3.0, 3.0, 3.0],
990                    metadata: None,
991                },
992            ],
993        };
994        db.collections
995            .insert("test_collection".to_string(), collection.clone());
996        let result = db.get_embeddings("test_collection");
997        assert!(result.is_some());
998        assert_eq!(result, Some(collection.embeddings));
999    }
1000
1001    #[test]
1002    fn test_get_embeddings_not_found() {
1003        let db = CacheDB::new();
1004
1005        let result = db.get_embeddings("non_existent_collection");
1006        assert!(result.is_none());
1007    }
1008
1009    #[test]
1010    fn test_get_similarity() {
1011        let mut id = HashMap::new();
1012        id.insert("unique_id".to_string(), "0".to_string());
1013
1014        let mut id_1 = HashMap::new();
1015        id_1.insert("unique_id".to_string(), "1".to_string());
1016
1017        let mut id_2 = HashMap::new();
1018        id_2.insert("unique_id".to_string(), "2".to_string());
1019
1020        let collection = Collection {
1021            dimension: 3,
1022            distance: Distance::Euclidean,
1023            embeddings: vec![
1024                Embedding {
1025                    id: id.clone(),
1026                    vector: vec![1.0, 1.0, 1.0],
1027                    metadata: None,
1028                },
1029                Embedding {
1030                    id: id_1.clone(),
1031                    vector: vec![2.0, 2.0, 2.0],
1032                    metadata: None,
1033                },
1034                Embedding {
1035                    id: id_2.clone(),
1036                    vector: vec![3.0, 3.0, 3.0],
1037                    metadata: None,
1038                },
1039            ],
1040        };
1041
1042        // Define a query vector
1043        let query = vec![0.0, 0.0, 0.0];
1044
1045        // Define the expected similarity results
1046        let expected_results = vec![
1047            SimilarityResult {
1048                score: 0.0,
1049                embedding: Embedding {
1050                    id: id_1,
1051                    vector: vec![2.0, 2.0, 2.0],
1052                    metadata: None,
1053                },
1054            },
1055            SimilarityResult {
1056                score: 0.0,
1057                embedding: Embedding {
1058                    id: id_2,
1059                    vector: vec![3.0, 3.0, 3.0],
1060                    metadata: None,
1061                },
1062            },
1063            SimilarityResult {
1064                score: 0.0,
1065                embedding: Embedding {
1066                    id: id,
1067                    vector: vec![1.0, 1.0, 1.0],
1068                    metadata: None,
1069                },
1070            },
1071        ];
1072
1073        // Call the get_similarity method
1074        let results = collection.get_similarity(&query, 3);
1075
1076        // Assert that the results are as expected
1077        assert_eq!(results, expected_results);
1078    }
1079}