...
This commit is contained in:
		@@ -1,20 +1,84 @@
 | 
			
		||||
 | 
			
		||||
use crate::zaz::models::*;
 | 
			
		||||
use crate::core::base::*;
 | 
			
		||||
use std::any::TypeId;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
use std::path::{Path, PathBuf};
 | 
			
		||||
use std::sync::{Arc, Mutex, RwLock};
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
use bincode;
 | 
			
		||||
 | 
			
		||||
/// Represents a single database operation in a transaction
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
enum DbOperation {
 | 
			
		||||
    Set {
 | 
			
		||||
        model_type: TypeId,
 | 
			
		||||
        serialized: Vec<u8>,
 | 
			
		||||
    },
 | 
			
		||||
    Delete {
 | 
			
		||||
        model_type: TypeId,
 | 
			
		||||
        id: String,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Trait for type-erased database operations
 | 
			
		||||
pub trait AnyDbOperations: Send + Sync {
 | 
			
		||||
    fn delete(&self, id: &str) -> SledDBResult<()>;
 | 
			
		||||
    fn get_any(&self, id: &str) -> SledDBResult<Box<dyn std::any::Any>>;
 | 
			
		||||
    fn list_any(&self) -> SledDBResult<Box<dyn std::any::Any>>;
 | 
			
		||||
    fn insert_any(&self, model: &dyn std::any::Any) -> SledDBResult<()>; 
 | 
			
		||||
    fn insert_any_raw(&self, serialized: &[u8]) -> SledDBResult<()>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Implementation of AnyDbOperations for any SledDB<T>
 | 
			
		||||
impl<T: SledModel> AnyDbOperations for SledDB<T> {
 | 
			
		||||
    fn delete(&self, id: &str) -> SledDBResult<()> {
 | 
			
		||||
        self.delete(id)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn get_any(&self, id: &str) -> SledDBResult<Box<dyn std::any::Any>> {
 | 
			
		||||
        let result = self.get(id)?;
 | 
			
		||||
        Ok(Box::new(result))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn list_any(&self) -> SledDBResult<Box<dyn std::any::Any>> {
 | 
			
		||||
        let result = self.list()?;
 | 
			
		||||
        Ok(Box::new(result))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn insert_any(&self, model: &dyn std::any::Any) -> SledDBResult<()> {
 | 
			
		||||
        // Downcast to the specific type T
 | 
			
		||||
        match model.downcast_ref::<T>() {
 | 
			
		||||
            Some(t) => self.insert(t),
 | 
			
		||||
            None => Err(SledDBError::TypeError),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn insert_any_raw(&self, serialized: &[u8]) -> SledDBResult<()> {
 | 
			
		||||
        // Deserialize the bytes into model of type T
 | 
			
		||||
        let model: T = bincode::deserialize(serialized)?;
 | 
			
		||||
        // Use the regular insert method
 | 
			
		||||
        self.insert(&model)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Transaction state for DB operations
 | 
			
		||||
pub struct TransactionState {
 | 
			
		||||
    operations: Vec<DbOperation>,
 | 
			
		||||
    active: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TransactionState {
 | 
			
		||||
    /// Create a new transaction state
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            operations: Vec::new(),
 | 
			
		||||
            active: true,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Main DB manager that automatically handles all root models
 | 
			
		||||
pub struct DB {
 | 
			
		||||
    db_path: PathBuf,
 | 
			
		||||
    user_db: SledDB<User>,
 | 
			
		||||
    company_db: SledDB<Company>,
 | 
			
		||||
    meeting_db: SledDB<Meeting>,
 | 
			
		||||
    product_db: SledDB<Product>,
 | 
			
		||||
    sale_db: SledDB<Sale>,
 | 
			
		||||
    vote_db: SledDB<Vote>,
 | 
			
		||||
    shareholder_db: SledDB<Shareholder>,
 | 
			
		||||
    
 | 
			
		||||
    // Type map for generic operations
 | 
			
		||||
    type_map: HashMap<TypeId, Box<dyn AnyDbOperations>>,
 | 
			
		||||
@@ -26,8 +90,83 @@ pub struct DB {
 | 
			
		||||
    transaction: RwLock<Option<TransactionState>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for DB that allows registering models
 | 
			
		||||
pub struct DBBuilder {
 | 
			
		||||
    base_path: PathBuf,
 | 
			
		||||
    model_registrations: Vec<Box<dyn ModelRegistration>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Trait for model registration
 | 
			
		||||
pub trait ModelRegistration: Send + Sync {
 | 
			
		||||
    fn register(&self, path: &Path) -> SledDBResult<(TypeId, Box<dyn AnyDbOperations>)>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Implementation of ModelRegistration for any SledModel type
 | 
			
		||||
pub struct SledModelRegistration<T: SledModel> {
 | 
			
		||||
    phantom: std::marker::PhantomData<T>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: SledModel> SledModelRegistration<T> {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            phantom: std::marker::PhantomData,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: SledModel> ModelRegistration for SledModelRegistration<T> {
 | 
			
		||||
    fn register(&self, path: &Path) -> SledDBResult<(TypeId, Box<dyn AnyDbOperations>)> {
 | 
			
		||||
        let db: SledDB<T> = SledDB::open(path.join(T::db_prefix()))?;
 | 
			
		||||
        Ok((TypeId::of::<T>(), Box::new(db) as Box<dyn AnyDbOperations>))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl DBBuilder {
 | 
			
		||||
    /// Create a new DB builder
 | 
			
		||||
    pub fn new<P: Into<PathBuf>>(base_path: P) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            base_path: base_path.into(),
 | 
			
		||||
            model_registrations: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Register a model type with the DB
 | 
			
		||||
    pub fn register_model<T: SledModel>(mut self) -> Self {
 | 
			
		||||
        self.model_registrations.push(Box::new(SledModelRegistration::<T>::new()));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Build the DB with the registered models
 | 
			
		||||
    pub fn build(self) -> SledDBResult<DB> {
 | 
			
		||||
        let base_path = self.base_path;
 | 
			
		||||
        
 | 
			
		||||
        // Ensure base directory exists
 | 
			
		||||
        if !base_path.exists() {
 | 
			
		||||
            std::fs::create_dir_all(&base_path)?;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Register all models
 | 
			
		||||
        let mut type_map: HashMap<TypeId, Box<dyn AnyDbOperations>> = HashMap::new();
 | 
			
		||||
        
 | 
			
		||||
        for registration in self.model_registrations {
 | 
			
		||||
            let (type_id, db) = registration.register(&base_path)?;
 | 
			
		||||
            type_map.insert(type_id, db);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        let _write_locks = Arc::new(Mutex::new(HashMap::new()));
 | 
			
		||||
        let transaction = RwLock::new(None);
 | 
			
		||||
        
 | 
			
		||||
        Ok(DB {
 | 
			
		||||
            db_path: base_path,
 | 
			
		||||
            type_map,
 | 
			
		||||
            _write_locks,
 | 
			
		||||
            transaction,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl DB {
 | 
			
		||||
    /// Create a new DB instance with all model databases
 | 
			
		||||
    /// Create a new empty DB instance without any models
 | 
			
		||||
    pub fn new<P: Into<PathBuf>>(base_path: P) -> SledDBResult<Self> {
 | 
			
		||||
        let base_path = base_path.into();
 | 
			
		||||
        
 | 
			
		||||
@@ -36,38 +175,12 @@ impl DB {
 | 
			
		||||
            std::fs::create_dir_all(&base_path)?;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Create individual database instances for each model type
 | 
			
		||||
        let user_db = SledDB::open(base_path.join("user"))?;
 | 
			
		||||
        let company_db = SledDB::open(base_path.join("company"))?;
 | 
			
		||||
        let meeting_db = SledDB::open(base_path.join("meeting"))?;
 | 
			
		||||
        let product_db = SledDB::open(base_path.join("product"))?;
 | 
			
		||||
        let sale_db = SledDB::open(base_path.join("sale"))?;
 | 
			
		||||
        let vote_db = SledDB::open(base_path.join("vote"))?;
 | 
			
		||||
        let shareholder_db = SledDB::open(base_path.join("shareholder"))?;
 | 
			
		||||
        
 | 
			
		||||
        // Create type map for generic operations
 | 
			
		||||
        let mut type_map: HashMap<TypeId, Box<dyn AnyDbOperations>> = HashMap::new();
 | 
			
		||||
        type_map.insert(TypeId::of::<User>(), Box::new(user_db.clone()));
 | 
			
		||||
        type_map.insert(TypeId::of::<Company>(), Box::new(company_db.clone()));
 | 
			
		||||
        type_map.insert(TypeId::of::<Meeting>(), Box::new(meeting_db.clone()));
 | 
			
		||||
        type_map.insert(TypeId::of::<Product>(), Box::new(product_db.clone()));
 | 
			
		||||
        type_map.insert(TypeId::of::<Sale>(), Box::new(sale_db.clone()));
 | 
			
		||||
        type_map.insert(TypeId::of::<Vote>(), Box::new(vote_db.clone()));
 | 
			
		||||
        type_map.insert(TypeId::of::<Shareholder>(), Box::new(shareholder_db.clone()));
 | 
			
		||||
        
 | 
			
		||||
        let _write_locks = Arc::new(Mutex::new(HashMap::new()));
 | 
			
		||||
        let transaction = RwLock::new(None);
 | 
			
		||||
        
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            db_path: base_path,
 | 
			
		||||
            user_db,
 | 
			
		||||
            company_db,
 | 
			
		||||
            meeting_db,
 | 
			
		||||
            product_db,
 | 
			
		||||
            sale_db,
 | 
			
		||||
            vote_db,
 | 
			
		||||
            shareholder_db,
 | 
			
		||||
            type_map,
 | 
			
		||||
            type_map: HashMap::new(),
 | 
			
		||||
            _write_locks,
 | 
			
		||||
            transaction,
 | 
			
		||||
        })
 | 
			
		||||
@@ -93,58 +206,13 @@ impl DB {
 | 
			
		||||
    
 | 
			
		||||
    /// Apply a set operation with the serialized data - bypass transaction check
 | 
			
		||||
    fn apply_set_operation(&self, model_type: TypeId, serialized: &[u8]) -> SledDBResult<()> {
 | 
			
		||||
        // User model
 | 
			
		||||
        if model_type == TypeId::of::<User>() {
 | 
			
		||||
            let model: User = bincode::deserialize(serialized)?;
 | 
			
		||||
            // Access the database operations directly to avoid transaction recursion
 | 
			
		||||
            if let Some(db_ops) = self.type_map.get(&TypeId::of::<User>()) {
 | 
			
		||||
                return db_ops.insert_any(&model);
 | 
			
		||||
            }
 | 
			
		||||
        } 
 | 
			
		||||
        // Company model
 | 
			
		||||
        else if model_type == TypeId::of::<Company>() {
 | 
			
		||||
            let model: Company = bincode::deserialize(serialized)?;
 | 
			
		||||
            if let Some(db_ops) = self.type_map.get(&TypeId::of::<Company>()) {
 | 
			
		||||
                return db_ops.insert_any(&model);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Meeting model 
 | 
			
		||||
        else if model_type == TypeId::of::<Meeting>() {
 | 
			
		||||
            let model: Meeting = bincode::deserialize(serialized)?;
 | 
			
		||||
            if let Some(db_ops) = self.type_map.get(&TypeId::of::<Meeting>()) {
 | 
			
		||||
                return db_ops.insert_any(&model);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Product model
 | 
			
		||||
        else if model_type == TypeId::of::<Product>() {
 | 
			
		||||
            let model: Product = bincode::deserialize(serialized)?;
 | 
			
		||||
            if let Some(db_ops) = self.type_map.get(&TypeId::of::<Product>()) {
 | 
			
		||||
                return db_ops.insert_any(&model);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Sale model
 | 
			
		||||
        else if model_type == TypeId::of::<Sale>() {
 | 
			
		||||
            let model: Sale = bincode::deserialize(serialized)?;
 | 
			
		||||
            if let Some(db_ops) = self.type_map.get(&TypeId::of::<Sale>()) {
 | 
			
		||||
                return db_ops.insert_any(&model);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Vote model
 | 
			
		||||
        else if model_type == TypeId::of::<Vote>() {
 | 
			
		||||
            let model: Vote = bincode::deserialize(serialized)?;
 | 
			
		||||
            if let Some(db_ops) = self.type_map.get(&TypeId::of::<Vote>()) {
 | 
			
		||||
                return db_ops.insert_any(&model);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Shareholder model
 | 
			
		||||
        else if model_type == TypeId::of::<Shareholder>() {
 | 
			
		||||
            let model: Shareholder = bincode::deserialize(serialized)?;
 | 
			
		||||
            if let Some(db_ops) = self.type_map.get(&TypeId::of::<Shareholder>()) {
 | 
			
		||||
                return db_ops.insert_any(&model);
 | 
			
		||||
            }
 | 
			
		||||
        // Get the database operations for this model type
 | 
			
		||||
        if let Some(db_ops) = self.type_map.get(&model_type) {
 | 
			
		||||
            // Just pass the raw serialized data to a special raw insert method
 | 
			
		||||
            return db_ops.insert_any_raw(serialized);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        Err(SledDBError::TypeError)
 | 
			
		||||
        Err(SledDBError::GeneralError(format!("No DB registered for type ID {:?}", model_type)))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Commit the current transaction, applying all operations
 | 
			
		||||
@@ -251,11 +319,14 @@ impl DB {
 | 
			
		||||
                    // Then check if it has been set in the transaction
 | 
			
		||||
                    DbOperation::Set { model_type, serialized } => {
 | 
			
		||||
                        if *model_type == type_id {
 | 
			
		||||
                            // Deserialize to check the ID
 | 
			
		||||
                            if let Ok(model) = bincode::deserialize::<T>(serialized) {
 | 
			
		||||
                            // Try to deserialize and check the ID
 | 
			
		||||
                            match bincode::deserialize::<T>(serialized) {
 | 
			
		||||
                                Ok(model) => {
 | 
			
		||||
                                if model.get_id() == id_str {
 | 
			
		||||
                                    return Some(Ok(Some(model)));
 | 
			
		||||
                                }
 | 
			
		||||
                                },
 | 
			
		||||
                                Err(_) => continue, // Skip if deserialization fails
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
@@ -337,34 +408,103 @@ impl DB {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Convenience methods to get each specific database
 | 
			
		||||
    
 | 
			
		||||
    pub fn user_db(&self) -> &SledDB<User> {
 | 
			
		||||
        &self.user_db
 | 
			
		||||
    // Register a model type with this DB instance
 | 
			
		||||
    pub fn register<T: SledModel>(&mut self) -> SledDBResult<()> {
 | 
			
		||||
        let db_path = self.db_path.join(T::db_prefix());
 | 
			
		||||
        let db: SledDB<T> = SledDB::open(db_path)?;
 | 
			
		||||
        self.type_map.insert(TypeId::of::<T>(), Box::new(db));
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn company_db(&self) -> &SledDB<Company> {
 | 
			
		||||
        &self.company_db
 | 
			
		||||
    // Get a typed handle to a registered model DB
 | 
			
		||||
    pub fn db_for<T: SledModel>(&self) -> SledDBResult<&dyn AnyDbOperations> {
 | 
			
		||||
        match self.type_map.get(&TypeId::of::<T>()) {
 | 
			
		||||
            Some(db) => Ok(&**db),
 | 
			
		||||
            None => Err(SledDBError::GeneralError(format!("No DB registered for type {}", std::any::type_name::<T>()))),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test module with mocked models
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use chrono::Utc;
 | 
			
		||||
    use tempfile::tempdir;
 | 
			
		||||
    use serde::{Deserialize, Serialize};
 | 
			
		||||
    
 | 
			
		||||
    // Test model
 | 
			
		||||
    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
    struct TestUser {
 | 
			
		||||
        id: u32,
 | 
			
		||||
        name: String,
 | 
			
		||||
        email: String,
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn meeting_db(&self) -> &SledDB<Meeting> {
 | 
			
		||||
        &self.meeting_db
 | 
			
		||||
    impl TestUser {
 | 
			
		||||
        fn new(id: u32, name: String, email: String) -> Self {
 | 
			
		||||
            Self { id, name, email }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn product_db(&self) -> &SledDB<Product> {
 | 
			
		||||
        &self.product_db
 | 
			
		||||
    impl Storable for TestUser {}
 | 
			
		||||
    
 | 
			
		||||
    impl SledModel for TestUser {
 | 
			
		||||
        fn get_id(&self) -> String {
 | 
			
		||||
            self.id.to_string()
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        fn db_prefix() -> &'static str {
 | 
			
		||||
            "test_user"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn sale_db(&self) -> &SledDB<Sale> {
 | 
			
		||||
        &self.sale_db
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_db_builder() {
 | 
			
		||||
        // Create a temporary directory for the test
 | 
			
		||||
        let dir = tempdir().expect("Failed to create temp dir");
 | 
			
		||||
        
 | 
			
		||||
        // Create a DB with the builder
 | 
			
		||||
        let db = DBBuilder::new(dir.path())
 | 
			
		||||
            .register_model::<TestUser>()
 | 
			
		||||
            .build()
 | 
			
		||||
            .expect("Failed to build DB");
 | 
			
		||||
        
 | 
			
		||||
        // Create a test user
 | 
			
		||||
        let user = TestUser::new(1, "Test User".to_string(), "test@example.com".to_string());
 | 
			
		||||
        
 | 
			
		||||
        // Set the user
 | 
			
		||||
        db.set(&user).expect("Failed to set user");
 | 
			
		||||
        
 | 
			
		||||
        // Get the user
 | 
			
		||||
        let retrieved: TestUser = db.get(&user.id.to_string()).expect("Failed to get user");
 | 
			
		||||
        
 | 
			
		||||
        // Check that it matches
 | 
			
		||||
        assert_eq!(user, retrieved);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn vote_db(&self) -> &SledDB<Vote> {
 | 
			
		||||
        &self.vote_db
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn shareholder_db(&self) -> &SledDB<Shareholder> {
 | 
			
		||||
        &self.shareholder_db
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_dynamic_registration() {
 | 
			
		||||
        // Create a temporary directory for the test
 | 
			
		||||
        let dir = tempdir().expect("Failed to create temp dir");
 | 
			
		||||
        
 | 
			
		||||
        // Create an empty DB
 | 
			
		||||
        let mut db = DB::new(dir.path()).expect("Failed to create DB");
 | 
			
		||||
        
 | 
			
		||||
        // Register the TestUser model
 | 
			
		||||
        db.register::<TestUser>().expect("Failed to register TestUser");
 | 
			
		||||
        
 | 
			
		||||
        // Create a test user
 | 
			
		||||
        let user = TestUser::new(2, "Dynamic User".to_string(), "dynamic@example.com".to_string());
 | 
			
		||||
        
 | 
			
		||||
        // Set the user
 | 
			
		||||
        db.set(&user).expect("Failed to set user");
 | 
			
		||||
        
 | 
			
		||||
        // Get the user
 | 
			
		||||
        let retrieved: TestUser = db.get(&user.id.to_string()).expect("Failed to get user");
 | 
			
		||||
        
 | 
			
		||||
        // Check that it matches
 | 
			
		||||
        assert_eq!(user, retrieved);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,70 +1,6 @@
 | 
			
		||||
mod base;
 | 
			
		||||
pub mod base;
 | 
			
		||||
pub mod db;
 | 
			
		||||
 | 
			
		||||
pub use base::*;
 | 
			
		||||
 | 
			
		||||
use std::any::TypeId;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
use std::sync::{Arc, Mutex, RwLock};
 | 
			
		||||
 | 
			
		||||
/// Represents a single database operation in a transaction
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
enum DbOperation {
 | 
			
		||||
    Set {
 | 
			
		||||
        model_type: TypeId,
 | 
			
		||||
        serialized: Vec<u8>,
 | 
			
		||||
    },
 | 
			
		||||
    Delete {
 | 
			
		||||
        model_type: TypeId,
 | 
			
		||||
        id: String,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Trait for type-erased database operations
 | 
			
		||||
trait AnyDbOperations: Send + Sync {
 | 
			
		||||
    fn delete(&self, id: &str) -> SledDBResult<()>;
 | 
			
		||||
    fn get_any(&self, id: &str) -> SledDBResult<Box<dyn std::any::Any>>;
 | 
			
		||||
    fn list_any(&self) -> SledDBResult<Box<dyn std::any::Any>>;
 | 
			
		||||
    fn insert_any(&self, model: &dyn std::any::Any) -> SledDBResult<()>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Implementation of AnyDbOperations for any SledDB<T>
 | 
			
		||||
impl<T: SledModel> AnyDbOperations for SledDB<T> {
 | 
			
		||||
    fn delete(&self, id: &str) -> SledDBResult<()> {
 | 
			
		||||
        self.delete(id)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn get_any(&self, id: &str) -> SledDBResult<Box<dyn std::any::Any>> {
 | 
			
		||||
        let result = self.get(id)?;
 | 
			
		||||
        Ok(Box::new(result))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn list_any(&self) -> SledDBResult<Box<dyn std::any::Any>> {
 | 
			
		||||
        let result = self.list()?;
 | 
			
		||||
        Ok(Box::new(result))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn insert_any(&self, model: &dyn std::any::Any) -> SledDBResult<()> {
 | 
			
		||||
        // Downcast to the specific type T
 | 
			
		||||
        match model.downcast_ref::<T>() {
 | 
			
		||||
            Some(t) => self.insert(t),
 | 
			
		||||
            None => Err(SledDBError::TypeError),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Transaction state for DB operations
 | 
			
		||||
pub struct TransactionState {
 | 
			
		||||
    operations: Vec<DbOperation>,
 | 
			
		||||
    active: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TransactionState {
 | 
			
		||||
    /// Create a new transaction state
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            operations: Vec::new(),
 | 
			
		||||
            active: true,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
// Re-export everything needed at the module level
 | 
			
		||||
pub use base::{SledDB, SledDBError, SledDBResult, Storable, SledModel};
 | 
			
		||||
pub use db::{DB, DBBuilder, ModelRegistration, SledModelRegistration};
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,15 @@
 | 
			
		||||
//! This library provides a simple interface for working with a sled-based database
 | 
			
		||||
//! and includes support for defining and working with data models.
 | 
			
		||||
 | 
			
		||||
mod db;
 | 
			
		||||
// Core modules
 | 
			
		||||
pub mod core;
 | 
			
		||||
mod error;
 | 
			
		||||
mod model;
 | 
			
		||||
 | 
			
		||||
pub use db::{Database, Collection};
 | 
			
		||||
// Domain-specific modules
 | 
			
		||||
pub mod zaz;
 | 
			
		||||
 | 
			
		||||
// Re-exports
 | 
			
		||||
pub use error::Error;
 | 
			
		||||
pub use model::{Model, ModelId, Timestamp};
 | 
			
		||||
 | 
			
		||||
/// Re-export sled for advanced usage
 | 
			
		||||
pub use sled;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								herodb/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								herodb/src/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
//! Main entry point for running HeroDB examples
 | 
			
		||||
 | 
			
		||||
use herodb::zaz::cmd::examples::run_db_examples;
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    println!("Starting HeroDB examples...");
 | 
			
		||||
    
 | 
			
		||||
    match run_db_examples() {
 | 
			
		||||
        Ok(_) => println!("Examples completed successfully!"),
 | 
			
		||||
        Err(e) => eprintln!("Error running examples: {}", e),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,98 +1,91 @@
 | 
			
		||||
# Zaz DB System
 | 
			
		||||
# HeroDB Architecture
 | 
			
		||||
 | 
			
		||||
The Zaz DB system is a new implementation that provides automatic database persistence for all root models in the system.
 | 
			
		||||
This document explains the architecture of HeroDB, focusing on the separation between model definitions and database logic.
 | 
			
		||||
 | 
			
		||||
## Architecture
 | 
			
		||||
## Core Principles
 | 
			
		||||
 | 
			
		||||
- Each root model (User, Company, Meeting, Product, Sale, Vote, Shareholder) is stored in its own database file
 | 
			
		||||
- The DB system uses Sled, a high-performance embedded database
 | 
			
		||||
- Each model is automatically serialized with Bincode and compressed with Brotli
 | 
			
		||||
- The DB system provides generic methods that work with any model type
 | 
			
		||||
1. **Separation of Concerns**: The DB core should not know about specific models
 | 
			
		||||
2. **Registration-Based System**: Models get registered with the DB through a factory pattern
 | 
			
		||||
3. **Type-Safety**: Despite the separation, we maintain full type safety
 | 
			
		||||
 | 
			
		||||
## Directory Structure
 | 
			
		||||
## Components
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
src/zaz/
 | 
			
		||||
├── db/
 | 
			
		||||
│   ├── base.rs    # Core traits and SledDB implementation
 | 
			
		||||
│   └── mod.rs     # Main DB implementation that handles all models
 | 
			
		||||
└── models/
 | 
			
		||||
    ├── user.rs
 | 
			
		||||
    ├── company.rs
 | 
			
		||||
    ├── meeting.rs
 | 
			
		||||
    ├── product.rs
 | 
			
		||||
    ├── sale.rs
 | 
			
		||||
    ├── vote.rs
 | 
			
		||||
    ├── shareholder.rs
 | 
			
		||||
    └── lib.rs      # Re-exports all models
 | 
			
		||||
```
 | 
			
		||||
### Core Module
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
The `core` module provides the database foundation without knowing about specific models:
 | 
			
		||||
 | 
			
		||||
- `SledModel` trait: Defines the interface models must implement
 | 
			
		||||
- `Storable` trait: Provides serialization/deserialization capabilities
 | 
			
		||||
- `SledDB<T>`: Generic database wrapper for any model type
 | 
			
		||||
- `DB`: Main database manager that holds registered models
 | 
			
		||||
- `DBBuilder`: Builder for creating a DB with registered models
 | 
			
		||||
 | 
			
		||||
### Zaz Module
 | 
			
		||||
 | 
			
		||||
The `zaz` module contains domain-specific models and factories:
 | 
			
		||||
 | 
			
		||||
- `models`: Defines specific model types like User, Company, etc.
 | 
			
		||||
- `factory`: Provides functions to create a DB with zaz models registered
 | 
			
		||||
 | 
			
		||||
## Using the DB
 | 
			
		||||
 | 
			
		||||
### Option 1: Factory Function
 | 
			
		||||
 | 
			
		||||
The easiest way to create a DB with all zaz models is to use the factory:
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
use crate::db::core::DB;
 | 
			
		||||
use crate::zaz::models::*;
 | 
			
		||||
use herodb::zaz::create_zaz_db;
 | 
			
		||||
 | 
			
		||||
// Create a DB instance (handles all model types)
 | 
			
		||||
let db = DB::new("/path/to/db").expect("Failed to create DB");
 | 
			
		||||
// Create a DB with all zaz models registered
 | 
			
		||||
let db = create_zaz_db("/path/to/db")?;
 | 
			
		||||
 | 
			
		||||
// --- User Example ---
 | 
			
		||||
let user = User::new(
 | 
			
		||||
    1,
 | 
			
		||||
    "John Doe".to_string(),
 | 
			
		||||
    "john@example.com".to_string(),
 | 
			
		||||
    "password123".to_string(),
 | 
			
		||||
    "ACME Corp".to_string(),
 | 
			
		||||
    "Admin".to_string(),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// Insert user (DB automatically detects the type)
 | 
			
		||||
db.set(&user).expect("Failed to insert user");
 | 
			
		||||
 | 
			
		||||
// Get user
 | 
			
		||||
let retrieved_user: User = db.get(&user.id.to_string())
 | 
			
		||||
    .expect("Failed to get user");
 | 
			
		||||
 | 
			
		||||
// List all users
 | 
			
		||||
let users: Vec<User> = db.list().expect("Failed to list users");
 | 
			
		||||
 | 
			
		||||
// Delete user
 | 
			
		||||
db.delete::<User>(&user.id.to_string()).expect("Failed to delete user");
 | 
			
		||||
 | 
			
		||||
// --- Company Example ---
 | 
			
		||||
let company = Company::new(
 | 
			
		||||
    1,
 | 
			
		||||
    "ACME Corporation".to_string(),
 | 
			
		||||
    "REG12345".to_string(),
 | 
			
		||||
    Utc::now(),
 | 
			
		||||
    "12-31".to_string(),
 | 
			
		||||
    // other fields...
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// Similar operations for company and other models
 | 
			
		||||
 | 
			
		||||
// --- Direct Database Access ---
 | 
			
		||||
// You can also access the specific database for a model type directly
 | 
			
		||||
let user_db = db.user_db();
 | 
			
		||||
let company_db = db.company_db();
 | 
			
		||||
// etc.
 | 
			
		||||
// Use the DB with specific model types
 | 
			
		||||
let user = User::new(...);
 | 
			
		||||
db.set(&user)?;
 | 
			
		||||
let retrieved: User = db.get(&id)?;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Benefits
 | 
			
		||||
### Option 2: Builder Pattern
 | 
			
		||||
 | 
			
		||||
1. **Automatic Type Handling**: The DB system automatically detects the model type and routes operations to the appropriate database
 | 
			
		||||
2. **Generic Interface**: Same methods work with any model type
 | 
			
		||||
3. **Persistence**: All models are automatically persisted to disk
 | 
			
		||||
4. **Performance**: Fast serialization with Bincode and efficient compression with Brotli
 | 
			
		||||
5. **Storage Separation**: Each model type has its own database file, making maintenance easier
 | 
			
		||||
For more control, use the builder pattern to register only the models you need:
 | 
			
		||||
 | 
			
		||||
## Implementation Notes
 | 
			
		||||
```rust
 | 
			
		||||
use herodb::core::{DBBuilder, DB};
 | 
			
		||||
use herodb::zaz::models::{User, Company};
 | 
			
		||||
 | 
			
		||||
- Each model implements the `SledModel` trait which provides the necessary methods for database operations
 | 
			
		||||
- The `Storable` trait handles serialization and deserialization
 | 
			
		||||
- The DB uses separate Sled databases for each model type to ensure proper separation of concerns
 | 
			
		||||
- Type-safe operations are ensured through Rust's type system
 | 
			
		||||
// Create a DB with only User and Company models
 | 
			
		||||
let db = DBBuilder::new("/path/to/db")
 | 
			
		||||
    .register_model::<User>()
 | 
			
		||||
    .register_model::<Company>()
 | 
			
		||||
    .build()?;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Examples
 | 
			
		||||
### Option 3: Dynamic Registration
 | 
			
		||||
 | 
			
		||||
See the `examples.rs` file for complete examples of how to use the DB system.
 | 
			
		||||
You can also register models with an existing DB:
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
use herodb::core::DB;
 | 
			
		||||
use herodb::zaz::models::User;
 | 
			
		||||
 | 
			
		||||
// Create an empty DB
 | 
			
		||||
let mut db = DB::new("/path/to/db")?;
 | 
			
		||||
 | 
			
		||||
// Register the User model
 | 
			
		||||
db.register::<User>()?;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Benefits of this Architecture
 | 
			
		||||
 | 
			
		||||
1. **Modularity**: The core DB code doesn't need to change when models change
 | 
			
		||||
2. **Extensibility**: New model types can be added without modifying core DB code
 | 
			
		||||
3. **Flexibility**: Different modules can define and use their own models with the same DB code
 | 
			
		||||
4. **Type Safety**: Full compile-time type checking is maintained
 | 
			
		||||
 | 
			
		||||
## Implementation Details
 | 
			
		||||
 | 
			
		||||
The key to this architecture is the combination of generic types and trait objects:
 | 
			
		||||
 | 
			
		||||
- `SledDB<T>` provides type-safe operations for specific model types
 | 
			
		||||
- `AnyDbOperations` trait allows type-erased operations through a common interface
 | 
			
		||||
- `TypeId` mapping enables runtime lookup of the correct DB for a given model type
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
//! Examples demonstrating how to use the new DB implementation
 | 
			
		||||
 | 
			
		||||
use crate::db::core::DB;
 | 
			
		||||
use crate::db::zaz::models::*;
 | 
			
		||||
use crate::db::zaz::models::shareholder::ShareholderType;
 | 
			
		||||
// Core DB is now imported via the factory
 | 
			
		||||
use crate::zaz::models::*;
 | 
			
		||||
use crate::zaz::models::shareholder::ShareholderType;
 | 
			
		||||
use crate::zaz::factory::create_zaz_db;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
use std::fs;
 | 
			
		||||
use chrono::Utc;
 | 
			
		||||
@@ -28,7 +29,7 @@ pub fn run_db_examples() -> Result<(), String> {
 | 
			
		||||
    println!("Using DB path: {:?}", db_path);
 | 
			
		||||
    
 | 
			
		||||
    // Create a DB instance
 | 
			
		||||
    let db = DB::new(db_path).map_err(|e| format!("Failed to create DB: {}", e))?;
 | 
			
		||||
    let db = create_zaz_db(db_path).map_err(|e| format!("Failed to create DB: {}", e))?;
 | 
			
		||||
    
 | 
			
		||||
    // --- User Example ---
 | 
			
		||||
    println!("\nRunning User example:");
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								herodb/src/zaz/cmd/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								herodb/src/zaz/cmd/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
//! Command line examples and utilities for the zaz module
 | 
			
		||||
 | 
			
		||||
pub mod examples;
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
// Examples for using the Zaz database
 | 
			
		||||
 | 
			
		||||
use crate::db::core::DB;
 | 
			
		||||
use crate::db::zaz::models::*;
 | 
			
		||||
use crate::zaz::models::*;
 | 
			
		||||
use crate::zaz::factory::create_zaz_db;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
use chrono::Utc;
 | 
			
		||||
 | 
			
		||||
@@ -14,7 +14,7 @@ pub fn run_db_examples() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    std::fs::create_dir_all(&db_path)?;
 | 
			
		||||
    
 | 
			
		||||
    // Create DB instance
 | 
			
		||||
    let db = DB::new(&db_path)?;
 | 
			
		||||
    let db = create_zaz_db(&db_path)?;
 | 
			
		||||
    
 | 
			
		||||
    // Example 1: User operations
 | 
			
		||||
    println!("\n--- User Examples ---");
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								herodb/src/zaz/factory.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								herodb/src/zaz/factory.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
//! Factory module for creating a DB with all zaz models registered
 | 
			
		||||
 | 
			
		||||
use crate::core::{DB, DBBuilder, SledDBResult};
 | 
			
		||||
use crate::zaz::models::*;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
 | 
			
		||||
/// Create a new DB instance with all zaz models registered
 | 
			
		||||
pub fn create_zaz_db<P: Into<PathBuf>>(path: P) -> SledDBResult<DB> {
 | 
			
		||||
    // Using the builder pattern to register all models
 | 
			
		||||
    DBBuilder::new(path)
 | 
			
		||||
        .register_model::<User>()
 | 
			
		||||
        .register_model::<Company>()
 | 
			
		||||
        .register_model::<Meeting>()
 | 
			
		||||
        .register_model::<Product>()
 | 
			
		||||
        .register_model::<Sale>()
 | 
			
		||||
        .register_model::<Vote>()
 | 
			
		||||
        .register_model::<Shareholder>()
 | 
			
		||||
        .build()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Register all zaz models with an existing DB instance
 | 
			
		||||
pub fn register_zaz_models(db: &mut DB) -> SledDBResult<()> {
 | 
			
		||||
    // Dynamically register all zaz models
 | 
			
		||||
    db.register::<User>()?;
 | 
			
		||||
    db.register::<Company>()?;
 | 
			
		||||
    db.register::<Meeting>()?;
 | 
			
		||||
    db.register::<Product>()?;
 | 
			
		||||
    db.register::<Sale>()?;
 | 
			
		||||
    db.register::<Vote>()?;
 | 
			
		||||
    db.register::<Shareholder>()?;
 | 
			
		||||
    
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
@@ -2,8 +2,15 @@
 | 
			
		||||
#[path = "models/lib.rs"] // Tell compiler where to find models module source
 | 
			
		||||
pub mod models;
 | 
			
		||||
 | 
			
		||||
// Add a factory module for DB creation
 | 
			
		||||
mod factory;
 | 
			
		||||
pub use factory::create_zaz_db;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Declare the examples module for the new DB implementation
 | 
			
		||||
#[path = "examples.rs"] // Tell compiler where to find the examples module
 | 
			
		||||
pub mod examples;
 | 
			
		||||
 | 
			
		||||
// Expose the cmd module
 | 
			
		||||
pub mod cmd;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
use crate::db::core::{SledModel, Storable, SledDB, SledDBError}; // Import from new location
 | 
			
		||||
use crate::core::{SledModel, Storable, SledDB, SledDBError};
 | 
			
		||||
use super::shareholder::Shareholder; // Use super:: for sibling module
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
@@ -110,127 +110,3 @@ impl Company {
 | 
			
		||||
    // Removed dump and load_from_bytes methods, now provided by Storable trait
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::db::zaz::db::{SledDB, SledDBError, SledModel};
 | 
			
		||||
    use crate::db::zaz::models::shareholder::{Shareholder, ShareholderType};
 | 
			
		||||
    use tempfile::tempdir;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_company_sled_crud() {
 | 
			
		||||
        // 1. Setup: Create a temporary directory for the Sled DB
 | 
			
		||||
        let dir = tempdir().expect("Failed to create temp dir");
 | 
			
		||||
        let db_path = dir.path();
 | 
			
		||||
        let company_db: SledDB<Company> = SledDB::open(db_path.join("company")).expect("Failed to open Company Sled DB");
 | 
			
		||||
        let shareholder_db: SledDB<Shareholder> = SledDB::open(db_path.join("shareholder")).expect("Failed to open Shareholder Sled DB");
 | 
			
		||||
 | 
			
		||||
        // 2. Create a sample Company
 | 
			
		||||
        let incorporation_date = Utc::now();
 | 
			
		||||
        let mut company1 = Company::new(
 | 
			
		||||
            1,
 | 
			
		||||
            "Test Corp".to_string(),
 | 
			
		||||
            "REG123".to_string(),
 | 
			
		||||
            incorporation_date,
 | 
			
		||||
            "12-31".to_string(),
 | 
			
		||||
            "test@corp.com".to_string(),
 | 
			
		||||
            "123-456-7890".to_string(),
 | 
			
		||||
            "www.testcorp.com".to_string(),
 | 
			
		||||
            "123 Test St".to_string(),
 | 
			
		||||
            BusinessType::Global,
 | 
			
		||||
            "Tech".to_string(),
 | 
			
		||||
            "A test company".to_string(),
 | 
			
		||||
            CompanyStatus::Active,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let company_id = company1.get_id();
 | 
			
		||||
 | 
			
		||||
        // 3. Create and add a shareholder to the company
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        // Define shareholder properties separately
 | 
			
		||||
        let shareholder_id = 1;
 | 
			
		||||
        let shareholder_name = "Dummy Shareholder".to_string();
 | 
			
		||||
        
 | 
			
		||||
        // Create the shareholder
 | 
			
		||||
        let shareholder = Shareholder::new(
 | 
			
		||||
            shareholder_id,
 | 
			
		||||
            0, // company_id will be set by add_shareholder
 | 
			
		||||
            0, // user_id
 | 
			
		||||
            shareholder_name.clone(),
 | 
			
		||||
            100.0, // shares
 | 
			
		||||
            10.0,  // percentage
 | 
			
		||||
            ShareholderType::Individual,
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Add the shareholder
 | 
			
		||||
        company1.add_shareholder(&shareholder_db, shareholder).expect("Failed to add shareholder");
 | 
			
		||||
 | 
			
		||||
        // 4. Insert the company
 | 
			
		||||
        company_db.insert(&company1).expect("Failed to insert company");
 | 
			
		||||
 | 
			
		||||
        // 5. Get and Assert
 | 
			
		||||
        let retrieved_company = company_db.get(&company_id).expect("Failed to get company");
 | 
			
		||||
        assert_eq!(company1, retrieved_company, "Retrieved company does not match original");
 | 
			
		||||
 | 
			
		||||
        // 6. List and Assert
 | 
			
		||||
        let all_companies = company_db.list().expect("Failed to list companies");
 | 
			
		||||
        assert_eq!(all_companies.len(), 1, "Should be one company in the list");
 | 
			
		||||
        assert_eq!(all_companies[0], company1, "List should contain the inserted company");
 | 
			
		||||
 | 
			
		||||
        // 7. Delete
 | 
			
		||||
        company_db.delete(&company_id).expect("Failed to delete company");
 | 
			
		||||
 | 
			
		||||
        // 8. Get after delete and Assert NotFound
 | 
			
		||||
        match company_db.get(&company_id) {
 | 
			
		||||
            Err(SledDBError::NotFound(id)) => {
 | 
			
		||||
                assert_eq!(id, company_id, "NotFound error should contain the correct ID");
 | 
			
		||||
            }
 | 
			
		||||
            Ok(_) => panic!("Should not have found the company after deletion"),
 | 
			
		||||
            Err(e) => panic!("Unexpected error after delete: {:?}", e),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 9. List after delete
 | 
			
		||||
        let companies_after_delete = company_db.list().expect("Failed to list companies after delete");
 | 
			
		||||
        assert!(companies_after_delete.is_empty(), "List should be empty after deletion");
 | 
			
		||||
 | 
			
		||||
        // 10. Check if shareholder exists in shareholder db
 | 
			
		||||
        let retrieved_shareholder = shareholder_db.get(&shareholder_id.to_string()).expect("Failed to get shareholder");
 | 
			
		||||
        assert_eq!(shareholder_id, retrieved_shareholder.id, "Retrieved shareholder should have the correct ID");
 | 
			
		||||
        assert_eq!(shareholder_name, retrieved_shareholder.name, "Retrieved shareholder should have the correct name");
 | 
			
		||||
        assert_eq!(1, retrieved_shareholder.company_id, "Retrieved shareholder should have company_id set to 1");
 | 
			
		||||
 | 
			
		||||
        // Temporary directory `dir` is automatically removed when it goes out of scope here.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_dump_load() {
 | 
			
		||||
         // Create a sample Company
 | 
			
		||||
        let incorporation_date = Utc::now();
 | 
			
		||||
        let original_company = Company::new(
 | 
			
		||||
            2,
 | 
			
		||||
            "DumpLoad Test".to_string(),
 | 
			
		||||
            "DL987".to_string(),
 | 
			
		||||
            incorporation_date,
 | 
			
		||||
            "06-30".to_string(),
 | 
			
		||||
            "dump@load.com".to_string(),
 | 
			
		||||
            "987-654-3210".to_string(),
 | 
			
		||||
            "www.dumpload.com".to_string(),
 | 
			
		||||
            "456 DumpLoad Ave".to_string(),
 | 
			
		||||
            BusinessType::Coop,
 | 
			
		||||
            "Testing".to_string(),
 | 
			
		||||
            "Testing dump and load".to_string(),
 | 
			
		||||
            CompanyStatus::Active,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Dump (serialize + compress)
 | 
			
		||||
        let dumped_data = original_company.dump().expect("Failed to dump company");
 | 
			
		||||
        assert!(!dumped_data.is_empty(), "Dumped data should not be empty");
 | 
			
		||||
 | 
			
		||||
        // Load (decompress + deserialize)
 | 
			
		||||
        let loaded_company = Company::load_from_bytes(&dumped_data).expect("Failed to load company from bytes");
 | 
			
		||||
 | 
			
		||||
        // Assert equality
 | 
			
		||||
        assert_eq!(original_company, loaded_company, "Loaded company should match the original");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,8 +18,5 @@ pub use sale::Sale;
 | 
			
		||||
pub use shareholder::Shareholder;
 | 
			
		||||
 | 
			
		||||
// Re-export database components
 | 
			
		||||
// pub use db::{DB, DBError, DBResult, Model, ModelMetadata}; // Removed old DB re-exports
 | 
			
		||||
pub use crate::db::core::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB}; // Re-export Sled DB components
 | 
			
		||||
 | 
			
		||||
// Re-export migration components - Removed
 | 
			
		||||
// pub use migration::{Migrator, MigrationError, MigrationResult};
 | 
			
		||||
// Re-export from core module
 | 
			
		||||
pub use crate::core::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use crate::db::core::{SledModel, Storable}; // Import Sled traits from new location
 | 
			
		||||
use crate::core::{SledModel, Storable}; // Import Sled traits from new location
 | 
			
		||||
// use std::collections::HashMap; // Removed unused import
 | 
			
		||||
 | 
			
		||||
// use super::db::Model; // Removed old Model trait import
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
use chrono::{DateTime, Utc, Duration};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use crate::db::core::{SledModel, Storable}; // Import Sled traits from new location
 | 
			
		||||
use crate::core::{SledModel, Storable}; // Import Sled traits from new location
 | 
			
		||||
 | 
			
		||||
/// Currency represents a monetary value with amount and currency code
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
use super::product::Currency; // Use super:: for sibling module
 | 
			
		||||
use crate::db::core::{SledModel, Storable}; // Import Sled traits from new location
 | 
			
		||||
use crate::core::{SledModel, Storable}; // Import Sled traits from new location
 | 
			
		||||
// use super::db::Model; // Removed old Model trait import
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
use crate::db::core::{SledModel, Storable}; // Import Sled traits
 | 
			
		||||
use crate::core::{SledModel, Storable}; // Import Sled traits
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
// use std::collections::HashMap; // Removed unused import
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use crate::db::core::{SledModel, Storable}; // Import Sled traits from new location
 | 
			
		||||
use crate::core::{SledModel, Storable}; // Import Sled traits
 | 
			
		||||
// use std::collections::HashMap; // Removed unused import
 | 
			
		||||
 | 
			
		||||
/// User represents a user in the Freezone Manager system
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use crate::db::core::{SledModel, Storable}; // Import Sled traits from new location
 | 
			
		||||
use crate::core::{SledModel, Storable}; // Import Sled traits from new location
 | 
			
		||||
// use std::collections::HashMap; // Removed unused import
 | 
			
		||||
 | 
			
		||||
// use super::db::Model; // Removed old Model trait import
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										185
									
								
								herodb/src/zaz/tests/model_db_test.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								herodb/src/zaz/tests/model_db_test.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,185 @@
 | 
			
		||||
//! Tests for the new model-DB architecture
 | 
			
		||||
 | 
			
		||||
use crate::core::{DB, DBBuilder, SledDBResult, Storable, SledModel};
 | 
			
		||||
use crate::zaz::factory::create_zaz_db;
 | 
			
		||||
use crate::zaz::models::*;
 | 
			
		||||
use chrono::Utc;
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
use tempfile::tempdir;
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_zaz_db_factory() {
 | 
			
		||||
    // Create a temporary directory for testing
 | 
			
		||||
    let temp_dir = tempdir().expect("Failed to create temp directory");
 | 
			
		||||
    println!("Created temporary directory at: {:?}", temp_dir.path());
 | 
			
		||||
    
 | 
			
		||||
    // Create a DB with all zaz models registered using the factory
 | 
			
		||||
    let db = create_zaz_db(temp_dir.path()).expect("Failed to create zaz DB");
 | 
			
		||||
    
 | 
			
		||||
    // Test with a user model
 | 
			
		||||
    let user = User::new(
 | 
			
		||||
        1,
 | 
			
		||||
        "Factory Test User".to_string(),
 | 
			
		||||
        "factory@example.com".to_string(),
 | 
			
		||||
        "password".to_string(),
 | 
			
		||||
        "Test Company".to_string(),
 | 
			
		||||
        "User".to_string(),
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    // Insert the user
 | 
			
		||||
    db.set(&user).expect("Failed to insert user");
 | 
			
		||||
    
 | 
			
		||||
    // Retrieve the user
 | 
			
		||||
    let retrieved: User = db.get(&user.id.to_string()).expect("Failed to retrieve user");
 | 
			
		||||
    
 | 
			
		||||
    // Verify the user is correct
 | 
			
		||||
    assert_eq!(user.name, retrieved.name);
 | 
			
		||||
    assert_eq!(user.email, retrieved.email);
 | 
			
		||||
    
 | 
			
		||||
    // Test with a company model
 | 
			
		||||
    let company = Company::new(
 | 
			
		||||
        1,
 | 
			
		||||
        "Factory Test Corp".to_string(),
 | 
			
		||||
        "FTC-123".to_string(),
 | 
			
		||||
        Utc::now(),
 | 
			
		||||
        "12-31".to_string(),
 | 
			
		||||
        "info@ftc.com".to_string(),
 | 
			
		||||
        "123-456-7890".to_string(),
 | 
			
		||||
        "www.ftc.com".to_string(),
 | 
			
		||||
        "123 Factory St".to_string(),
 | 
			
		||||
        BusinessType::Global,
 | 
			
		||||
        "Technology".to_string(),
 | 
			
		||||
        "A test company for the factory pattern".to_string(),
 | 
			
		||||
        CompanyStatus::Active,
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    // Insert the company
 | 
			
		||||
    db.set(&company).expect("Failed to insert company");
 | 
			
		||||
    
 | 
			
		||||
    // Retrieve the company
 | 
			
		||||
    let retrieved: Company = db.get(&company.id.to_string()).expect("Failed to retrieve company");
 | 
			
		||||
    
 | 
			
		||||
    // Verify the company is correct
 | 
			
		||||
    assert_eq!(company.name, retrieved.name);
 | 
			
		||||
    assert_eq!(company.registration_number, retrieved.registration_number);
 | 
			
		||||
    
 | 
			
		||||
    println!("All zaz DB factory tests passed successfully!");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_db_builder() {
 | 
			
		||||
    // Create a temporary directory for testing
 | 
			
		||||
    let temp_dir = tempdir().expect("Failed to create temp directory");
 | 
			
		||||
    println!("Created temporary directory at: {:?}", temp_dir.path());
 | 
			
		||||
    
 | 
			
		||||
    // Create a DB with selectively registered models using the builder pattern
 | 
			
		||||
    let db = DBBuilder::new(temp_dir.path())
 | 
			
		||||
        .register_model::<User>()
 | 
			
		||||
        .register_model::<Company>()
 | 
			
		||||
        .build()
 | 
			
		||||
        .expect("Failed to build DB");
 | 
			
		||||
    
 | 
			
		||||
    // Test with a user model
 | 
			
		||||
    let user = User::new(
 | 
			
		||||
        2,
 | 
			
		||||
        "Builder Test User".to_string(),
 | 
			
		||||
        "builder@example.com".to_string(),
 | 
			
		||||
        "password".to_string(),
 | 
			
		||||
        "Test Company".to_string(),
 | 
			
		||||
        "User".to_string(),
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    // Insert the user
 | 
			
		||||
    db.set(&user).expect("Failed to insert user");
 | 
			
		||||
    
 | 
			
		||||
    // Retrieve the user
 | 
			
		||||
    let retrieved: User = db.get(&user.id.to_string()).expect("Failed to retrieve user");
 | 
			
		||||
    
 | 
			
		||||
    // Verify the user is correct
 | 
			
		||||
    assert_eq!(user.name, retrieved.name);
 | 
			
		||||
    assert_eq!(user.email, retrieved.email);
 | 
			
		||||
    
 | 
			
		||||
    // Test that unregistered models cause an error
 | 
			
		||||
    let product = Product::new(
 | 
			
		||||
        1,
 | 
			
		||||
        "Unregistered Product".to_string(),
 | 
			
		||||
        "PROD-123".to_string(),
 | 
			
		||||
        "A test product".to_string(),
 | 
			
		||||
        "Test".to_string(),
 | 
			
		||||
        ProductType::Standard,
 | 
			
		||||
        ProductStatus::Available,
 | 
			
		||||
        Currency::USD,
 | 
			
		||||
        100.0,
 | 
			
		||||
        vec![],
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    // This should fail because Product was not registered
 | 
			
		||||
    let result = db.set(&product);
 | 
			
		||||
    assert!(result.is_err(), "Setting unregistered model should fail");
 | 
			
		||||
    
 | 
			
		||||
    println!("All DB builder tests passed successfully!");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_dynamic_registration() {
 | 
			
		||||
    // Create a temporary directory for testing
 | 
			
		||||
    let temp_dir = tempdir().expect("Failed to create temp directory");
 | 
			
		||||
    println!("Created temporary directory at: {:?}", temp_dir.path());
 | 
			
		||||
    
 | 
			
		||||
    // Create an empty DB
 | 
			
		||||
    let mut db = DB::new(temp_dir.path()).expect("Failed to create empty DB");
 | 
			
		||||
    
 | 
			
		||||
    // Register User model dynamically
 | 
			
		||||
    db.register::<User>().expect("Failed to register User");
 | 
			
		||||
    
 | 
			
		||||
    // Test with a user model
 | 
			
		||||
    let user = User::new(
 | 
			
		||||
        3,
 | 
			
		||||
        "Dynamic Test User".to_string(),
 | 
			
		||||
        "dynamic@example.com".to_string(),
 | 
			
		||||
        "password".to_string(),
 | 
			
		||||
        "Test Company".to_string(),
 | 
			
		||||
        "User".to_string(),
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    // Insert the user
 | 
			
		||||
    db.set(&user).expect("Failed to insert user");
 | 
			
		||||
    
 | 
			
		||||
    // Retrieve the user
 | 
			
		||||
    let retrieved: User = db.get(&user.id.to_string()).expect("Failed to retrieve user");
 | 
			
		||||
    
 | 
			
		||||
    // Verify the user is correct
 | 
			
		||||
    assert_eq!(user.name, retrieved.name);
 | 
			
		||||
    assert_eq!(user.email, retrieved.email);
 | 
			
		||||
    
 | 
			
		||||
    // Now dynamically register Company
 | 
			
		||||
    db.register::<Company>().expect("Failed to register Company");
 | 
			
		||||
    
 | 
			
		||||
    // Test with a company model
 | 
			
		||||
    let company = Company::new(
 | 
			
		||||
        3,
 | 
			
		||||
        "Dynamic Test Corp".to_string(),
 | 
			
		||||
        "DTC-123".to_string(),
 | 
			
		||||
        Utc::now(),
 | 
			
		||||
        "12-31".to_string(),
 | 
			
		||||
        "info@dtc.com".to_string(),
 | 
			
		||||
        "123-456-7890".to_string(),
 | 
			
		||||
        "www.dtc.com".to_string(),
 | 
			
		||||
        "123 Dynamic St".to_string(),
 | 
			
		||||
        BusinessType::Global,
 | 
			
		||||
        "Technology".to_string(),
 | 
			
		||||
        "A test company for dynamic registration".to_string(),
 | 
			
		||||
        CompanyStatus::Active,
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    // Insert the company
 | 
			
		||||
    db.set(&company).expect("Failed to insert company");
 | 
			
		||||
    
 | 
			
		||||
    // Retrieve the company
 | 
			
		||||
    let retrieved: Company = db.get(&company.id.to_string()).expect("Failed to retrieve company");
 | 
			
		||||
    
 | 
			
		||||
    // Verify the company is correct
 | 
			
		||||
    assert_eq!(company.name, retrieved.name);
 | 
			
		||||
    
 | 
			
		||||
    println!("All dynamic registration tests passed successfully!");
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user