This commit is contained in:
2025-04-20 08:00:59 +02:00
parent 0051754c65
commit e3ec26a6ef
22 changed files with 971 additions and 691 deletions

140
herodb/Cargo.lock generated
View File

@@ -152,12 +152,6 @@ dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.0"
@@ -200,12 +194,6 @@ version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.10.1"
@@ -320,21 +308,6 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
version = "0.2.3"
@@ -475,16 +448,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
@@ -538,15 +501,6 @@ dependencies = [
"slab",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -659,13 +613,13 @@ dependencies = [
"brotli",
"chrono",
"lazy_static",
"ourdb",
"paste",
"poem",
"poem-openapi",
"rhai",
"serde",
"serde_json",
"sled",
"tempfile",
"thiserror",
"tokio",
@@ -923,7 +877,7 @@ version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
"bitflags 2.9.0",
"bitflags",
"cfg-if",
"libc",
]
@@ -968,14 +922,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
name = "ourdb"
version = "0.1.0"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
"crc32fast",
"log",
"rand",
"thiserror",
]
[[package]]
@@ -985,21 +938,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core 0.9.10",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
"parking_lot_core",
]
[[package]]
@@ -1010,7 +949,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.10",
"redox_syscall",
"smallvec",
"windows-targets",
]
@@ -1056,7 +995,7 @@ dependencies = [
"mime",
"multer",
"nix",
"parking_lot 0.12.3",
"parking_lot",
"percent-encoding",
"pin-project-lite",
"poem-derive",
@@ -1259,22 +1198,13 @@ dependencies = [
"getrandom 0.2.15",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
dependencies = [
"bitflags 2.9.0",
"bitflags",
]
[[package]]
@@ -1322,7 +1252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6"
dependencies = [
"ahash",
"bitflags 2.9.0",
"bitflags",
"instant",
"num-traits",
"once_cell",
@@ -1364,7 +1294,7 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [
"bitflags 2.9.0",
"bitflags",
"errno",
"libc",
"linux-raw-sys",
@@ -1498,22 +1428,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "sled"
version = "0.34.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
dependencies = [
"crc32fast",
"crossbeam-epoch",
"crossbeam-utils",
"fs2",
"fxhash",
"libc",
"log",
"parking_lot 0.11.2",
]
[[package]]
name = "smallvec"
version = "1.14.0"
@@ -1676,7 +1590,7 @@ dependencies = [
"bytes",
"libc",
"mio",
"parking_lot 0.12.3",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
@@ -1931,28 +1845,6 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ce1ab1f8c62655ebe1350f589c61e505cf94d385bc6a12899442d9081e71fd"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.61.0"
@@ -2109,7 +2001,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.0",
"bitflags",
]
[[package]]

View File

@@ -2,12 +2,12 @@
name = "herodb"
version = "0.1.0"
edition = "2024"
description = "A database library built on top of sled with model support"
description = "A database library built on top of ourdb with model support"
license = "MIT"
authors = ["HeroCode Team"]
[dependencies]
sled = "0.34.7"
ourdb = { path = "../ourdb" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
@@ -31,6 +31,10 @@ path = "examples/rhai_demo.rs"
name = "business_models_demo"
path = "examples/business_models_demo.rs"
[[example]]
name = "ourdb_example"
path = "examples/ourdb_example.rs"
[[bin]]
name = "dbexample_prod"
path = "src/cmd/dbexample_prod/main.rs"

View File

@@ -0,0 +1,80 @@
use herodb::db::{DB, DBBuilder, Model};
use herodb::models::biz::{Product, ProductBuilder, ProductType, ProductStatus, Currency, CurrencyBuilder};
use chrono::Utc;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("OurDB Backend Example");
println!("====================\n");
// Create a temporary directory for the database
let db_path = std::env::temp_dir().join("herodb_ourdb_example");
std::fs::create_dir_all(&db_path)?;
println!("Creating database at: {}", db_path.display());
// Create a new database with Product model registered
let db = DBBuilder::new(db_path.clone())
.register_model::<Product>()
.build()?;
println!("Database created successfully");
// Create a currency for pricing
let usd = CurrencyBuilder::new()
.amount(99.99)
.currency_code("USD")
.build()
.expect("Failed to create currency");
// Create a product
let product = ProductBuilder::new()
.id(1) // We're setting an ID manually for this example
.name("Test Product")
.description("A test product for our OurDB example")
.price(usd)
.type_(ProductType::Product)
.category("Test")
.status(ProductStatus::Available)
.max_amount(100)
.validity_days(365)
.build()
.expect("Failed to create product");
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>(product.get_id())?;
println!("\nRetrieved product from database:");
println!(" Name: {}", retrieved_product.name);
println!(" Description: {}", retrieved_product.description);
println!(" Price: ${} {}", retrieved_product.price.amount, retrieved_product.price.currency_code);
// Create a product with auto-incremented ID
// For this to work, we would need to modify the Product model to support auto-incremented IDs
// This is just a conceptual example
println!("\nDemonstrating auto-incremented IDs:");
println!("(Note: This would require additional implementation in the Product model)");
// Delete the product
db.delete::<Product>(product.get_id())?;
println!("\nProduct deleted from database");
// Try to retrieve the deleted product (should fail)
match db.get::<Product>(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(())
}

View File

@@ -6,7 +6,7 @@ use crate::models::biz::{
Invoice, InvoiceBuilder, InvoiceItem, InvoiceItemBuilder, InvoiceStatus, Payment, PaymentStatus,
Customer, CustomerBuilder,
};
use crate::db::base::SledModel;
use crate::db::model::Model;
/// This example demonstrates the business models in action:
/// 1. Defining products (2 types of server nodes)
@@ -41,13 +41,13 @@ fn main() {
// Simulate a user buying a product
println!("\nSimulating purchase of a Premium Node:");
let sale = create_sale(&customer, &premium_node);
println!(" - Sale created with ID: {}", sale.id);
println!(" - Sale created with ID: {}", sale.get_id());
println!(" - Total amount: ${} {}", sale.total_amount.amount, sale.total_amount.currency_code);
// Generate an invoice
println!("\nGenerating invoice:");
let invoice = create_invoice(&customer, &sale);
println!(" - Invoice created with ID: {}", invoice.id);
println!(" - Invoice created with ID: {}", invoice.get_id());
println!(" - Total amount: ${} {}", invoice.total_amount.amount, invoice.total_amount.currency_code);
println!(" - Due date: {}", invoice.due_date);
println!(" - Status: {:?}", invoice.status);
@@ -198,7 +198,7 @@ fn create_sale(customer: &Customer, product: &Product) -> Sale {
let sale_item = SaleItemBuilder::new()
.id(1)
.sale_id(1)
.product_id(product.id as u32)
.product_id(product.get_id())
.name(product.name.clone())
.description(product.description.clone())
.comments("Customer requested expedited setup")
@@ -213,7 +213,7 @@ fn create_sale(customer: &Customer, product: &Product) -> Sale {
let sale = SaleBuilder::new()
.id(1)
.company_id(101) // Assuming company ID 101
.customer_id(customer.id)
.customer_id(customer.get_id())
.buyer_name(customer.name.clone())
.buyer_email("contact@techcorp.com") // Example email
.currency_code(product.price.currency_code.clone())
@@ -236,14 +236,14 @@ fn create_invoice(customer: &Customer, sale: &Sale) -> Invoice {
.invoice_id(1)
.description(format!("Purchase of {}", sale.items[0].name))
.amount(sale.total_amount.clone())
.sale_id(sale.id)
.sale_id(sale.get_id())
.build()
.expect("Failed to create invoice item");
// Create the invoice
let invoice = InvoiceBuilder::new()
.id(1)
.customer_id(customer.id)
.customer_id(customer.get_id())
.currency_code(sale.total_amount.currency_code.clone())
.status(InvoiceStatus::Sent)
.issue_date(now)

View File

@@ -1,11 +1,12 @@
use crate::db::base::*;
use bincode;
use rhai::{CustomType, EvalAltResult, TypeBuilder};
use crate::db::error::{DbError, DbResult};
use crate::db::model::Model;
use crate::db::store::{DbOperations, OurDbStore};
use std::any::TypeId;
use std::collections::HashMap;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, RwLock};
use std::sync::{Arc, RwLock};
use rhai::{CustomType, EvalAltResult, TypeBuilder};
/// Represents a single database operation in a transaction
#[derive(Debug, Clone)]
@@ -16,51 +17,10 @@ enum DbOperation {
},
Delete {
model_type: TypeId,
id: String,
id: u32,
},
}
// 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>,
@@ -77,17 +37,14 @@ impl TransactionState {
}
}
/// Main DB manager that automatically handles all root models
/// Main DB manager that automatically handles all models
#[derive(Clone, CustomType)]
pub struct DB {
db_path: PathBuf,
// Type map for generic operations
type_map: HashMap<TypeId, Arc<dyn AnyDbOperations>>,
// Locks to ensure thread safety for key areas
_write_locks: Arc<Mutex<HashMap<String, bool>>>,
type_map: HashMap<TypeId, Arc<dyn DbOperations>>,
// Transaction state
transaction: Arc<RwLock<Option<TransactionState>>>,
}
@@ -101,15 +58,15 @@ pub struct DBBuilder {
/// Trait for model registration
pub trait ModelRegistration: Send + Sync {
fn register(&self, path: &Path) -> SledDBResult<(TypeId, Box<dyn AnyDbOperations>)>;
fn register(&self, path: &Path) -> DbResult<(TypeId, Box<dyn DbOperations>)>;
}
/// Implementation of ModelRegistration for any SledModel type
pub struct SledModelRegistration<T: SledModel> {
/// Implementation of ModelRegistration for any Model type
pub struct ModelRegistrar<T: Model> {
phantom: std::marker::PhantomData<T>,
}
impl<T: SledModel> SledModelRegistration<T> {
impl<T: Model> ModelRegistrar<T> {
pub fn new() -> Self {
Self {
phantom: std::marker::PhantomData,
@@ -117,10 +74,10 @@ impl<T: SledModel> SledModelRegistration<T> {
}
}
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<T: Model> ModelRegistration for ModelRegistrar<T> {
fn register(&self, path: &Path) -> DbResult<(TypeId, Box<dyn DbOperations>)> {
let store = OurDbStore::<T>::open(path.join(T::db_prefix()))?;
Ok((TypeId::of::<T>(), Box::new(store) as Box<dyn DbOperations>))
}
}
@@ -132,49 +89,47 @@ impl DBBuilder {
model_registrations: Vec::new(),
}
}
pub fn with_path<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 {
pub fn register_model<T: Model>(mut self) -> Self {
self.model_registrations
.push(Arc::new(SledModelRegistration::<T>::new()));
.push(Arc::new(ModelRegistrar::<T>::new()));
self
}
/// Build the DB with the registered models
pub fn build(self) -> Result<DB, Box<EvalAltResult>> {
let base_path = self.base_path;
// Ensure base directory exists
if !base_path.exists() {
std::fs::create_dir_all(&base_path).map_err(|e| {
EvalAltResult::ErrorSystem("Could not create base dir".to_string(), Box::new(e))
})?;
}
// Register all models
let mut type_map: HashMap<TypeId, Arc<dyn AnyDbOperations>> = HashMap::new();
let mut type_map: HashMap<TypeId, Arc<dyn DbOperations>> = HashMap::new();
for registration in self.model_registrations {
let (type_id, db) = registration.register(&base_path).map_err(|e| {
let (type_id, store) = registration.register(&base_path).map_err(|e| {
EvalAltResult::ErrorSystem("Could not register type".to_string(), Box::new(e))
})?;
type_map.insert(type_id, db.into());
type_map.insert(type_id, store.into());
}
let _write_locks = Arc::new(Mutex::new(HashMap::new()));
let transaction = Arc::new(RwLock::new(None));
Ok(DB {
db_path: base_path,
type_map,
_write_locks,
transaction,
})
}
@@ -182,68 +137,66 @@ impl DBBuilder {
impl DB {
/// Create a new empty DB instance without any models
pub fn new<P: Into<PathBuf>>(base_path: P) -> SledDBResult<Self> {
pub fn new<P: Into<PathBuf>>(base_path: P) -> DbResult<Self> {
let base_path = base_path.into();
// Ensure base directory exists
if !base_path.exists() {
std::fs::create_dir_all(&base_path)?;
}
let _write_locks = Arc::new(Mutex::new(HashMap::new()));
let transaction = Arc::new(RwLock::new(None));
Ok(Self {
db_path: base_path,
type_map: HashMap::new(),
_write_locks,
transaction,
})
}
// Transaction-related methods
/// Begin a new transaction
pub fn begin_transaction(&self) -> SledDBResult<()> {
pub fn begin_transaction(&self) -> DbResult<()> {
let mut tx = self.transaction.write().unwrap();
if tx.is_some() {
return Err(SledDBError::GeneralError(
return Err(DbError::TransactionError(
"Transaction already in progress".into(),
));
}
*tx = Some(TransactionState::new());
Ok(())
}
/// Check if a transaction is active
pub fn has_active_transaction(&self) -> bool {
let tx = self.transaction.read().unwrap();
tx.is_some() && tx.as_ref().unwrap().active
}
/// Apply a set operation with the serialized data - bypass transaction check
fn apply_set_operation(&self, model_type: TypeId, serialized: &[u8]) -> SledDBResult<()> {
fn apply_set_operation(&self, model_type: TypeId, serialized: &[u8]) -> DbResult<()> {
// 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);
return db_ops.insert_raw(serialized);
}
Err(SledDBError::GeneralError(format!(
Err(DbError::GeneralError(format!(
"No DB registered for type ID {:?}",
model_type
)))
}
/// Commit the current transaction, applying all operations
pub fn commit_transaction(&self) -> SledDBResult<()> {
pub fn commit_transaction(&self) -> DbResult<()> {
let mut tx_guard = self.transaction.write().unwrap();
if let Some(tx_state) = tx_guard.take() {
if !tx_state.active {
return Err(SledDBError::GeneralError("Transaction not active".into()));
return Err(DbError::TransactionError("Transaction not active".into()));
}
// Execute all operations in the transaction
for op in tx_state.operations {
match op {
@@ -257,80 +210,79 @@ impl DB {
let db_ops = self
.type_map
.get(&model_type)
.ok_or_else(|| SledDBError::TypeError)?;
db_ops.delete(&id)?;
.ok_or_else(|| DbError::TypeError)?;
db_ops.delete(id)?;
}
}
}
Ok(())
} else {
Err(SledDBError::GeneralError("No active transaction".into()))
Err(DbError::TransactionError("No active transaction".into()))
}
}
/// Rollback the current transaction, discarding all operations
pub fn rollback_transaction(&self) -> SledDBResult<()> {
pub fn rollback_transaction(&self) -> DbResult<()> {
let mut tx = self.transaction.write().unwrap();
if tx.is_none() {
return Err(SledDBError::GeneralError("No active transaction".into()));
return Err(DbError::TransactionError("No active transaction".into()));
}
*tx = None;
Ok(())
}
/// Get the path to the database
pub fn path(&self) -> &PathBuf {
&self.db_path
}
// Generic methods that work with any supported model type
/// Insert a model instance into its appropriate database based on type
pub fn set<T: SledModel>(&self, model: &T) -> SledDBResult<()> {
pub fn set<T: Model>(&self, model: &T) -> DbResult<()> {
// Try to acquire a write lock on the transaction
let mut tx_guard = self.transaction.write().unwrap();
// Check if there's an active transaction
if let Some(tx_state) = tx_guard.as_mut() {
if tx_state.active {
// Serialize the model for later use
let serialized = bincode::serialize(model)?;
let serialized = model.serialize()?;
// Record a Set operation in the transaction
tx_state.operations.push(DbOperation::Set {
model_type: TypeId::of::<T>(),
serialized,
});
return Ok(());
}
}
// If we got here, either there's no transaction or it's not active
// Drop the write lock before doing a direct database operation
drop(tx_guard);
// Execute directly
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => db_ops.insert_any(model),
None => Err(SledDBError::TypeError),
Some(db_ops) => db_ops.insert(model),
None => Err(DbError::TypeError),
}
}
/// Check the transaction state for the given type and id
fn check_transaction<T: SledModel>(&self, id: &str) -> Option<Result<Option<T>, SledDBError>> {
fn check_transaction<T: Model>(&self, id: u32) -> Option<Result<Option<T>, DbError>> {
// Try to acquire a read lock on the transaction
let tx_guard = self.transaction.read().unwrap();
if let Some(tx_state) = tx_guard.as_ref() {
if !tx_state.active {
return None;
}
let type_id = TypeId::of::<T>();
let id_str = id.to_string();
// Process operations in reverse order (last operation wins)
for op in tx_state.operations.iter().rev() {
match op {
@@ -339,9 +291,9 @@ impl DB {
model_type,
id: op_id,
} => {
if *model_type == type_id && op_id == id {
if *model_type == type_id && *op_id == id {
// Return NotFound error for deleted records
return Some(Err(SledDBError::NotFound(id.to_string())));
return Some(Err(DbError::NotFound(id)));
}
}
// Then check if it has been set in the transaction
@@ -351,9 +303,9 @@ impl DB {
} => {
if *model_type == type_id {
// Try to deserialize and check the ID
match bincode::deserialize::<T>(serialized) {
match T::deserialize(serialized) {
Ok(model) => {
if model.get_id() == id_str {
if model.get_id() == id {
return Some(Ok(Some(model)));
}
}
@@ -364,13 +316,13 @@ impl DB {
}
}
}
// Not found in transaction (continue to database)
None
}
/// Get a model instance by its ID and type
pub fn get<T: SledModel>(&self, id: &str) -> SledDBResult<T> {
pub fn get<T: Model>(&self, id: u32) -> DbResult<T> {
// First check if there's a pending value in the current transaction
if let Some(tx_result) = self.check_transaction::<T>(id) {
match tx_result {
@@ -379,82 +331,91 @@ impl DB {
Ok(None) => {} // Should never happen
}
}
// If no pending value, look up from the database
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => {
let result_any = db_ops.get_any(id)?;
let result_any = db_ops.get(id)?;
// We expect the result to be of type T since we looked it up by TypeId
match result_any.downcast::<T>() {
Ok(t) => Ok(*t),
Err(_) => Err(SledDBError::TypeError),
Err(_) => Err(DbError::TypeError),
}
}
None => Err(SledDBError::TypeError),
None => Err(DbError::TypeError),
}
}
/// Delete a model instance by its ID and type
pub fn delete<T: SledModel>(&self, id: &str) -> SledDBResult<()> {
pub fn delete<T: Model>(&self, id: u32) -> DbResult<()> {
// Try to acquire a write lock on the transaction
let mut tx_guard = self.transaction.write().unwrap();
// Check if there's an active transaction
if let Some(tx_state) = tx_guard.as_mut() {
if tx_state.active {
// Record a Delete operation in the transaction
tx_state.operations.push(DbOperation::Delete {
model_type: TypeId::of::<T>(),
id: id.to_string(),
id,
});
return Ok(());
}
}
// If we got here, either there's no transaction or it's not active
// Drop the write lock before doing a direct database operation
drop(tx_guard);
// Execute directly
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => db_ops.delete(id),
None => Err(SledDBError::TypeError),
None => Err(DbError::TypeError),
}
}
/// List all model instances of a specific type
pub fn list<T: SledModel>(&self) -> SledDBResult<Vec<T>> {
pub fn list<T: Model>(&self) -> DbResult<Vec<T>> {
// Look up the correct DB operations for type T in our type map
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => {
let result_any = db_ops.list_any()?;
let result_any = db_ops.list()?;
// We expect the result to be of type Vec<T> since we looked it up by TypeId
match result_any.downcast::<Vec<T>>() {
Ok(vec_t) => Ok(*vec_t),
Err(_) => Err(SledDBError::TypeError),
Err(_) => Err(DbError::TypeError),
}
}
None => Err(SledDBError::TypeError),
None => Err(DbError::TypeError),
}
}
/// Get the history of a model by its ID
pub fn get_history<T: Model>(&self, id: u32, depth: u8) -> DbResult<Vec<T>> {
// Look up the correct DB operations for type T in our type map
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => {
let result_any = db_ops.get_history(id, depth)?;
let mut result = Vec::with_capacity(result_any.len());
for item in result_any {
match item.downcast::<T>() {
Ok(t) => result.push(*t),
Err(_) => return Err(DbError::TypeError),
}
}
Ok(result)
}
None => Err(DbError::TypeError),
}
}
// 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>(), Arc::new(db));
pub fn register<T: Model>(&mut self) -> DbResult<()> {
let store = OurDbStore::<T>::open(&self.db_path)?;
self.type_map.insert(TypeId::of::<T>(), Arc::new(store));
Ok(())
}
// 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>()
))),
}
}
}

30
herodb/src/db/error.rs Normal file
View File

@@ -0,0 +1,30 @@
use thiserror::Error;
use std::fmt::Debug;
/// Errors that can occur during database operations
#[derive(Error, Debug)]
pub enum DbError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Serialization/Deserialization error: {0}")]
SerializationError(#[from] bincode::Error),
#[error("Record not found for ID: {0}")]
NotFound(u32),
#[error("Type mismatch during deserialization")]
TypeError,
#[error("Transaction error: {0}")]
TransactionError(String),
#[error("OurDB error: {0}")]
OurDbError(#[from] ourdb::Error),
#[error("General database error: {0}")]
GeneralError(String),
}
/// Result type for DB operations
pub type DbResult<T> = Result<T, DbError>;

View File

@@ -1,4 +1,4 @@
/// Macro to implement typed access methods on the DB struct for a given model
ere/// 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) => {
@@ -12,19 +12,24 @@ macro_rules! impl_model_methods {
}
/// Get a model instance by its ID
pub fn [<get_ $singular>](&mut self, id: i64) -> SledDBResult<$model> {
self.get::<$model>(&id.to_string())
pub fn [<get_ $singular>](&mut self, id: u32) -> DbResult<$model> {
self.get::<$model>(id)
}
/// Delete a model instance by its ID
pub fn [<delete_ $singular>](&mut self, id: i64) -> SledDBResult<()> {
self.delete::<$model>(&id.to_string())
pub fn [<delete_ $singular>](&mut self, id: u32) -> DbResult<()> {
self.delete::<$model>(id)
}
/// List all model instances
pub fn [<list_ $plural>](&mut self) -> SledDBResult<Vec<$model>> {
pub fn [<list_ $plural>](&mut self) -> DbResult<Vec<$model>> {
self.list::<$model>()
}
/// Get history of a model instance
pub fn [<get_ $singular _history>](&mut self, id: u32, depth: u8) -> DbResult<Vec<$model>> {
self.get_history::<$model>(id, depth)
}
}
}
};

View File

@@ -1,7 +1,18 @@
pub mod base;
pub mod db;
pub mod macros;
pub mod model_methods;
s using// Export the error module
pub mod error;
pub use error::{DbError, DbResult};
pub use base::{SledDB, SledDBError, SledDBResult, Storable, SledModel};
pub use db::{DB, DBBuilder};
// Export the model module
pub mod model;
pub use model::{Model, Storable};
// Export the store module
pub mod store;
pub use store::{DbOperations, OurDbStore};
// Export the db module
pub mod db;
pub use db::{DB, DBBuilder, ModelRegistration, ModelRegistrar};
// Export macros for model methods
pub mod macros;

30
herodb/src/db/model.rs Normal file
View File

@@ -0,0 +1,30 @@
use crate::db::error::{DbError, DbResult};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
/// Trait for models that can be serialized and deserialized
pub trait Storable: Serialize + for<'de> Deserialize<'de> + Sized {
/// Serializes the instance using bincode
fn serialize(&self) -> DbResult<Vec<u8>> {
bincode::serialize(self).map_err(DbError::SerializationError)
}
/// Deserializes data from bytes into an instance
fn deserialize(data: &[u8]) -> DbResult<Self> {
bincode::deserialize(data).map_err(DbError::SerializationError)
}
}
/// Trait identifying a model suitable for the database
/// The 'static lifetime bound is required for type identification via Any
pub trait Model: Storable + Debug + Clone + Send + Sync + 'static {
/// Returns the unique ID for this model instance
fn get_id(&self) -> u32;
/// Returns a prefix used for this model type in the database
/// Helps to logically separate different model types
fn db_prefix() -> &'static str;
}
// Implement Storable for common types that might be used in models
impl<T: Serialize + for<'de> Deserialize<'de> + Sized> Storable for T {}

View File

@@ -1,5 +1,5 @@
use crate::db::db::DB;
use crate::db::base::{SledDBResult, SledModel};
use crate::db::model::Model;
use crate::impl_model_methods;
use crate::models::biz::{Product, Sale, Currency, ExchangeRate, Service, Customer, Contract, Invoice};

152
herodb/src/db/store.rs Normal file
View File

@@ -0,0 +1,152 @@
use crate::db::error::{DbError, DbResult};
use crate::db::model::Model;
use ourdb::{OurDB, OurDBConfig, OurDBSetArgs};
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::any::Any;
/// Trait for type-erased database operations
pub trait DbOperations: Send + Sync {
fn delete(&self, id: u32) -> DbResult<()>;
fn get(&self, id: u32) -> DbResult<Box<dyn Any>>;
fn list(&self) -> DbResult<Box<dyn Any>>;
fn insert(&self, model: &dyn Any) -> DbResult<()>;
fn insert_raw(&self, serialized: &[u8]) -> DbResult<()>;
fn get_history(&self, id: u32, depth: u8) -> DbResult<Vec<Box<dyn Any>>>;
}
/// A store implementation using OurDB as the backend
pub struct OurDbStore<T: Model> {
db: OurDB,
path: PathBuf,
_phantom: PhantomData<T>,
}
impl<T: Model> OurDbStore<T> {
/// Opens or creates an OurDB database at the specified path
pub fn open<P: AsRef<Path>>(path: P) -> DbResult<Self> {
let path_buf = path.as_ref().to_path_buf();
let db_path = path_buf.join(T::db_prefix());
// Create directory if it doesn't exist
std::fs::create_dir_all(&db_path).map_err(DbError::IoError)?;
let config = OurDBConfig {
path: db_path.clone(),
incremental_mode: true, // Always use incremental mode for auto IDs
file_size: None, // Use default (500MB)
keysize: None, // Use default (4 bytes)
reset: None, // Don't reset existing database
};
let db = OurDB::new(config).map_err(DbError::OurDbError)?;
Ok(Self {
db,
path: db_path,
_phantom: PhantomData,
})
}
/// Inserts or updates a model instance in the database
pub fn insert(&self, model: &T) -> DbResult<()> {
let id = model.get_id();
let data = model.serialize()?;
self.db.set(OurDBSetArgs {
id: Some(id),
data: &data,
}).map_err(DbError::OurDbError)?;
Ok(())
}
/// Retrieves a model instance by its ID
pub fn get(&self, id: u32) -> DbResult<T> {
let data = self.db.get(id).map_err(|e| {
match e {
ourdb::Error::NotFound(_) => DbError::NotFound(id),
_ => DbError::OurDbError(e),
}
})?;
T::deserialize(&data)
}
/// Deletes a model instance by its ID
pub fn delete(&self, id: u32) -> DbResult<()> {
self.db.delete(id).map_err(|e| {
match e {
ourdb::Error::NotFound(_) => DbError::NotFound(id),
_ => DbError::OurDbError(e),
}
})
}
/// Lists all models of this type
pub fn list(&self) -> DbResult<Vec<T>> {
// OurDB doesn't have a built-in list function, so we need to implement it
// This is a placeholder - in a real implementation, we would need to
// maintain a list of all IDs for each model type
Err(DbError::GeneralError("List operation not implemented yet".to_string()))
}
/// Gets the history of a model by its ID
pub fn get_history(&self, id: u32, depth: u8) -> DbResult<Vec<T>> {
let history_data = self.db.get_history(id, depth).map_err(|e| {
match e {
ourdb::Error::NotFound(_) => DbError::NotFound(id),
_ => DbError::OurDbError(e),
}
})?;
let mut result = Vec::with_capacity(history_data.len());
for data in history_data {
result.push(T::deserialize(&data)?);
}
Ok(result)
}
}
impl<T: Model> DbOperations for OurDbStore<T> {
fn delete(&self, id: u32) -> DbResult<()> {
self.delete(id)
}
fn get(&self, id: u32) -> DbResult<Box<dyn Any>> {
let result = self.get(id)?;
Ok(Box::new(result))
}
fn list(&self) -> DbResult<Box<dyn Any>> {
let result = self.list()?;
Ok(Box::new(result))
}
fn insert(&self, model: &dyn Any) -> DbResult<()> {
// Downcast to the specific type T
match model.downcast_ref::<T>() {
Some(t) => self.insert(t),
None => Err(DbError::TypeError),
}
}
fn insert_raw(&self, serialized: &[u8]) -> DbResult<()> {
// Deserialize the bytes into model of type T
let model = T::deserialize(serialized)?;
// Use the regular insert method
self.insert(&model)
}
fn get_history(&self, id: u32, depth: u8) -> DbResult<Vec<Box<dyn Any>>> {
let history = self.get_history(id, depth)?;
let mut result = Vec::with_capacity(history.len());
for item in history {
result.push(Box::new(item) as Box<dyn Any>);
}
Ok(result)
}
}

View File

@@ -1,35 +1,22 @@
use thiserror::Error;
/// Error types for HeroDB operations
/// Error type for HeroDB operations
#[derive(Error, Debug)]
pub enum Error {
/// Error from the underlying sled database
#[error("Database error: {0}")]
Database(#[from] sled::Error),
/// Error during serialization or deserialization
DbError(#[from] crate::db::error::DbError),
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
/// Error when a requested item is not found
#[error("Item not found: {0}")]
NotFound(String),
/// Error when an item already exists
#[error("Item already exists: {0}")]
AlreadyExists(String),
/// Error when a model validation fails
#[error("Validation error: {0}")]
Validation(String),
/// Error when a transaction fails
#[error("Transaction error: {0}")]
Transaction(String),
/// Other errors
#[error("Other error: {0}")]
Other(String),
SerializationError(#[from] bincode::Error),
#[error("OurDB error: {0}")]
OurDbError(#[from] ourdb::Error),
#[error("General error: {0}")]
GeneralError(String),
}
/// Result type for HeroDB operations

View File

View File

@@ -1,6 +1,6 @@
//! HeroDB: A database library built on top of sled with model support
//! HeroDB: A database library built on top of ourdb with model support
//!
//! This library provides a simple interface for working with a sled-based database
//! This library provides a simple interface for working with an ourdb-based database
//! and includes support for defining and working with data models.
// Core modules
@@ -13,6 +13,7 @@ pub mod cmd;
// Re-exports
pub use error::Error;
pub use db::{DB, DBBuilder, Model, Storable, DbError, DbResult};
/// Re-export sled for advanced usage
pub use sled;
/// Re-export ourdb for advanced usage
pub use ourdb;

View File

@@ -1,19 +1,21 @@
use crate::db::base::{SledModel, Storable};
use crate::db::model::{Model, Storable};
use chrono::{DateTime, Duration, Utc};
use rhai::{CustomType, EvalAltResult, TypeBuilder};
use serde::{Deserialize, Serialize}; // Import Sled traits from db module
use serde::{Deserialize, Serialize};
/// Currency represents a monetary value with amount and currency code
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
pub struct Currency {
pub id: u32,
pub amount: f64,
pub currency_code: String,
}
impl Currency {
/// Create a new currency with amount and code
pub fn new(amount: f64, currency_code: String) -> Self {
pub fn new(id: u32, amount: f64, currency_code: String) -> Self {
Self {
id,
amount,
currency_code,
}
@@ -27,6 +29,7 @@ impl Currency {
/// Builder for Currency
#[derive(Clone, CustomType)]
pub struct CurrencyBuilder {
id: Option<u32>,
amount: Option<f64>,
currency_code: Option<String>,
}
@@ -35,11 +38,18 @@ impl CurrencyBuilder {
/// Create a new CurrencyBuilder with all fields set to None
pub fn new() -> Self {
Self {
id: None,
amount: None,
currency_code: None,
}
}
/// Set the id
pub fn id(mut self, id: u32) -> Self {
self.id = Some(id);
self
}
/// Set the amount
pub fn amount(mut self, amount: f64) -> Self {
self.amount = Some(amount);
@@ -55,20 +65,17 @@ impl CurrencyBuilder {
/// Build the Currency object
pub fn build(self) -> Result<Currency, Box<EvalAltResult>> {
Ok(Currency {
id: self.id.ok_or("id is required")?,
amount: self.amount.ok_or("amount is required")?,
currency_code: self.currency_code.ok_or("currency_code is required")?,
})
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for Currency {}
// Implement SledModel trait
impl SledModel for Currency {
fn get_id(&self) -> String {
// Use the currency code as the ID
self.currency_code.clone()
// Implement Model trait
impl Model for Currency {
fn get_id(&self) -> u32 {
self.id
}
fn db_prefix() -> &'static str {

View File

@@ -2,11 +2,12 @@ use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::db::base::{SledModel, Storable};
use crate::db::model::{Model, Storable};
/// ExchangeRate represents an exchange rate between two currencies
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExchangeRate {
pub id: u32,
pub base_currency: String,
pub target_currency: String,
pub rate: f64,
@@ -15,8 +16,9 @@ pub struct ExchangeRate {
impl ExchangeRate {
/// Create a new exchange rate
pub fn new(base_currency: String, target_currency: String, rate: f64) -> Self {
pub fn new(id: u32, base_currency: String, target_currency: String, rate: f64) -> Self {
Self {
id,
base_currency,
target_currency,
rate,
@@ -27,6 +29,7 @@ impl ExchangeRate {
/// Builder for ExchangeRate
pub struct ExchangeRateBuilder {
id: Option<u32>,
base_currency: Option<String>,
target_currency: Option<String>,
rate: Option<f64>,
@@ -37,6 +40,7 @@ impl ExchangeRateBuilder {
/// Create a new ExchangeRateBuilder with all fields set to None
pub fn new() -> Self {
Self {
id: None,
base_currency: None,
target_currency: None,
rate: None,
@@ -44,6 +48,12 @@ impl ExchangeRateBuilder {
}
}
/// Set the id
pub fn id(mut self, id: u32) -> Self {
self.id = Some(id);
self
}
/// Set the base currency
pub fn base_currency<S: Into<String>>(mut self, base_currency: S) -> Self {
self.base_currency = Some(base_currency.into());
@@ -72,6 +82,7 @@ impl ExchangeRateBuilder {
pub fn build(self) -> Result<ExchangeRate, &'static str> {
let now = Utc::now();
Ok(ExchangeRate {
id: self.id.ok_or("id is required")?,
base_currency: self.base_currency.ok_or("base_currency is required")?,
target_currency: self.target_currency.ok_or("target_currency is required")?,
rate: self.rate.ok_or("rate is required")?,
@@ -80,13 +91,10 @@ impl ExchangeRateBuilder {
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for ExchangeRate {}
// Implement SledModel trait
impl SledModel for ExchangeRate {
fn get_id(&self) -> String {
format!("{}_{}", self.base_currency, self.target_currency)
// Implement Model trait
impl Model for ExchangeRate {
fn get_id(&self) -> u32 {
self.id
}
fn db_prefix() -> &'static str {
@@ -156,11 +164,11 @@ lazy_static::lazy_static! {
let service = ExchangeRateService::new();
// Set some default exchange rates
service.set_rate(ExchangeRate::new("USD".to_string(), "EUR".to_string(), 0.85));
service.set_rate(ExchangeRate::new("USD".to_string(), "GBP".to_string(), 0.75));
service.set_rate(ExchangeRate::new("USD".to_string(), "JPY".to_string(), 110.0));
service.set_rate(ExchangeRate::new("USD".to_string(), "CAD".to_string(), 1.25));
service.set_rate(ExchangeRate::new("USD".to_string(), "AUD".to_string(), 1.35));
service.set_rate(ExchangeRate::new(1, "USD".to_string(), "EUR".to_string(), 0.85));
service.set_rate(ExchangeRate::new(2, "USD".to_string(), "GBP".to_string(), 0.75));
service.set_rate(ExchangeRate::new(3, "USD".to_string(), "JPY".to_string(), 110.0));
service.set_rate(ExchangeRate::new(4, "USD".to_string(), "CAD".to_string(), 1.25));
service.set_rate(ExchangeRate::new(5, "USD".to_string(), "AUD".to_string(), 1.35));
service
};

View File

@@ -1,7 +1,7 @@
use crate::db::base::{SledModel, Storable};
use crate::db::model::{Model, Storable};
use chrono::{DateTime, Duration, Utc};
use rhai::{CustomType, EvalAltResult, TypeBuilder, export_module};
use serde::{Deserialize, Serialize}; // Import Sled traits from db module
use serde::{Deserialize, Serialize};
/// ProductType represents the type of a product
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -20,7 +20,7 @@ pub enum ProductStatus {
/// ProductComponent represents a component of a product
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProductComponent {
pub id: i64,
pub id: u32,
pub name: String,
pub description: String,
pub quantity: i64,
@@ -30,7 +30,7 @@ pub struct ProductComponent {
impl ProductComponent {
/// Create a new product component with default timestamps
pub fn new(id: i64, name: String, description: String, quantity: i64) -> Self {
pub fn new(id: u32, name: String, description: String, quantity: i64) -> Self {
let now = Utc::now();
Self {
id,
@@ -46,7 +46,7 @@ impl ProductComponent {
/// Builder for ProductComponent
#[derive(Clone, CustomType)]
pub struct ProductComponentBuilder {
id: Option<i64>,
id: Option<u32>,
name: Option<String>,
description: Option<String>,
quantity: Option<i64>,
@@ -68,7 +68,7 @@ impl ProductComponentBuilder {
}
/// Set the id
pub fn id(mut self, id: i64) -> Self {
pub fn id(mut self, id: u32) -> Self {
self.id = Some(id);
self
}
@@ -120,7 +120,7 @@ impl ProductComponentBuilder {
/// Product represents a product or service offered in the system
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Product {
pub id: i64,
pub id: u32,
pub name: String,
pub description: String,
pub price: Currency,
@@ -135,12 +135,10 @@ pub struct Product {
pub components: Vec<ProductComponent>,
}
// Removed old Model trait implementation
impl Product {
/// Create a new product with default timestamps
pub fn new(
id: i64,
id: u32,
name: String,
description: String,
price: Currency,
@@ -201,7 +199,7 @@ impl Product {
/// Builder for Product
#[derive(Clone, CustomType)]
pub struct ProductBuilder {
id: Option<i64>,
id: Option<u32>,
name: Option<String>,
description: Option<String>,
price: Option<Currency>,
@@ -239,7 +237,7 @@ impl ProductBuilder {
}
/// Set the id
pub fn id(mut self, id: i64) -> Self {
pub fn id(mut self, id: u32) -> Self {
self.id = Some(id);
self
}
@@ -344,13 +342,10 @@ impl ProductBuilder {
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for Product {}
// Implement SledModel trait
impl SledModel for Product {
fn get_id(&self) -> String {
self.id.to_string()
// Implement Model trait
impl Model for Product {
fn get_id(&self) -> u32 {
self.id
}
fn db_prefix() -> &'static str {