diff --git a/herodb/Cargo.toml b/herodb/Cargo.toml index 6cf8e54..7a53d9a 100644 --- a/herodb/Cargo.toml +++ b/herodb/Cargo.toml @@ -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"] diff --git a/herodb/examples/minimal_ourdb_example.rs b/herodb/examples/minimal_ourdb_example.rs new file mode 100644 index 0000000..23d4fb6 --- /dev/null +++ b/herodb/examples/minimal_ourdb_example.rs @@ -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, +} + +// 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> { + 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::() + .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::(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::(product.get_id())?; + println!("\nProduct deleted from database"); + + // Try to retrieve the deleted product (should fail) + match db.get::(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(()) +} \ No newline at end of file diff --git a/herodb/examples/ourdb_example.rs b/herodb/examples/ourdb_example.rs index 285aafd..592441c 100644 --- a/herodb/examples/ourdb_example.rs +++ b/herodb/examples/ourdb_example.rs @@ -21,6 +21,7 @@ fn main() -> Result<(), Box> { // Create a currency for pricing let usd = CurrencyBuilder::new() + .id(1) // Add an ID for the currency .amount(99.99) .currency_code("USD") .build() diff --git a/herodb/src/db/base.rs b/herodb/src/db/base.rs deleted file mode 100644 index 444cf58..0000000 --- a/herodb/src/db/base.rs +++ /dev/null @@ -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 = Result; - -/// 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> { - let encoded: Vec = 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 { - 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 { - db: sled::Db, - _phantom: PhantomData, -} - -impl SledDB { - /// Opens or creates a Sled database at the specified path. - pub fn open>(path: P) -> SledDBResult { - 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 { - 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 { - 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> { - 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 - } -} diff --git a/herodb/src/db/macros.rs b/herodb/src/db/macros.rs index 65214c3..66c001a 100644 --- a/herodb/src/db/macros.rs +++ b/herodb/src/db/macros.rs @@ -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) => { diff --git a/herodb/src/db/mod.rs b/herodb/src/db/mod.rs index 9eb5c94..191cb70 100644 --- a/herodb/src/db/mod.rs +++ b/herodb/src/db/mod.rs @@ -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; diff --git a/ourdb_example/Cargo.lock b/ourdb_example/Cargo.lock new file mode 100644 index 0000000..f43c056 --- /dev/null +++ b/ourdb_example/Cargo.lock @@ -0,0 +1,178 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "ourdb" +version = "0.1.0" +dependencies = [ + "crc32fast", + "log", + "rand", + "thiserror", +] + +[[package]] +name = "ourdb_example" +version = "0.1.0" +dependencies = [ + "ourdb", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/ourdb_example/Cargo.toml b/ourdb_example/Cargo.toml new file mode 100644 index 0000000..ea46db1 --- /dev/null +++ b/ourdb_example/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "ourdb_example" +version = "0.1.0" +edition = "2021" + +[dependencies] +ourdb = { path = "../ourdb" } diff --git a/ourdb_example/src/main.rs b/ourdb_example/src/main.rs new file mode 100644 index 0000000..6d1514e --- /dev/null +++ b/ourdb_example/src/main.rs @@ -0,0 +1,74 @@ +use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; +use std::env::temp_dir; +use std::time::{SystemTime, UNIX_EPOCH}; + +fn main() -> Result<(), Box> { + println!("Standalone OurDB Example"); + println!("=======================\n"); + + // Create a temporary directory for the database + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + let db_path = temp_dir().join(format!("ourdb_example_{}", timestamp)); + std::fs::create_dir_all(&db_path)?; + + println!("Creating database at: {}", db_path.display()); + + // Create a new OurDB instance + let config = OurDBConfig { + path: db_path.clone(), + incremental_mode: true, + file_size: None, + keysize: None, + reset: Some(false), + }; + + let mut db = OurDB::new(config)?; + println!("Database created successfully"); + + // Store some data + let test_data = b"Hello, OurDB!"; + let id = db.set(OurDBSetArgs { id: None, data: test_data })?; + println!("\nStored data with ID: {}", id); + + // Retrieve the data + let retrieved = db.get(id)?; + println!("Retrieved data: {}", String::from_utf8_lossy(&retrieved)); + + // Update the data + let updated_data = b"Updated data in OurDB!"; + db.set(OurDBSetArgs { id: Some(id), data: updated_data })?; + println!("\nUpdated data with ID: {}", id); + + // Retrieve the updated data + let retrieved = db.get(id)?; + println!("Retrieved updated data: {}", String::from_utf8_lossy(&retrieved)); + + // Get history + let history = db.get_history(id, 2)?; + println!("\nHistory for ID {}:", id); + for (i, data) in history.iter().enumerate() { + println!(" Version {}: {}", i + 1, String::from_utf8_lossy(data)); + } + + // Delete the data + db.delete(id)?; + println!("\nDeleted data with ID: {}", id); + + // Try to retrieve the deleted data (should fail) + match db.get(id) { + Ok(_) => println!("Data still exists (unexpected)"), + Err(e) => println!("Verified deletion: {}", e), + } + + println!("\nExample completed successfully!"); + + // Clean up + db.close()?; + std::fs::remove_dir_all(&db_path)?; + println!("Cleaned up database directory"); + + Ok(()) +} diff --git a/standalone_ourdb_example.rs b/standalone_ourdb_example.rs new file mode 100644 index 0000000..f9f5cb2 --- /dev/null +++ b/standalone_ourdb_example.rs @@ -0,0 +1,74 @@ +use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; +use std::env::temp_dir; +use std::time::{SystemTime, UNIX_EPOCH}; + +fn main() -> Result<(), Box> { + println!("Standalone OurDB Example"); + println!("=======================\n"); + + // Create a temporary directory for the database + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + let db_path = temp_dir().join(format!("ourdb_example_{}", timestamp)); + std::fs::create_dir_all(&db_path)?; + + println!("Creating database at: {}", db_path.display()); + + // Create a new OurDB instance + let config = OurDBConfig { + path: db_path.clone(), + incremental_mode: true, + file_size: None, + keysize: None, + reset: Some(false), + }; + + let mut db = OurDB::new(config)?; + println!("Database created successfully"); + + // Store some data + let test_data = b"Hello, OurDB!"; + let id = db.set(OurDBSetArgs { id: None, data: test_data })?; + println!("\nStored data with ID: {}", id); + + // Retrieve the data + let retrieved = db.get(id)?; + println!("Retrieved data: {}", String::from_utf8_lossy(&retrieved)); + + // Update the data + let updated_data = b"Updated data in OurDB!"; + db.set(OurDBSetArgs { id: Some(id), data: updated_data })?; + println!("\nUpdated data with ID: {}", id); + + // Retrieve the updated data + let retrieved = db.get(id)?; + println!("Retrieved updated data: {}", String::from_utf8_lossy(&retrieved)); + + // Get history + let history = db.get_history(id, 2)?; + println!("\nHistory for ID {}:", id); + for (i, data) in history.iter().enumerate() { + println!(" Version {}: {}", i + 1, String::from_utf8_lossy(data)); + } + + // Delete the data + db.delete(id)?; + println!("\nDeleted data with ID: {}", id); + + // Try to retrieve the deleted data (should fail) + match db.get(id) { + Ok(_) => println!("Data still exists (unexpected)"), + Err(e) => println!("Verified deletion: {}", e), + } + + println!("\nExample completed successfully!"); + + // Clean up + db.close()?; + std::fs::remove_dir_all(&db_path)?; + println!("Cleaned up database directory"); + + Ok(()) +} \ No newline at end of file