This commit is contained in:
despiegk 2025-04-19 09:02:35 +02:00
parent 9c7baa3b4e
commit 56f73e5802
32 changed files with 60 additions and 2606 deletions

View File

@ -28,9 +28,13 @@ name = "rhai_demo"
path = "examples/rhai_demo.rs"
[[bin]]
name = "dbexample2"
path = "src/cmd/dbexample2/main.rs"
name = "dbexample_prod"
path = "src/cmd/dbexample_prod/main.rs"
[[bin]]
name = "dbexample_mcc"
path = "src/cmd/dbexample_mcc/main.rs"
[[bin]]
name = "dbexample_gov"
path = "src/cmd/dbexample_gov/main.rs"

View File

@ -7,7 +7,12 @@ A database library built on top of sled with model support.
## example
```bash
cargo run --bin dbexample2
#test for mcc module
cargo run --bin dbexample_mcc
#test for governance module
cargo run --bin dbexample_gov
#test for products
cargo run --bin dbexample_prod
```
## Features

View File

@ -1,38 +0,0 @@
//! Demonstrates how to use the Rhai wrappers for our models
use herodb::zaz::rhai::{run_script_file, run_example_script};
use std::path::PathBuf;
use std::fs;
use std::time::SystemTime;
fn main() -> Result<(), String> {
println!("=== RHAI MODEL WRAPPERS DEMONSTRATION ===");
// Run our test script that creates model objects
let test_script_path = "src/zaz/rhai/test.rhai";
println!("\n1. Running model creation test script: {}", test_script_path);
run_script_file(test_script_path)?;
// Create temporary directory for DB example
let temp_dir = create_temp_dir()
.map_err(|e| format!("Failed to create temp dir: {}", e))?;
// Run our example script that uses the DB
println!("\n2. Running example with database at: {:?}", temp_dir);
run_example_script(temp_dir.to_str().unwrap())?;
println!("\n=== DEMONSTRATION COMPLETED SUCCESSFULLY ===");
Ok(())
}
/// Creates a simple temporary directory
fn create_temp_dir() -> std::io::Result<PathBuf> {
let temp_dir = std::env::temp_dir();
let random_name = format!("rhai-demo-{}", SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis());
let path = temp_dir.join(random_name);
fs::create_dir_all(&path)?;
Ok(path)
}

View File

@ -1,91 +0,0 @@
# HeroDB Architecture
This document explains the architecture of HeroDB, focusing on the separation between model definitions and database logic.
## Core Principles
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
## Components
### Core Module
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 herodb::zaz::create_zaz_db;
// Create a DB with all zaz models registered
let db = create_zaz_db("/path/to/db")?;
// Use the DB with specific model types
let user = User::new(...);
db.set(&user)?;
let retrieved: User = db.get(&id)?;
```
### Option 2: Builder Pattern
For more control, use the builder pattern to register only the models you need:
```rust
use herodb::core::{DBBuilder, DB};
use herodb::zaz::models::{User, Company};
// Create a DB with only User and Company models
let db = DBBuilder::new("/path/to/db")
.register_model::<User>()
.register_model::<Company>()
.build()?;
```
### Option 3: Dynamic Registration
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

View File

@ -1,168 +0,0 @@
//! Integration tests for zaz database module
#[cfg(test)]
mod tests {
use sled;
use bincode;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::Path;
use tempfile::tempdir;
use std::collections::HashMap;
/// Test model for database operations
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct User {
id: u32,
name: String,
email: String,
balance: f64,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
}
impl User {
fn new(id: u32, name: String, email: String, balance: f64) -> Self {
let now = Utc::now();
Self {
id,
name,
email,
balance,
created_at: now,
updated_at: now,
}
}
}
/// Test basic CRUD operations
#[test]
fn test_basic_crud() {
// Create a temporary directory for testing
let temp_dir = tempdir().expect("Failed to create temp directory");
println!("Created temporary directory at: {:?}", temp_dir.path());
// Open a sled database in the temporary directory
let db = sled::open(temp_dir.path().join("users")).expect("Failed to open database");
println!("Opened database at: {:?}", temp_dir.path().join("users"));
// CREATE a user
let user = User::new(1, "Test User".to_string(), "test@example.com".to_string(), 100.0);
let user_key = user.id.to_string();
let user_value = bincode::serialize(&user).expect("Failed to serialize user");
db.insert(user_key.as_bytes(), user_value).expect("Failed to insert user");
db.flush().expect("Failed to flush database");
println!("Created user: {} ({})", user.name, user.email);
// READ the user
let result = db.get(user_key.as_bytes()).expect("Failed to query database");
assert!(result.is_some(), "User should exist");
if let Some(data) = result {
let retrieved_user: User = bincode::deserialize(&data).expect("Failed to deserialize user");
println!("Retrieved user: {} ({})", retrieved_user.name, retrieved_user.email);
assert_eq!(user, retrieved_user, "Retrieved user should match original");
}
// UPDATE the user
let updated_user = User::new(1, "Updated User".to_string(), "updated@example.com".to_string(), 150.0);
let updated_value = bincode::serialize(&updated_user).expect("Failed to serialize updated user");
db.insert(user_key.as_bytes(), updated_value).expect("Failed to update user");
db.flush().expect("Failed to flush database");
println!("Updated user: {} ({})", updated_user.name, updated_user.email);
let result = db.get(user_key.as_bytes()).expect("Failed to query database");
if let Some(data) = result {
let retrieved_user: User = bincode::deserialize(&data).expect("Failed to deserialize user");
assert_eq!(updated_user, retrieved_user, "Retrieved user should match updated version");
} else {
panic!("User should exist after update");
}
// DELETE the user
db.remove(user_key.as_bytes()).expect("Failed to delete user");
db.flush().expect("Failed to flush database");
println!("Deleted user");
let result = db.get(user_key.as_bytes()).expect("Failed to query database");
assert!(result.is_none(), "User should be deleted");
// Clean up
drop(db);
temp_dir.close().expect("Failed to cleanup temporary directory");
}
/// Test transaction-like behavior with multiple operations
#[test]
fn test_transaction_behavior() {
// Create a temporary directory for testing
let temp_dir = tempdir().expect("Failed to create temp directory");
println!("Created temporary directory at: {:?}", temp_dir.path());
// Open a sled database in the temporary directory
let db = sled::open(temp_dir.path().join("tx_test")).expect("Failed to open database");
println!("Opened transaction test database at: {:?}", temp_dir.path().join("tx_test"));
// Create initial users
let user1 = User::new(1, "User One".to_string(), "one@example.com".to_string(), 100.0);
let user2 = User::new(2, "User Two".to_string(), "two@example.com".to_string(), 50.0);
// Insert initial users
db.insert(user1.id.to_string().as_bytes(), bincode::serialize(&user1).unwrap()).unwrap();
db.insert(user2.id.to_string().as_bytes(), bincode::serialize(&user2).unwrap()).unwrap();
db.flush().unwrap();
println!("Inserted initial users");
// Simulate a transaction - transfer 25.0 from user1 to user2
println!("Starting transaction simulation: transfer 25.0 from user1 to user2");
// Create transaction workspace
let mut tx_workspace = HashMap::new();
// Retrieve current state
if let Some(data) = db.get(user1.id.to_string().as_bytes()).unwrap() {
let user: User = bincode::deserialize(&data).unwrap();
tx_workspace.insert(user1.id.to_string(), user);
}
if let Some(data) = db.get(user2.id.to_string().as_bytes()).unwrap() {
let user: User = bincode::deserialize(&data).unwrap();
tx_workspace.insert(user2.id.to_string(), user);
}
// Modify both users in the transaction
let mut updated_user1 = tx_workspace.get(&user1.id.to_string()).unwrap().clone();
let mut updated_user2 = tx_workspace.get(&user2.id.to_string()).unwrap().clone();
updated_user1.balance -= 25.0;
updated_user2.balance += 25.0;
// Update the workspace
tx_workspace.insert(user1.id.to_string(), updated_user1);
tx_workspace.insert(user2.id.to_string(), updated_user2);
// Commit the transaction
println!("Committing transaction");
for (key, user) in tx_workspace {
let user_bytes = bincode::serialize(&user).unwrap();
db.insert(key.as_bytes(), user_bytes).unwrap();
}
db.flush().unwrap();
// Verify the results
if let Some(data) = db.get(user1.id.to_string().as_bytes()).unwrap() {
let final_user1: User = bincode::deserialize(&data).unwrap();
assert_eq!(final_user1.balance, 75.0, "User1 balance should be 75.0");
println!("Verified user1 balance is now {}", final_user1.balance);
}
if let Some(data) = db.get(user2.id.to_string().as_bytes()).unwrap() {
let final_user2: User = bincode::deserialize(&data).unwrap();
assert_eq!(final_user2.balance, 75.0, "User2 balance should be 75.0");
println!("Verified user2 balance is now {}", final_user2.balance);
}
// Clean up
drop(db);
temp_dir.close().expect("Failed to cleanup temporary directory");
}
}

View File

@ -1,64 +0,0 @@
// Examples for using the Zaz database
use crate::zaz::models::*;
use crate::zaz::factory::create_zaz_db;
use std::path::PathBuf;
use chrono::Utc;
/// Run a simple example of the DB operations
pub fn run_db_examples() -> Result<(), Box<dyn std::error::Error>> {
println!("Running Zaz DB examples...");
// Create a temp DB path
let db_path = PathBuf::from("/tmp/zaz-examples");
std::fs::create_dir_all(&db_path)?;
// Create DB instance
let db = create_zaz_db(&db_path)?;
// Example 1: User operations
println!("\n--- User Examples ---");
let user = User::new(
1,
"John Doe".to_string(),
"john@example.com".to_string(),
"secure123".to_string(),
"Example Corp".to_string(),
"User".to_string(),
);
db.set(&user)?;
println!("Inserted user: {}", user.name);
let retrieved_user = db.get::<User>(&user.id.to_string())?;
println!("Retrieved user: {} ({})", retrieved_user.name, retrieved_user.email);
// Example 2: Company operations
println!("\n--- Company Examples ---");
let company = Company::new(
1,
"Example Corp".to_string(),
"EX123456".to_string(),
Utc::now(),
"12-31".to_string(),
"info@example.com".to_string(),
"123-456-7890".to_string(),
"www.example.com".to_string(),
"123 Example St, Example City".to_string(),
BusinessType::Global,
"Technology".to_string(),
"An example company".to_string(),
CompanyStatus::Active,
);
db.set(&company)?;
println!("Inserted company: {}", company.name);
let companies = db.list::<Company>()?;
println!("Found {} companies", companies.len());
// Clean up
std::fs::remove_dir_all(db_path)?;
Ok(())
}

View File

@ -1,33 +0,0 @@
//! 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(())
}

View File

@ -1,6 +1,6 @@
use chrono::{Utc, Duration};
use herodb::db::DBBuilder;
use herodb::models::governance::{
use herodb::db::{DBBuilder, SledDB, SledModel};
use herodb::models::gov::{
Company, CompanyStatus, BusinessType,
Shareholder, ShareholderType,
Meeting, Attendee, MeetingStatus, AttendeeRole, AttendeeStatus,
@ -12,11 +12,11 @@ use std::path::PathBuf;
use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("DB Example: Governance Module");
println!("DB Example: Gov Module");
println!("============================");
// Create a temporary directory for the database
let db_path = PathBuf::from("/tmp/dbexample_governance");
let db_path = PathBuf::from("/tmp/dbexample_gov");
if db_path.exists() {
fs::remove_dir_all(&db_path)?;
}
@ -54,7 +54,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
);
// Insert the company
db.insert(&company)?;
db.set(&company)?;
println!("Company created: {} (ID: {})", company.name, company.id);
println!("Status: {:?}, Business Type: {:?}", company.status, company.business_type);
@ -90,9 +90,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
);
// Insert the users
db.insert(&user1)?;
db.insert(&user2)?;
db.insert(&user3)?;
db.set(&user1)?;
db.set(&user2)?;
db.set(&user3)?;
println!("User created: {} ({})", user1.name, user1.role);
println!("User created: {} ({})", user2.name, user2.role);
@ -133,9 +133,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
);
// Insert the shareholders
db.insert(&shareholder1)?;
db.insert(&shareholder2)?;
db.insert(&shareholder3)?;
db.set(&shareholder1)?;
db.set(&shareholder2)?;
db.set(&shareholder3)?;
println!("Shareholder created: {} ({} shares, {}%)",
shareholder1.name, shareholder1.shares, shareholder1.percentage);
@ -146,7 +146,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Update shareholder shares
shareholder1.update_shares(1100.0, 44.0);
db.insert(&shareholder1)?;
db.set(&shareholder1)?;
println!("Updated shareholder: {} ({} shares, {}%)",
shareholder1.name, shareholder1.shares, shareholder1.percentage);
@ -194,7 +194,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
meeting.add_attendee(attendee3);
// Insert the meeting
db.insert(&meeting)?;
db.set(&meeting)?;
println!("Meeting created: {} ({})", meeting.title, meeting.date.format("%Y-%m-%d %H:%M"));
println!("Status: {:?}, Attendees: {}", meeting.status, meeting.attendees.len());
@ -205,7 +205,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
if let Some(attendee) = meeting.find_attendee_by_user_id_mut(user3.id) {
attendee.update_status(AttendeeStatus::Confirmed);
}
db.insert(&meeting)?;
db.set(&meeting)?;
// Get confirmed attendees
let confirmed = meeting.confirmed_attendees();
@ -238,19 +238,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
resolution.link_to_meeting(meeting.id);
// Insert the resolution
db.insert(&resolution)?;
db.set(&resolution)?;
println!("Resolution created: {} (Status: {:?})", resolution.title, resolution.status);
// Propose the resolution
resolution.propose();
db.insert(&resolution)?;
db.set(&resolution)?;
println!("Resolution proposed on {}", resolution.proposed_at.format("%Y-%m-%d"));
// Add approvals
resolution.add_approval(user1.id, user1.name.clone(), true, "Approved as proposed".to_string());
resolution.add_approval(user2.id, user2.name.clone(), true, "Financials look good".to_string());
resolution.add_approval(user3.id, user3.name.clone(), true, "No concerns".to_string());
db.insert(&resolution)?;
db.set(&resolution)?;
// Check approval status
println!("Approvals: {}, Rejections: {}",
@ -259,7 +259,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Approve the resolution
resolution.approve();
db.insert(&resolution)?;
db.set(&resolution)?;
println!("Resolution approved on {}",
resolution.approved_at.unwrap().format("%Y-%m-%d"));
@ -283,7 +283,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
vote.add_option("Abstain".to_string(), 0);
// Insert the vote
db.insert(&vote)?;
db.set(&vote)?;
println!("Vote created: {} (Status: {:?})", vote.title, vote.status);
println!("Voting period: {} to {}",
vote.start_date.format("%Y-%m-%d"),
@ -293,7 +293,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
vote.add_ballot(user1.id, 1, 1000); // User 1 votes "Approve" with 1000 shares
vote.add_ballot(user2.id, 1, 750); // User 2 votes "Approve" with 750 shares
vote.add_ballot(user3.id, 3, 750); // User 3 votes "Abstain" with 750 shares
db.insert(&vote)?;
db.set(&vote)?;
// Check voting results
println!("Voting results:");
@ -314,7 +314,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Link the resolution to the vote
vote_resolution.link_to_vote(vote.id);
vote_resolution.propose();
db.insert(&vote_resolution)?;
db.set(&vote_resolution)?;
println!("Created resolution linked to vote: {}", vote_resolution.title);
println!("\n7. Retrieving Related Objects");

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +0,0 @@
[package]
name = "dbexample_governance"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "dbexample_governance"
path = "main.rs"
[dependencies]
herodb = { path = "../../.." }
chrono = "0.4"

View File

@ -115,7 +115,7 @@ pub mod governance;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable, SledDB, SledDBError};
use crate::models::governance::{Meeting, Vote};
use crate::models::gov::{Meeting, Vote};
/// ResolutionStatus represents the status of a resolution
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,7 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable, SledDB, SledDBError};
use crate::models::governance::User;
use crate::models::gov::User;
/// CommitteeRole represents the role of a member in a committee
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -123,8 +123,8 @@ impl Company {
}
/// Get all resolutions for this company
pub fn get_resolutions(&self, db: &SledDB<crate::models::governance::Resolution>) -> Result<Vec<crate::models::governance::Resolution>, SledDBError> {
let all_resolutions = db.list()?;
pub fn get_resolutions(&self, db: &crate::db::DB) -> Result<Vec<super::Resolution>, SledDBError> {
let all_resolutions = db.list::<super::Resolution>()?;
let company_resolutions = all_resolutions
.into_iter()
.filter(|resolution| resolution.company_id == self.id)

View File

@ -1,7 +1,7 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable, SledDB, SledDBError};
use crate::models::governance::Company;
use crate::models::gov::Company;
/// ComplianceRequirement represents a regulatory requirement
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]

View File

@ -164,8 +164,8 @@ impl Meeting {
}
/// Get all resolutions discussed in this meeting
pub fn get_resolutions(&self, db: &SledDB<crate::models::governance::Resolution>) -> Result<Vec<crate::models::governance::Resolution>, SledDBError> {
let all_resolutions = db.list()?;
pub fn get_resolutions(&self, db: &crate::db::DB) -> Result<Vec<super::Resolution>, SledDBError> {
let all_resolutions = db.list::<super::Resolution>()?;
let meeting_resolutions = all_resolutions
.into_iter()
.filter(|resolution| resolution.meeting_id == Some(self.id))

View File

@ -1,7 +1,7 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable, SledDB, SledDBError};
use crate::models::governance::{Meeting, Vote};
use crate::models::gov::{Meeting, Vote};
/// ResolutionStatus represents the status of a resolution
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@ -158,10 +158,10 @@ impl Resolution {
}
/// Get the meeting associated with this resolution
pub fn get_meeting(&self, db: &SledDB<Meeting>) -> Result<Option<Meeting>, SledDBError> {
pub fn get_meeting(&self, db: &crate::db::DB) -> Result<Option<Meeting>, SledDBError> {
match self.meeting_id {
Some(meeting_id) => {
let meeting = db.get(&meeting_id.to_string())?;
let meeting = db.get::<Meeting>(&meeting_id.to_string())?;
Ok(Some(meeting))
}
None => Ok(None),
@ -169,10 +169,10 @@ impl Resolution {
}
/// Get the vote associated with this resolution
pub fn get_vote(&self, db: &SledDB<Vote>) -> Result<Option<Vote>, SledDBError> {
pub fn get_vote(&self, db: &crate::db::DB) -> Result<Option<Vote>, SledDBError> {
match self.vote_id {
Some(vote_id) => {
let vote = db.get(&vote_id.to_string())?;
let vote = db.get::<Vote>(&vote_id.to_string())?;
Ok(Some(vote))
}
None => Ok(None),

View File

@ -128,8 +128,8 @@ impl Vote {
}
/// Get the resolution associated with this vote
pub fn get_resolution(&self, db: &SledDB<crate::models::governance::Resolution>) -> Result<Option<crate::models::governance::Resolution>, SledDBError> {
let all_resolutions = db.list()?;
pub fn get_resolution(&self, db: &crate::db::DB) -> Result<Option<super::Resolution>, SledDBError> {
let all_resolutions = db.list::<super::Resolution>()?;
let vote_resolution = all_resolutions
.into_iter()
.find(|resolution| resolution.vote_id == Some(self.id));

View File

@ -1,4 +1,4 @@
pub mod biz;
pub mod mcc;
pub mod circle;
pub mod governance;
pub mod gov;

View File

@ -0,0 +1,4 @@
segment_size: 524288
use_compression: false
version: 0.34
v

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,4 @@
segment_size: 524288
use_compression: false
version: 0.34
v

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,4 @@
segment_size: 524288
use_compression: false
version: 0.34
v

Binary file not shown.

Binary file not shown.