neodb 0.1.0-alpha.2

A fast, lightweight key-value database written in Rust. (Work in Progress)
Documentation
use std::collections::{HashMap, HashSet};

pub struct NeoCollection {
    pub name: String,
    pub records: HashMap<String, String>,
    pub indexes: HashMap<String, HashMap<String, HashSet<String>>>,
    pub reverse_indexes: HashMap<String, HashMap<String, String>>,
}

impl NeoCollection {
    pub fn new(name: String) -> Self {
        Self {
            name,
            records: HashMap::new(),
            indexes: HashMap::new(),
            reverse_indexes: HashMap::new(),
        }
    }

    pub fn get(&self, key: &str) -> Option<&String> {
        self.records.get(key)
    }

    pub fn put(&mut self, key: String, value: String, indexes: Option<HashMap<String, String>>) {
        self.delete_indexes(&key);
        if let Some(idx_map) = indexes {
            let mut rev_idx = HashMap::with_capacity(idx_map.len());
            for (index_name, index_value) in idx_map {
                self.indexes
                    .entry(index_name.clone())
                    .or_default()
                    .entry(index_value.clone())
                    .or_default()
                    .insert(key.clone());

                rev_idx.insert(index_name, index_value);
            }
            self.reverse_indexes.insert(key.clone(), rev_idx);
        }
        self.records.insert(key, value);
    }

    fn delete_indexes(&mut self, key: &str) {
        if let Some(index_to_remove) = self.reverse_indexes.remove(key) {
            for (index_name, index_value) in index_to_remove {
                if let Some(inner_map) = self.indexes.get_mut(&index_name) {
                    if let Some(keys_set) = inner_map.get_mut(&index_value) {
                        keys_set.remove(key);
                        if keys_set.is_empty() {
                            inner_map.remove(&index_value);
                        }
                    }
                    if inner_map.is_empty() {
                        self.indexes.remove(&index_name);
                    }
                }
            }
        }
    }

    pub fn delete(&mut self, key: &str) -> bool {
        self.delete_indexes(key);
        self.records.remove(key).is_some()
    }

    pub fn find_keys(&self, index_name: &str, index_value: &str) -> HashSet<String> {
        self.indexes
            .get(index_name)
            .and_then(|inner_map| inner_map.get(index_value))
            .cloned()
            .unwrap_or_default()
    }
}

pub struct NeoDB {
    pub dbname: String,
    pub collections: HashMap<String, NeoCollection>,
    collection_number: usize,
}

impl NeoDB {
    pub fn new(dbname: &str) -> Self {
        Self {
            dbname: dbname.to_string(),
            collections: HashMap::new(),
            collection_number: 0,
        }
    }

    pub fn collection(&mut self, collection_name: Option<String>) -> &mut NeoCollection {
        let name = match collection_name {
            Some(name) => name,
            None => {
                self.collection_number += 1;
                format!("Collection_{}", self.collection_number)
            }
        };

        self.collections
            .entry(name.clone())
            .or_insert_with(|| NeoCollection::new(name))
    }

    pub fn list_collections(&self) -> Vec<String> {
        self.collections.keys().cloned().collect()
    }

    pub fn drop_collection(&mut self, collection_name: &str) -> bool {
        self.collections.remove(collection_name).is_some()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_neodb_auto_naming() {
        let mut db = NeoDB::new("test");
        let col1 = db.collection(None);
        assert_eq!(col1.name, "Collection_1");
        let col2 = db.collection(None);
        assert_eq!(col2.name, "Collection_2");
    }

    #[test]
    fn test_neodb_named_collection() {
        let mut db = NeoDB::new("test");
        let col = db.collection(Some("users".to_string()));
        assert_eq!(col.name, "users");
        
        let col_again = db.collection(Some("users".to_string()));
        assert_eq!(col_again.name, "users");
    }

    #[test]
    fn test_neocollection_put_get() {
        let mut col = NeoCollection::new("test".to_string());
        col.put("k1".to_string(), "v1".to_string(), None);
        assert_eq!(col.get("k1"), Some(&"v1".to_string()));
        assert_eq!(col.get("k2"), None);
    }

    #[test]
    fn test_indexing() {
        let mut col = NeoCollection::new("test".to_string());
        let mut indexes = HashMap::new();
        indexes.insert("color".to_string(), "red".to_string());
        
        col.put("k1".to_string(), "v1".to_string(), Some(indexes.clone()));
        col.put("k2".to_string(), "v2".to_string(), Some(indexes));

        let red_keys = col.find_keys("color", "red");
        assert_eq!(red_keys.len(), 2);
        assert!(red_keys.contains("k1"));
        assert!(red_keys.contains("k2"));

        col.delete("k1");
        let red_keys_after = col.find_keys("color", "red");
        assert_eq!(red_keys_after.len(), 1);
        assert!(red_keys_after.contains("k2"));
    }

    #[test]
    fn test_list_and_drop_collections() {
        let mut db = NeoDB::new("test");
        db.collection(Some("c1".to_string()));
        db.collection(Some("c2".to_string()));
        
        let list = db.list_collections();
        assert_eq!(list.len(), 2);
        assert!(list.contains(&"c1".to_string()));
        assert!(list.contains(&"c2".to_string()));

        assert!(db.drop_collection("c1"));
        assert!(!db.drop_collection("c1"));
        assert_eq!(db.list_collections().len(), 1);
    }

    #[test]
    fn test_delete_record() {
        let mut col = NeoCollection::new("test".to_string());
        col.put("k1".to_string(), "v1".to_string(), None);
        assert!(col.delete("k1"));
        assert!(!col.delete("k1"));
        assert_eq!(col.get("k1"), None);
    }
}