...
This commit is contained in:
		@@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "herodb"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2024"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
description = "A database library built on top of ourdb with model support"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
authors = ["HeroCode Team"]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										83
									
								
								herodb/examples/minimal_ourdb_example.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								herodb/examples/minimal_ourdb_example.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
use herodb::db::{DB, DBBuilder, Model};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use chrono::Utc;
 | 
			
		||||
 | 
			
		||||
// Define a simple Product model
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
struct SimpleProduct {
 | 
			
		||||
    id: u32,
 | 
			
		||||
    name: String,
 | 
			
		||||
    description: String,
 | 
			
		||||
    price: f64,
 | 
			
		||||
    created_at: chrono::DateTime<Utc>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Implement the Model trait for SimpleProduct
 | 
			
		||||
impl Model for SimpleProduct {
 | 
			
		||||
    fn get_id(&self) -> u32 {
 | 
			
		||||
        self.id
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn db_prefix() -> &'static str {
 | 
			
		||||
        "simple_product"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    println!("OurDB Minimal Example");
 | 
			
		||||
    println!("====================\n");
 | 
			
		||||
 | 
			
		||||
    // Create a temporary directory for the database
 | 
			
		||||
    let db_path = std::env::temp_dir().join("herodb_minimal_example");
 | 
			
		||||
    std::fs::create_dir_all(&db_path)?;
 | 
			
		||||
    
 | 
			
		||||
    println!("Creating database at: {}", db_path.display());
 | 
			
		||||
    
 | 
			
		||||
    // Create a new database with SimpleProduct model registered
 | 
			
		||||
    let db = DBBuilder::new(db_path.clone())
 | 
			
		||||
        .register_model::<SimpleProduct>()
 | 
			
		||||
        .build()?;
 | 
			
		||||
    
 | 
			
		||||
    println!("Database created successfully");
 | 
			
		||||
    
 | 
			
		||||
    // Create a product
 | 
			
		||||
    let product = SimpleProduct {
 | 
			
		||||
        id: 1,
 | 
			
		||||
        name: "Test Product".to_string(),
 | 
			
		||||
        description: "A test product for our minimal OurDB example".to_string(),
 | 
			
		||||
        price: 99.99,
 | 
			
		||||
        created_at: Utc::now(),
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    println!("\nCreated product: {}", product.name);
 | 
			
		||||
    println!("Product ID: {}", product.get_id());
 | 
			
		||||
    
 | 
			
		||||
    // Insert the product into the database
 | 
			
		||||
    db.set(&product)?;
 | 
			
		||||
    println!("Product saved to database");
 | 
			
		||||
    
 | 
			
		||||
    // Retrieve the product from the database
 | 
			
		||||
    let retrieved_product = db.get::<SimpleProduct>(product.get_id())?;
 | 
			
		||||
    println!("\nRetrieved product from database:");
 | 
			
		||||
    println!("  Name: {}", retrieved_product.name);
 | 
			
		||||
    println!("  Description: {}", retrieved_product.description);
 | 
			
		||||
    println!("  Price: ${}", retrieved_product.price);
 | 
			
		||||
    
 | 
			
		||||
    // Delete the product
 | 
			
		||||
    db.delete::<SimpleProduct>(product.get_id())?;
 | 
			
		||||
    println!("\nProduct deleted from database");
 | 
			
		||||
    
 | 
			
		||||
    // Try to retrieve the deleted product (should fail)
 | 
			
		||||
    match db.get::<SimpleProduct>(product.get_id()) {
 | 
			
		||||
        Ok(_) => println!("Product still exists (unexpected)"),
 | 
			
		||||
        Err(e) => println!("Verified deletion: {}", e),
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    println!("\nExample completed successfully!");
 | 
			
		||||
    
 | 
			
		||||
    // Clean up
 | 
			
		||||
    std::fs::remove_dir_all(&db_path)?;
 | 
			
		||||
    println!("Cleaned up database directory");
 | 
			
		||||
    
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
@@ -21,6 +21,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    
 | 
			
		||||
    // Create a currency for pricing
 | 
			
		||||
    let usd = CurrencyBuilder::new()
 | 
			
		||||
        .id(1) // Add an ID for the currency
 | 
			
		||||
        .amount(99.99)
 | 
			
		||||
        .currency_code("USD")
 | 
			
		||||
        .build()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,144 +0,0 @@
 | 
			
		||||
use bincode;
 | 
			
		||||
use brotli::{CompressorReader, Decompressor};
 | 
			
		||||
use rhai::CustomType;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use sled;
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
use std::io::Read;
 | 
			
		||||
use std::marker::PhantomData;
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
 | 
			
		||||
/// Errors that can occur during Sled database operations
 | 
			
		||||
#[derive(Error, Debug)]
 | 
			
		||||
pub enum SledDBError {
 | 
			
		||||
    #[error("Sled database error: {0}")]
 | 
			
		||||
    SledError(#[from] sled::Error),
 | 
			
		||||
    #[error("Serialization/Deserialization error: {0}")]
 | 
			
		||||
    SerdeError(#[from] bincode::Error),
 | 
			
		||||
    #[error("Compression/Decompression error: {0}")]
 | 
			
		||||
    IoError(#[from] std::io::Error),
 | 
			
		||||
    #[error("Record not found for ID: {0}")]
 | 
			
		||||
    NotFound(String),
 | 
			
		||||
    #[error("Type mismatch during deserialization")]
 | 
			
		||||
    TypeError,
 | 
			
		||||
    #[error("General database error: {0}")]
 | 
			
		||||
    GeneralError(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Result type for Sled DB operations
 | 
			
		||||
pub type SledDBResult<T> = Result<T, SledDBError>;
 | 
			
		||||
 | 
			
		||||
/// Trait for models that can be stored in the Sled database.
 | 
			
		||||
/// Requires `Serialize` and `Deserialize` for the underlying storage mechanism.
 | 
			
		||||
pub trait Storable: Serialize + for<'de> Deserialize<'de> + Sized {
 | 
			
		||||
    /// Serializes and compresses the instance using bincode and brotli.
 | 
			
		||||
    fn dump(&self) -> SledDBResult<Vec<u8>> {
 | 
			
		||||
        let encoded: Vec<u8> = bincode::serialize(self)?;
 | 
			
		||||
 | 
			
		||||
        let mut compressed = Vec::new();
 | 
			
		||||
        // Default Brotli parameters: quality 5, lgwin 22 (window size)
 | 
			
		||||
        const BROTLI_QUALITY: u32 = 5;
 | 
			
		||||
        const BROTLI_LGWIN: u32 = 22;
 | 
			
		||||
        const BUFFER_SIZE: usize = 4096; // 4KB buffer
 | 
			
		||||
 | 
			
		||||
        let mut compressor =
 | 
			
		||||
            CompressorReader::new(&encoded[..], BUFFER_SIZE, BROTLI_QUALITY, BROTLI_LGWIN);
 | 
			
		||||
        compressor.read_to_end(&mut compressed)?;
 | 
			
		||||
 | 
			
		||||
        Ok(compressed)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Deserializes and decompresses data from bytes into an instance.
 | 
			
		||||
    fn load_from_bytes(data: &[u8]) -> SledDBResult<Self> {
 | 
			
		||||
        let mut decompressed = Vec::new();
 | 
			
		||||
        const BUFFER_SIZE: usize = 4096; // 4KB buffer
 | 
			
		||||
 | 
			
		||||
        let mut decompressor = Decompressor::new(data, BUFFER_SIZE);
 | 
			
		||||
        decompressor.read_to_end(&mut decompressed)?;
 | 
			
		||||
 | 
			
		||||
        let decoded: Self = bincode::deserialize(&decompressed)?;
 | 
			
		||||
        Ok(decoded)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Trait identifying a model suitable for the Sled database.
 | 
			
		||||
/// The 'static lifetime bound is required for type identification via Any
 | 
			
		||||
pub trait SledModel: Storable + Debug + Clone + Send + Sync + 'static {
 | 
			
		||||
    /// Returns the unique ID for this model instance, used as the key in Sled.
 | 
			
		||||
    fn get_id(&self) -> String;
 | 
			
		||||
 | 
			
		||||
    /// Returns a prefix used for this model type in the Sled database.
 | 
			
		||||
    /// Helps to logically separate different model types.
 | 
			
		||||
    fn db_prefix() -> &'static str;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A generic database layer on top of Sled.
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct SledDB<T: SledModel> {
 | 
			
		||||
    db: sled::Db,
 | 
			
		||||
    _phantom: PhantomData<T>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: SledModel> SledDB<T> {
 | 
			
		||||
    /// Opens or creates a Sled database at the specified path.
 | 
			
		||||
    pub fn open<P: AsRef<Path>>(path: P) -> SledDBResult<Self> {
 | 
			
		||||
        let db = sled::open(path)?;
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            db,
 | 
			
		||||
            _phantom: PhantomData,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Generates the full Sled key using the model's prefix and ID.
 | 
			
		||||
    fn get_full_key(id: &str) -> Vec<u8> {
 | 
			
		||||
        format!("{}:{}", T::db_prefix(), id).into_bytes()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Inserts or updates a model instance in the database.
 | 
			
		||||
    pub fn insert(&self, model: &T) -> SledDBResult<()> {
 | 
			
		||||
        let key = Self::get_full_key(&model.get_id());
 | 
			
		||||
        let value = model.dump()?;
 | 
			
		||||
        self.db.insert(key, value)?;
 | 
			
		||||
        // Optionally force a disk flush for durability, but it impacts performance.
 | 
			
		||||
        // self.db.flush()?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Retrieves a model instance by its ID.
 | 
			
		||||
    pub fn get(&self, id: &str) -> SledDBResult<T> {
 | 
			
		||||
        let key = Self::get_full_key(id);
 | 
			
		||||
        match self.db.get(&key)? {
 | 
			
		||||
            Some(ivec) => T::load_from_bytes(&ivec),
 | 
			
		||||
            None => Err(SledDBError::NotFound(id.to_string())),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Deletes a model instance by its ID.
 | 
			
		||||
    pub fn delete(&self, id: &str) -> SledDBResult<()> {
 | 
			
		||||
        let key = Self::get_full_key(id);
 | 
			
		||||
        match self.db.remove(&key)? {
 | 
			
		||||
            Some(_) => Ok(()),
 | 
			
		||||
            None => Err(SledDBError::NotFound(id.to_string())),
 | 
			
		||||
        }
 | 
			
		||||
        // Optionally flush after delete
 | 
			
		||||
        // self.db.flush()?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Lists all models of this type.
 | 
			
		||||
    /// Warning: This can be inefficient for large datasets as it loads all models into memory.
 | 
			
		||||
    pub fn list(&self) -> SledDBResult<Vec<T>> {
 | 
			
		||||
        let prefix = format!("{}:", T::db_prefix());
 | 
			
		||||
        let mut models = Vec::new();
 | 
			
		||||
        for result in self.db.scan_prefix(prefix.as_bytes()) {
 | 
			
		||||
            let (_key, value) = result?;
 | 
			
		||||
            models.push(T::load_from_bytes(&value)?);
 | 
			
		||||
        }
 | 
			
		||||
        Ok(models)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Provides access to the underlying Sled Db instance for advanced operations.
 | 
			
		||||
    pub fn raw_db(&self) -> &sled::Db {
 | 
			
		||||
        &self.db
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
ere/// Macro to implement typed access methods on the DB struct for a given model
 | 
			
		||||
//! Macros for implementing model methods
 | 
			
		||||
 | 
			
		||||
/// Macro to implement typed access methods on the DB struct for a given model
 | 
			
		||||
#[macro_export]
 | 
			
		||||
macro_rules! impl_model_methods {
 | 
			
		||||
    ($model:ty, $singular:ident, $plural:ident) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
s using// Export the error module
 | 
			
		||||
// Export the error module
 | 
			
		||||
pub mod error;
 | 
			
		||||
pub use error::{DbError, DbResult};
 | 
			
		||||
 | 
			
		||||
@@ -14,5 +14,8 @@ pub use store::{DbOperations, OurDbStore};
 | 
			
		||||
pub mod db;
 | 
			
		||||
pub use db::{DB, DBBuilder, ModelRegistration, ModelRegistrar};
 | 
			
		||||
 | 
			
		||||
// Export the base module (compatibility layer for migration)
 | 
			
		||||
pub mod base;
 | 
			
		||||
 | 
			
		||||
// Export macros for model methods
 | 
			
		||||
pub mod macros;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user