From 7d9a6906c63f732ea6eb7c196df078423bba239d Mon Sep 17 00:00:00 2001 From: despiegk Date: Tue, 5 Aug 2025 11:00:20 +0200 Subject: [PATCH 1/8] ... --- specs/models_heroledger/core/base.v | 11 +++ specs/models_heroledger/main/dnsrecord.v | 69 +++++++++++++++++ specs/models_heroledger/main/group.v | 51 ++++++++++++ specs/models_heroledger/main/membership.v | 34 ++++++++ specs/models_heroledger/main/money.v | 94 +++++++++++++++++++++++ specs/models_heroledger/main/secretbox.v | 31 ++++++++ specs/models_heroledger/main/signature.v | 32 ++++++++ specs/models_heroledger/main/user.v | 58 ++++++++++++++ specs/models_heroledger/main/user_kvs.v | 22 ++++++ 9 files changed, 402 insertions(+) create mode 100644 specs/models_heroledger/core/base.v create mode 100644 specs/models_heroledger/main/dnsrecord.v create mode 100644 specs/models_heroledger/main/group.v create mode 100644 specs/models_heroledger/main/membership.v create mode 100644 specs/models_heroledger/main/money.v create mode 100644 specs/models_heroledger/main/secretbox.v create mode 100644 specs/models_heroledger/main/signature.v create mode 100644 specs/models_heroledger/main/user.v create mode 100644 specs/models_heroledger/main/user_kvs.v diff --git a/specs/models_heroledger/core/base.v b/specs/models_heroledger/core/base.v new file mode 100644 index 0000000..e25e354 --- /dev/null +++ b/specs/models_heroledger/core/base.v @@ -0,0 +1,11 @@ +module core + +// BaseData provides common fields for all models +pub struct Base { +pub mut: + id u32 + created u64 // Unix timestamp of creation + updated u64 // Unix timestamp of last update + deleted bool + version u32 +} diff --git a/specs/models_heroledger/main/dnsrecord.v b/specs/models_heroledger/main/dnsrecord.v new file mode 100644 index 0000000..7e08b8e --- /dev/null +++ b/specs/models_heroledger/main/dnsrecord.v @@ -0,0 +1,69 @@ +module circle + +import freeflowuniverse.herolib.hero.models.core + + +pub struct DNSZone { + core.Base +pub mut: + domain string @[index] // The actual domain name + dnsrecords []DNSRecord + administrators []u32 + status DNSZoneStatus // active, suspended, etc. + metadata map[string]string + soarecord []SOARecord //one soa record per zone, last one is valid, rest is for history +} + + +// Name represents a domain name configuration for a circle +pub struct DNSRecord { +pub mut: + subdomain string // Optional subdomain e.g. main, means mail.example.com, example.com would be the domain, here only 'mail' + record_type NameType // Type of DNS record + value string // DNS record value/target + priority u32 // Priority for MX records + ttl u32 // Time to live in seconds + is_active bool // Whether this record is currently active + cat NameCat // Category of the DNS record, e.g., ipv4, ipv6, mycelium + is_wildcard bool // Whether this is a wildcard record + +} + +// NameType defines the supported DNS record types +pub enum NameType { + a + aaaa + cname + mx + txt + srv + ptr + ns +} + +pub enum NameCat { + ipv4 + ipv6 + mycelium +} + +pub enum DNSZoneStatus { + active + suspended + archived +} + +// SOA (Start of Authority) record for a DNS zone +pub struct SOARecord { +pub mut: + zone_id u32 // Reference to DNSZone + primary_ns string // Primary nameserver (e.g., ns1.example.com) + admin_email string // Responsible party's email (e.g., admin.example.com) + serial u64 // Serial number of the zone file, needs to be incremented on changes + refresh u32 = 3600 // Time before zone should be refreshed (in seconds) + retry u32 = 600 // Time before retry if refresh fails (in seconds) + expire u32 = 604800 // Time before zone is considered no longer authoritative + minimum_ttl u32 = 3600 // Default TTL for records without explicit TTL + is_active bool = true // Whether this SOA record is active +} + diff --git a/specs/models_heroledger/main/group.v b/specs/models_heroledger/main/group.v new file mode 100644 index 0000000..ad21c98 --- /dev/null +++ b/specs/models_heroledger/main/group.v @@ -0,0 +1,51 @@ +module group + +import freeflowuniverse.herolib.hero.models.core + +// Group represents a collaborative or access-controlled unit within the system +@[heap] +pub struct Group { + core.Base +pub mut: + name string // Human-readable name of the group @[index] + description string // Detailed explanation of the group's purpose + dnsrecords []u32 // DNSRecord IDs associated with this group (if any) + administrators []u32 // User IDs with admin rights over the group + config GroupConfig // Configuration settings for group behavior + status GroupStatus // Current operational state + visibility Visibility // Who can see this group + created u64 // Unix timestamp when the group was created + updated u64 // Unix timestamp when the group was last updated +} + +@[heap] +pub struct UserGroupMembership { + core.Base +pub mut: + user_id u32 @[index] // Reference to the user entity + group_ids []u32 @[index] // Reference to the group entity +} + +// GroupConfig holds rules that govern group membership and behavior +pub struct GroupConfig { +pub mut: + max_members u32 // Maximum number of users allowed + allow_guests bool // Whether guest users (unregistered or read-only) can access + auto_approve bool // Whether member join requests are auto-approved + require_invite bool // Whether joining the group requires an explicit invitation +} + +// GroupStatus defines the lifecycle of a group +pub enum GroupStatus { + active + inactive + suspended + archived +} + +// Visibility controls who can discover or view the group +pub enum Visibility { + public // Anyone can see and request to join + private // Only invited users can see the group + unlisted // Not visible in search; only accessible by direct link or DNS +} diff --git a/specs/models_heroledger/main/membership.v b/specs/models_heroledger/main/membership.v new file mode 100644 index 0000000..9e94c9c --- /dev/null +++ b/specs/models_heroledger/main/membership.v @@ -0,0 +1,34 @@ +module circle + +import freeflowuniverse.herolib.hero.models.core + +> STILL WRONG + +// Member represents a member within a circle +pub struct Member { + core.Base +pub mut: + user_id u32 // Reference to the user entity @[index] + role MemberRole // Member's role within the circle + status MemberStatus // Current membership status + joined_at u64 // Unix timestamp when member joined + invited_by u32 // User ID of who invited this member + permissions []string // List of custom permissions +} + +// MemberRole defines the possible roles a member can have +pub enum MemberRole { + owner + admin + moderator + member + guest +} + +// MemberStatus represents the current status of membership +pub enum MemberStatus { + active + pending + suspended + removed +} diff --git a/specs/models_heroledger/main/money.v b/specs/models_heroledger/main/money.v new file mode 100644 index 0000000..e86796d --- /dev/null +++ b/specs/models_heroledger/main/money.v @@ -0,0 +1,94 @@ +module circle + +import freeflowuniverse.herolib.hero.models.core + +// Wallet represents a wallet associated with a circle for financial operations +pub struct Account { + core.Base +pub mut: + owner_id u32 // Reference to the user who owns this account, owner not necessarily has admin rights + address string // Blockchain address for this wallet @[index] + balance f64 // Current balance in the wallet + currency string // Currency type (e.g., "USD", "BTC", "ETH") + assetid u32 + last_activity u64 // Unix timestamp of last transaction + administrators []u32 // List of user IDs who are super admins, they have all rights, without any treashold + accountpolicy u32 // Policy for signing transactions, 0 means none +} + + +pub struct Asset { + core.Base +pub mut: + address string // Blockchain address for this asset @[index] + assetid u32 // Unique identifier for the asset (e.g., "USD", "BTC", "ETH") + asset_type string // "fiat", "crypto", "stablecoin", etc. + issuer u32 // Issuer account + supply f64 // Total circulating supply + decimals u8 // Decimal precision (e.g., 2 for cents) + is_frozen bool // Whether the asset is frozen globally + metadata map[string]string // Additional metadata associated with the asset + administrators []u32 + min_signatures u32 // Minimum number of signatures required for change of properties, linked to administrators + + //BLOCKCHAIN +} + + +pub struct AccountPolicy { + core.Base +pub mut: + transferpolicy AccountPolicyItem //can transfer money + adminpolicy AccountPolicyItem //can change other policies + clawbackpolicy AccountPolicyItem // Policy for clawback money + freezepolicy AccountPolicyItem // Policy for freezing, unfreezing funds +} + +pub struct AccountPolicyItem { +pub mut: + signers []u32 // List of user IDs who are authorized to sign transactions + min_signatures u32 // Minimum number of signatures required for a transaction + enabled bool // Whether clawback is enabled for this account + threshold f64 // Threshold amount for triggering clawback + recipient u32 // Account ID of the recipient for clawback funds +} + +pub enum AccountStatus { + active + inactive + suspended + archived +} + + + +pub struct Transaction { + core.Base +pub mut: + txid u32 // Unique identifier for the transaction + source u32 + destination u32 + assetid u32 + amount f64 + timestamp u64 + status string // pending, confirmed, failed + memo string + tx_type TransactionType // transfer, clawback, freeze, issue, etc. + signatures []Signature +} + + +pub enum AccountStatus { + active + inactive + suspended + archived +} +pub enum TransactionType { + transfer + clawback + freeze + unfreeze + issue + burn +} \ No newline at end of file diff --git a/specs/models_heroledger/main/secretbox.v b/specs/models_heroledger/main/secretbox.v new file mode 100644 index 0000000..9f23887 --- /dev/null +++ b/specs/models_heroledger/main/secretbox.v @@ -0,0 +1,31 @@ +module main + +pub struct SecretBox { +pub mut: + notary_id u32 // person who is allowed to decrypt this info + value string //the actual incrypted value + version u16 //version of the schema used to encrypt this value + timestamp u64 + cat SecretBoxCategory //category of the secret box, e.g. profile +} + +pub enum SecretBoxCategory { + profile +} + +pub struct Notary { + core.Base +pub mut: + userid u32 // Reference to the user entity @[index] + status NotaryStatus // Current user status + myceliumaddress string // Mycelium address of the notary + pubkey string // Public key for cryptographic operations @[index] +} + +pub enum NotaryStatus { + active + inactive + suspended + archived + error +} diff --git a/specs/models_heroledger/main/signature.v b/specs/models_heroledger/main/signature.v new file mode 100644 index 0000000..7eb627b --- /dev/null +++ b/specs/models_heroledger/main/signature.v @@ -0,0 +1,32 @@ +module circle + +import freeflowuniverse.herolib.hero.models.core + +// Wallet represents a wallet associated with a circle for financial operations +pub struct Signature { + core.Base +pub mut: + signature_id u32 // Reference to the user who created the signature @[index] + user_id u32 // Reference to the user who created the signature @[index] + value string // The actual signature value + objectid u32 // Reference to the user who created the signature @[index] + objecttype ObjectType // Type of object being signed (e.g., + status SignatureStatus + timestamp u64 +} + +pub enum SignatureStatus { + active + inactive + pending + revoked +} + +pub enum ObjectType { + account + dnsrecord + membership + user + transaction + kyc +} \ No newline at end of file diff --git a/specs/models_heroledger/main/user.v b/specs/models_heroledger/main/user.v new file mode 100644 index 0000000..1a89fe7 --- /dev/null +++ b/specs/models_heroledger/main/user.v @@ -0,0 +1,58 @@ +module circle + +import freeflowuniverse.herolib.hero.models.core + +// Wallet represents a wallet associated with a circle for financial operations +pub struct User { + core.Base +pub mut: + username string // Unique username for the user @[index] + pubkey string // Public key for cryptographic operations @[index] + email []string @[index] // User's email addresses, needs to be considered unique and can be multiple + status UserStatus // Current user status + userprofile []SecretBox // User profile information stored in a secret box + kyc []SecretBox // KYC information stored in a secret box + } + + +pub enum UserStatus { + active + inactive + suspended + archived +} + +pub struct UserProfile { +pub mut: + user_id u32 // Reference to the user entity @[index] + full_name string // Full name of the user + bio string // Short biography or description + profile_pic string // URL to the user's profile picture + links map[string]string // Social media or other relevant links + metadata map[string]string // Additional metadata associated with the user profile +} + +pub struct KYCInfo { +pub mut: + user_id u32 // Reference to the user entity @[index] + full_name string // Full name of the user + date_of_birth u64 // Unix timestamp of user's date of birth + address string // User's residential address + phone_number string // User's phone number + id_number string // Government-issued ID number (e.g., passport, driver's license) + id_type string // Type of ID (e.g., "passport", "driver's license") + id_expiry u64 // Unix timestamp of ID expiry date + kyc_status KYCStatus // Current KYC status + kyc_verified bool // Whether the KYC information has been verified + kyc_verified_by u32 // User ID of the person who verified the KYC + kyc_verified_at u64 // Unix timestamp when KYC was verified + kyc_rejected_reason string // Reason for KYC rejection, if applicable + kyc_signature u32 // Signature of the user for KYC verification + metadata map[string]string // Additional metadata associated with the user profile +} + +pub enum KYCStatus { + pending + approved + rejected +} \ No newline at end of file diff --git a/specs/models_heroledger/main/user_kvs.v b/specs/models_heroledger/main/user_kvs.v new file mode 100644 index 0000000..7c072ed --- /dev/null +++ b/specs/models_heroledger/main/user_kvs.v @@ -0,0 +1,22 @@ +module circle + +import freeflowuniverse.herolib.hero.models.core + +//a per user db +pub struct UserKVS { + core.Base +pub mut: + userid u32 // Reference to the user entity @[index] + name string // Name of the key-value store + } + +pub struct UserKVSItem { + core.Base +pub mut: + userkvs_id u32 // Reference to the user entity @[index] + key string + value string // Value associated with the key + secretbox []SecretBox // Optional secret boxes for sensitive data + timestamp u64 // Timestamp when the item was created or last updated + } + From 1a62fcacddbdbbb3175da5fa8f575fe585560a1c Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Tue, 5 Aug 2025 12:53:24 +0200 Subject: [PATCH 2/8] add heroledger models --- heromodels/docs/payment_usage.md | 318 ----------- .../prompts/v_specs_to_rust_heromodels.md | 301 ++++++++++ heromodels/src/models/heroledger/dnsrecord.rs | 301 ++++++++++ heromodels/src/models/heroledger/group.rs | 236 ++++++++ .../src/models/heroledger/membership.rs | 115 ++++ heromodels/src/models/heroledger/mod.rs | 19 + heromodels/src/models/heroledger/money.rs | 515 ++++++++++++++++++ heromodels/src/models/heroledger/secretbox.rs | 142 +++++ heromodels/src/models/heroledger/signature.rs | 120 ++++ heromodels/src/models/heroledger/user.rs | 370 +++++++++++++ heromodels/src/models/heroledger/user_kvs.rs | 120 ++++ heromodels/src/models/mod.rs | 1 + 12 files changed, 2240 insertions(+), 318 deletions(-) delete mode 100644 heromodels/docs/payment_usage.md create mode 100644 heromodels/docs/prompts/v_specs_to_rust_heromodels.md create mode 100644 heromodels/src/models/heroledger/dnsrecord.rs create mode 100644 heromodels/src/models/heroledger/group.rs create mode 100644 heromodels/src/models/heroledger/membership.rs create mode 100644 heromodels/src/models/heroledger/mod.rs create mode 100644 heromodels/src/models/heroledger/money.rs create mode 100644 heromodels/src/models/heroledger/secretbox.rs create mode 100644 heromodels/src/models/heroledger/signature.rs create mode 100644 heromodels/src/models/heroledger/user.rs create mode 100644 heromodels/src/models/heroledger/user_kvs.rs diff --git a/heromodels/docs/payment_usage.md b/heromodels/docs/payment_usage.md deleted file mode 100644 index 666b448..0000000 --- a/heromodels/docs/payment_usage.md +++ /dev/null @@ -1,318 +0,0 @@ -# Payment Model Usage Guide - -This document provides comprehensive instructions for AI assistants on how to use the Payment model in the heromodels repository. - -## Overview - -The Payment model represents a payment transaction in the system, typically associated with company registration or subscription payments. It integrates with Stripe for payment processing and maintains comprehensive status tracking. - -## Model Structure - -```rust -pub struct Payment { - pub base_data: BaseModelData, // Auto-managed ID, timestamps, comments - pub payment_intent_id: String, // Stripe payment intent ID - pub company_id: u32, // Foreign key to Company - pub payment_plan: String, // "monthly", "yearly", "two_year" - pub setup_fee: f64, // One-time setup fee - pub monthly_fee: f64, // Recurring monthly fee - pub total_amount: f64, // Total amount paid - pub currency: String, // Currency code (defaults to "usd") - pub status: PaymentStatus, // Current payment status - pub stripe_customer_id: Option, // Stripe customer ID (set on completion) - pub created_at: i64, // Payment creation timestamp - pub completed_at: Option, // Payment completion timestamp -} - -pub enum PaymentStatus { - Pending, // Initial state - payment created but not processed - Processing, // Payment is being processed by Stripe - Completed, // Payment successfully completed - Failed, // Payment processing failed - Refunded, // Payment was refunded -} -``` - -## Basic Usage - -### 1. Creating a New Payment - -```rust -use heromodels::models::biz::{Payment, PaymentStatus}; - -// Create a new payment with required fields -let payment = Payment::new( - "pi_1234567890".to_string(), // Stripe payment intent ID - company_id, // Company ID from database - "monthly".to_string(), // Payment plan - 100.0, // Setup fee - 49.99, // Monthly fee - 149.99, // Total amount -); - -// Payment defaults: -// - status: PaymentStatus::Pending -// - currency: "usd" -// - stripe_customer_id: None -// - created_at: current timestamp -// - completed_at: None -``` - -### 2. Using Builder Pattern - -```rust -let payment = Payment::new( - "pi_1234567890".to_string(), - company_id, - "yearly".to_string(), - 500.0, - 99.99, - 1699.88, -) -.currency("eur".to_string()) -.stripe_customer_id(Some("cus_existing_customer".to_string())); -``` - -### 3. Database Operations - -```rust -use heromodels::db::Collection; - -// Save payment to database -let db = get_db()?; -let (payment_id, saved_payment) = db.set(&payment)?; - -// Retrieve payment by ID -let retrieved_payment: Payment = db.get_by_id(payment_id)?.unwrap(); - -// Update payment -let updated_payment = saved_payment.complete_payment(Some("cus_new_customer".to_string())); -let (_, final_payment) = db.set(&updated_payment)?; -``` - -## Payment Status Management - -### Status Transitions - -```rust -// 1. Start with Pending status (default) -let payment = Payment::new(/* ... */); -assert!(payment.is_pending()); - -// 2. Mark as processing when Stripe starts processing -let processing_payment = payment.process_payment(); -assert!(processing_payment.is_processing()); - -// 3. Complete payment when Stripe confirms success -let completed_payment = processing_payment.complete_payment(Some("cus_123".to_string())); -assert!(completed_payment.is_completed()); -assert!(completed_payment.completed_at.is_some()); - -// 4. Handle failure if payment fails -let failed_payment = processing_payment.fail_payment(); -assert!(failed_payment.has_failed()); - -// 5. Refund if needed -let refunded_payment = completed_payment.refund_payment(); -assert!(refunded_payment.is_refunded()); -``` - -### Status Check Methods - -```rust -// Check current status -if payment.is_pending() { - // Show "Payment Pending" UI -} else if payment.is_processing() { - // Show "Processing Payment" UI -} else if payment.is_completed() { - // Show "Payment Successful" UI - // Enable company features -} else if payment.has_failed() { - // Show "Payment Failed" UI - // Offer retry option -} else if payment.is_refunded() { - // Show "Payment Refunded" UI -} -``` - -## Integration with Company Model - -### Complete Payment Flow - -```rust -use heromodels::models::biz::{Company, CompanyStatus, Payment, PaymentStatus}; - -// 1. Create company with pending payment status -let company = Company::new( - "TechStart Inc.".to_string(), - "REG-TS-2024-001".to_string(), - chrono::Utc::now().timestamp(), -) -.email("contact@techstart.com".to_string()) -.status(CompanyStatus::PendingPayment); - -let (company_id, company) = db.set(&company)?; - -// 2. Create payment for the company -let payment = Payment::new( - stripe_payment_intent_id, - company_id, - "yearly".to_string(), - 500.0, // Setup fee - 99.0, // Monthly fee - 1688.0, // Total (setup + 12 months) -); - -let (payment_id, payment) = db.set(&payment)?; - -// 3. Process payment through Stripe -let processing_payment = payment.process_payment(); -let (_, processing_payment) = db.set(&processing_payment)?; - -// 4. On successful Stripe webhook -let completed_payment = processing_payment.complete_payment(Some(stripe_customer_id)); -let (_, completed_payment) = db.set(&completed_payment)?; - -// 5. Activate company -let active_company = company.status(CompanyStatus::Active); -let (_, active_company) = db.set(&active_company)?; -``` - -## Database Indexing - -The Payment model provides custom indexes for efficient querying: - -```rust -// Indexed fields for fast lookups: -// - payment_intent_id: Find payment by Stripe intent ID -// - company_id: Find all payments for a company -// - status: Find payments by status - -// Example queries (conceptual - actual implementation depends on your query layer) -// let pending_payments = db.find_by_index("status", "Pending")?; -// let company_payments = db.find_by_index("company_id", company_id.to_string())?; -// let stripe_payment = db.find_by_index("payment_intent_id", "pi_1234567890")?; -``` - -## Error Handling Best Practices - -```rust -use heromodels::db::DbError; - -fn process_payment_flow(payment_intent_id: String, company_id: u32) -> Result { - let db = get_db()?; - - // Create payment - let payment = Payment::new( - payment_intent_id, - company_id, - "monthly".to_string(), - 100.0, - 49.99, - 149.99, - ); - - // Save to database - let (payment_id, payment) = db.set(&payment)?; - - // Process through Stripe (external API call) - match process_stripe_payment(&payment.payment_intent_id) { - Ok(stripe_customer_id) => { - // Success: complete payment - let completed_payment = payment.complete_payment(Some(stripe_customer_id)); - let (_, final_payment) = db.set(&completed_payment)?; - Ok(final_payment) - } - Err(_) => { - // Failure: mark as failed - let failed_payment = payment.fail_payment(); - let (_, final_payment) = db.set(&failed_payment)?; - Ok(final_payment) - } - } -} -``` - -## Testing - -The Payment model includes comprehensive tests in `tests/payment.rs`. When working with payments: - -1. **Always test status transitions** -2. **Verify timestamp handling** -3. **Test database persistence** -4. **Test integration with Company model** -5. **Test builder pattern methods** - -```bash -# Run payment tests -cargo test payment - -# Run specific test -cargo test test_payment_completion -``` - -## Common Patterns - -### 1. Payment Retry Logic -```rust -fn retry_failed_payment(payment: Payment) -> Payment { - if payment.has_failed() { - // Reset to pending for retry - Payment::new( - payment.payment_intent_id, - payment.company_id, - payment.payment_plan, - payment.setup_fee, - payment.monthly_fee, - payment.total_amount, - ) - .currency(payment.currency) - } else { - payment - } -} -``` - -### 2. Payment Summary -```rust -fn get_payment_summary(payment: &Payment) -> String { - format!( - "Payment {} for company {}: {} {} ({})", - payment.payment_intent_id, - payment.company_id, - payment.total_amount, - payment.currency.to_uppercase(), - payment.status - ) -} -``` - -### 3. Payment Validation -```rust -fn validate_payment(payment: &Payment) -> Result<(), String> { - if payment.total_amount <= 0.0 { - return Err("Total amount must be positive".to_string()); - } - if payment.payment_intent_id.is_empty() { - return Err("Payment intent ID is required".to_string()); - } - if payment.company_id == 0 { - return Err("Valid company ID is required".to_string()); - } - Ok(()) -} -``` - -## Key Points for AI Assistants - -1. **Always use auto-generated IDs** - Don't manually set IDs, let OurDB handle them -2. **Follow status flow** - Pending → Processing → Completed/Failed → (optionally) Refunded -3. **Update timestamps** - `completed_at` is automatically set when calling `complete_payment()` -4. **Use builder pattern** - For optional fields and cleaner code -5. **Test thoroughly** - Payment logic is critical, always verify with tests -6. **Handle errors gracefully** - Payment failures should be tracked, not ignored -7. **Integrate with Company** - Payments typically affect company status -8. **Use proper indexing** - Leverage indexed fields for efficient queries - -This model follows the heromodels patterns and integrates seamlessly with the existing codebase architecture. diff --git a/heromodels/docs/prompts/v_specs_to_rust_heromodels.md b/heromodels/docs/prompts/v_specs_to_rust_heromodels.md new file mode 100644 index 0000000..2f97961 --- /dev/null +++ b/heromodels/docs/prompts/v_specs_to_rust_heromodels.md @@ -0,0 +1,301 @@ +# AI Prompt: Convert V Language Specs to Rust Hero Models + +## Objective +Convert V language model specifications (`.v` files) to Rust hero models that integrate with the heromodels framework. The generated Rust models should follow the established patterns for base data embedding, indexing, fluent builder APIs, and Rhai scripting integration. + +## V Language Input Structure Analysis + +### V Spec Patterns to Recognize: +1. **Module Declaration**: `module circle` or `module group` +2. **Base Embedding**: `core.Base` - represents the base model data +3. **Index Fields**: Fields marked with `@[index]` comments +4. **Mutability**: Fields declared with `pub mut:` +5. **Enums**: `pub enum Status { active, inactive, suspended }` +6. **Nested Structs**: Embedded configuration or related data structures +7. **Collections**: `[]u32`, `[]string`, `map[string]string` +8. **References**: `u32` fields typically represent foreign key references + +### Example V Spec Structure: +```v +module circle + +import freeflowuniverse.herolib.hero.models.core + +pub struct User { + core.Base +pub mut: + username string @[index] // Unique username + email []string @[index] // Multiple email addresses + status UserStatus // Enum reference + profile UserProfile // Nested struct + metadata map[string]string // Key-value pairs +} + +pub enum UserStatus { + active + inactive + suspended +} + +pub struct UserProfile { +pub mut: + full_name string + bio string + links map[string]string +} +``` + +## Rust Hero Model Conversion Rules + +### 1. File Structure and Imports +```rust +use heromodels_core::{Model, BaseModelData, IndexKey}; +use heromodels_derive::model; +use rhai::CustomType; +use rhailib_derive::RhaiApi; +use serde::{Deserialize, Serialize}; +use chrono::{DateTime, Utc}; +``` + +### 2. Base Data Embedding +- **V**: `core.Base` +- **Rust**: `pub base_data: BaseModelData,` + +### 3. Index Field Conversion +- **V**: `field_name string @[index]` +- **Rust**: `#[index] pub field_name: String,` + +### 4. Type Mappings +| V Type | Rust Type | +|--------|-----------| +| `string` | `String` | +| `[]string` | `Vec` | +| `[]u32` | `Vec` | +| `u32` | `u32` | +| `u64` | `u64` | +| `f64` | `f64` | +| `bool` | `bool` | +| `map[string]string` | `std::collections::HashMap` | + +### 5. Struct Declaration Pattern +```rust +/// Documentation comment describing the model +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default, RhaiApi)] +pub struct ModelName { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub indexed_field: String, + pub regular_field: String, + pub optional_field: Option, + pub nested_struct: NestedType, + pub collection: Vec, + pub metadata: std::collections::HashMap, +} +``` + +### 6. Enum Conversion +```rust +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum UserStatus { + Active, + Inactive, + Suspended, +} +``` + +### 7. Fluent Builder Implementation +Every model must implement a fluent builder pattern: + +```rust +impl ModelName { + /// Create a new instance + pub fn new(id: u32) -> Self { + Self { + base_data: BaseModelData::new(id), + indexed_field: String::new(), + regular_field: String::new(), + optional_field: None, + nested_struct: NestedType::new(), + collection: Vec::new(), + metadata: std::collections::HashMap::new(), + } + } + + /// Set indexed field (fluent) + pub fn indexed_field(mut self, value: impl ToString) -> Self { + self.indexed_field = value.to_string(); + self + } + + /// Set regular field (fluent) + pub fn regular_field(mut self, value: impl ToString) -> Self { + self.regular_field = value.to_string(); + self + } + + /// Set optional field (fluent) + pub fn optional_field(mut self, value: impl ToString) -> Self { + self.optional_field = Some(value.to_string()); + self + } + + /// Set nested struct (fluent) + pub fn nested_struct(mut self, value: NestedType) -> Self { + self.nested_struct = value; + self + } + + /// Add to collection (fluent) + pub fn add_to_collection(mut self, value: u32) -> Self { + self.collection.push(value); + self + } + + /// Set entire collection (fluent) + pub fn collection(mut self, value: Vec) -> Self { + self.collection = value; + self + } + + /// Add metadata entry (fluent) + pub fn add_metadata(mut self, key: impl ToString, value: impl ToString) -> Self { + self.metadata.insert(key.to_string(), value.to_string()); + self + } + + /// Build the final instance + pub fn build(self) -> Self { + self + } +} +``` + +### 8. Model Trait Implementation +```rust +impl Model for ModelName { + fn db_prefix() -> &'static str { + "modelname" + } + + fn get_id(&self) -> u32 { + self.base_data.id + } + + fn base_data_mut(&mut self) -> &mut BaseModelData { + &mut self.base_data + } + + fn db_keys(&self) -> Vec { + let mut keys = Vec::new(); + + // Add index keys for fields marked with #[index] + keys.push(IndexKey::new("indexed_field", &self.indexed_field)); + + // Add additional index keys as needed + keys + } +} +``` + +### 9. Nested Struct Builder Pattern +For embedded types, implement similar builder patterns: + +```rust +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct NestedType { + pub field1: String, + pub field2: String, +} + +impl NestedType { + pub fn new() -> Self { + Self { + field1: String::new(), + field2: String::new(), + } + } + + pub fn field1(mut self, value: impl ToString) -> Self { + self.field1 = value.to_string(); + self + } + + pub fn field2(mut self, value: impl ToString) -> Self { + self.field2 = value.to_string(); + self + } + + pub fn build(self) -> Self { + self + } +} +``` + +## Conversion Steps + +1. **Analyze V Spec Structure** + - Identify the module name and main structs + - Note which fields are marked with `@[index]` + - Identify nested structs and enums + - Map field types from V to Rust + +2. **Create Rust File Structure** + - Add appropriate imports + - Convert enums first (they're often referenced by structs) + - Convert nested structs before main structs + +3. **Implement Main Struct** + - Add `#[model]` macro and derives + - Embed `BaseModelData` as `base_data` + - Mark indexed fields with `#[index]` + - Convert field types according to mapping table + +4. **Implement Builder Pattern** + - Add `new(id: u32)` constructor + - Add fluent setter methods for each field + - Handle optional fields appropriately + - Add collection manipulation methods + +5. **Implement Model Trait** + - Define appropriate `db_prefix` + - Implement required trait methods + - Add index keys for searchable fields + +6. **Add Documentation** + - Document the struct and its purpose + - Document each field's meaning + - Add usage examples in comments + +## Example Usage After Conversion + +```rust +let user = User::new(1) + .username("john_doe") + .add_email("john@example.com") + .add_email("john.doe@company.com") + .status(UserStatus::Active) + .profile( + UserProfile::new() + .full_name("John Doe") + .bio("Software developer") + .build() + ) + .add_metadata("department", "engineering") + .build(); +``` + +## Notes and Best Practices + +1. **Field Naming**: Convert V snake_case to Rust snake_case (usually no change needed) +2. **Optional Fields**: Use `Option` for fields that may be empty in V +3. **Collections**: Always provide both `add_item` and `set_collection` methods +4. **Error Handling**: Builder methods should not panic; use appropriate defaults +5. **Documentation**: Include comprehensive documentation for public APIs +6. **Testing**: Consider adding unit tests for builder patterns +7. **Validation**: Add validation logic in builder methods if needed + +## File Organization + +Place the converted Rust models in the appropriate subdirectory under `heromodels/src/models/` based on the domain (e.g., `user/`, `finance/`, `governance/`, etc.). diff --git a/heromodels/src/models/heroledger/dnsrecord.rs b/heromodels/src/models/heroledger/dnsrecord.rs new file mode 100644 index 0000000..47fc162 --- /dev/null +++ b/heromodels/src/models/heroledger/dnsrecord.rs @@ -0,0 +1,301 @@ +use heromodels_core::{Model, BaseModelData, IndexKey}; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Defines the supported DNS record types +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum NameType { + A, + AAAA, + CNAME, + MX, + TXT, + SRV, + PTR, + NS, +} + +impl Default for NameType { + fn default() -> Self { + NameType::A + } +} + +/// Category of the DNS record +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum NameCat { + IPv4, + IPv6, + Mycelium, +} + +impl Default for NameCat { + fn default() -> Self { + NameCat::IPv4 + } +} + +/// Status of a DNS zone +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum DNSZoneStatus { + Active, + Suspended, + Archived, +} + +impl Default for DNSZoneStatus { + fn default() -> Self { + DNSZoneStatus::Active + } +} + +/// Represents a DNS record configuration +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct DNSRecord { + pub subdomain: String, + pub record_type: NameType, + pub value: String, + pub priority: u32, + pub ttl: u32, + pub is_active: bool, + pub cat: NameCat, + pub is_wildcard: bool, +} + +impl DNSRecord { + pub fn new() -> Self { + Self { + subdomain: String::new(), + record_type: NameType::default(), + value: String::new(), + priority: 0, + ttl: 3600, + is_active: true, + cat: NameCat::default(), + is_wildcard: false, + } + } + + pub fn subdomain(mut self, subdomain: impl ToString) -> Self { + self.subdomain = subdomain.to_string(); + self + } + + pub fn record_type(mut self, record_type: NameType) -> Self { + self.record_type = record_type; + self + } + + pub fn value(mut self, value: impl ToString) -> Self { + self.value = value.to_string(); + self + } + + pub fn priority(mut self, priority: u32) -> Self { + self.priority = priority; + self + } + + pub fn ttl(mut self, ttl: u32) -> Self { + self.ttl = ttl; + self + } + + pub fn is_active(mut self, is_active: bool) -> Self { + self.is_active = is_active; + self + } + + pub fn cat(mut self, cat: NameCat) -> Self { + self.cat = cat; + self + } + + pub fn is_wildcard(mut self, is_wildcard: bool) -> Self { + self.is_wildcard = is_wildcard; + self + } + + pub fn build(self) -> Self { + self + } +} + +/// SOA (Start of Authority) record for a DNS zone +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct SOARecord { + pub zone_id: u32, + pub primary_ns: String, + pub admin_email: String, + pub serial: u64, + pub refresh: u32, + pub retry: u32, + pub expire: u32, + pub minimum_ttl: u32, + pub is_active: bool, +} + +impl SOARecord { + pub fn new() -> Self { + Self { + zone_id: 0, + primary_ns: String::new(), + admin_email: String::new(), + serial: 0, + refresh: 3600, + retry: 600, + expire: 604800, + minimum_ttl: 3600, + is_active: true, + } + } + + pub fn zone_id(mut self, zone_id: u32) -> Self { + self.zone_id = zone_id; + self + } + + pub fn primary_ns(mut self, primary_ns: impl ToString) -> Self { + self.primary_ns = primary_ns.to_string(); + self + } + + pub fn admin_email(mut self, admin_email: impl ToString) -> Self { + self.admin_email = admin_email.to_string(); + self + } + + pub fn serial(mut self, serial: u64) -> Self { + self.serial = serial; + self + } + + pub fn refresh(mut self, refresh: u32) -> Self { + self.refresh = refresh; + self + } + + pub fn retry(mut self, retry: u32) -> Self { + self.retry = retry; + self + } + + pub fn expire(mut self, expire: u32) -> Self { + self.expire = expire; + self + } + + pub fn minimum_ttl(mut self, minimum_ttl: u32) -> Self { + self.minimum_ttl = minimum_ttl; + self + } + + pub fn is_active(mut self, is_active: bool) -> Self { + self.is_active = is_active; + self + } + + pub fn build(self) -> Self { + self + } +} + +/// Represents a DNS zone with its configuration and records +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct DNSZone { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub domain: String, + pub dnsrecords: Vec, + pub administrators: Vec, + pub status: DNSZoneStatus, + pub metadata: HashMap, + pub soarecord: Vec, +} + +impl DNSZone { + /// Create a new DNS zone instance + pub fn new(id: u32) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); + Self { + base_data, + domain: String::new(), + dnsrecords: Vec::new(), + administrators: Vec::new(), + status: DNSZoneStatus::default(), + metadata: HashMap::new(), + soarecord: Vec::new(), + } + } + + /// Set the domain name (fluent) + pub fn domain(mut self, domain: impl ToString) -> Self { + self.domain = domain.to_string(); + self + } + + /// Add a DNS record (fluent) + pub fn add_dnsrecord(mut self, record: DNSRecord) -> Self { + self.dnsrecords.push(record); + self + } + + /// Set all DNS records (fluent) + pub fn dnsrecords(mut self, dnsrecords: Vec) -> Self { + self.dnsrecords = dnsrecords; + self + } + + /// Add an administrator (fluent) + pub fn add_administrator(mut self, admin_id: u32) -> Self { + self.administrators.push(admin_id); + self + } + + /// Set all administrators (fluent) + pub fn administrators(mut self, administrators: Vec) -> Self { + self.administrators = administrators; + self + } + + /// Set the zone status (fluent) + pub fn status(mut self, status: DNSZoneStatus) -> Self { + self.status = status; + self + } + + /// Add metadata entry (fluent) + pub fn add_metadata(mut self, key: impl ToString, value: impl ToString) -> Self { + self.metadata.insert(key.to_string(), value.to_string()); + self + } + + /// Set all metadata (fluent) + pub fn metadata(mut self, metadata: HashMap) -> Self { + self.metadata = metadata; + self + } + + /// Add an SOA record (fluent) + pub fn add_soarecord(mut self, soa: SOARecord) -> Self { + self.soarecord.push(soa); + self + } + + /// Set all SOA records (fluent) + pub fn soarecord(mut self, soarecord: Vec) -> Self { + self.soarecord = soarecord; + self + } + + /// Build the final DNS zone instance + pub fn build(self) -> Self { + self + } +} + + diff --git a/heromodels/src/models/heroledger/group.rs b/heromodels/src/models/heroledger/group.rs new file mode 100644 index 0000000..a01b98d --- /dev/null +++ b/heromodels/src/models/heroledger/group.rs @@ -0,0 +1,236 @@ +use heromodels_core::{Model, BaseModelData, IndexKey}; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; + +/// Defines the lifecycle of a group +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum GroupStatus { + Active, + Inactive, + Suspended, + Archived, +} + +impl Default for GroupStatus { + fn default() -> Self { + GroupStatus::Active + } +} + +/// Visibility controls who can discover or view the group +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum Visibility { + Public, // Anyone can see and request to join + Private, // Only invited users can see the group + Unlisted, // Not visible in search; only accessible by direct link or DNS +} + +impl Default for Visibility { + fn default() -> Self { + Visibility::Public + } +} + +/// GroupConfig holds rules that govern group membership and behavior +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct GroupConfig { + pub max_members: u32, + pub allow_guests: bool, + pub auto_approve: bool, + pub require_invite: bool, +} + +impl GroupConfig { + pub fn new() -> Self { + Self { + max_members: 0, + allow_guests: false, + auto_approve: false, + require_invite: false, + } + } + + pub fn max_members(mut self, max_members: u32) -> Self { + self.max_members = max_members; + self + } + + pub fn allow_guests(mut self, allow_guests: bool) -> Self { + self.allow_guests = allow_guests; + self + } + + pub fn auto_approve(mut self, auto_approve: bool) -> Self { + self.auto_approve = auto_approve; + self + } + + pub fn require_invite(mut self, require_invite: bool) -> Self { + self.require_invite = require_invite; + self + } + + pub fn build(self) -> Self { + self + } +} + +/// Represents a collaborative or access-controlled unit within the system +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct Group { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub name: String, + pub description: String, + pub dnsrecords: Vec, + pub administrators: Vec, + pub config: GroupConfig, + pub status: GroupStatus, + pub visibility: Visibility, + pub created: u64, + pub updated: u64, +} + +impl Group { + /// Create a new group instance + pub fn new(id: u32) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); + Self { + base_data, + name: String::new(), + description: String::new(), + dnsrecords: Vec::new(), + administrators: Vec::new(), + config: GroupConfig::new(), + status: GroupStatus::default(), + visibility: Visibility::default(), + created: 0, + updated: 0, + } + } + + /// Set the group name (fluent) + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); + self + } + + /// Set the group description (fluent) + pub fn description(mut self, description: impl ToString) -> Self { + self.description = description.to_string(); + self + } + + /// Add a DNS record ID (fluent) + pub fn add_dnsrecord(mut self, dnsrecord_id: u32) -> Self { + self.dnsrecords.push(dnsrecord_id); + self + } + + /// Set all DNS record IDs (fluent) + pub fn dnsrecords(mut self, dnsrecords: Vec) -> Self { + self.dnsrecords = dnsrecords; + self + } + + /// Add an administrator user ID (fluent) + pub fn add_administrator(mut self, user_id: u32) -> Self { + self.administrators.push(user_id); + self + } + + /// Set all administrator user IDs (fluent) + pub fn administrators(mut self, administrators: Vec) -> Self { + self.administrators = administrators; + self + } + + /// Set the group configuration (fluent) + pub fn config(mut self, config: GroupConfig) -> Self { + self.config = config; + self + } + + /// Set the group status (fluent) + pub fn status(mut self, status: GroupStatus) -> Self { + self.status = status; + self + } + + /// Set the group visibility (fluent) + pub fn visibility(mut self, visibility: Visibility) -> Self { + self.visibility = visibility; + self + } + + /// Set the created timestamp (fluent) + pub fn created(mut self, created: u64) -> Self { + self.created = created; + self + } + + /// Set the updated timestamp (fluent) + pub fn updated(mut self, updated: u64) -> Self { + self.updated = updated; + self + } + + /// Build the final group instance + pub fn build(self) -> Self { + self + } +} + + + +/// Represents the membership relationship between users and groups +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct UserGroupMembership { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub user_id: u32, + pub group_ids: Vec, +} + +impl UserGroupMembership { + /// Create a new user group membership instance + pub fn new(id: u32) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); + Self { + base_data, + user_id: 0, + group_ids: Vec::new(), + } + } + + /// Set the user ID (fluent) + pub fn user_id(mut self, user_id: u32) -> Self { + self.user_id = user_id; + self + } + + /// Add a group ID (fluent) + pub fn add_group_id(mut self, group_id: u32) -> Self { + self.group_ids.push(group_id); + self + } + + /// Set all group IDs (fluent) + pub fn group_ids(mut self, group_ids: Vec) -> Self { + self.group_ids = group_ids; + self + } + + /// Build the final membership instance + pub fn build(self) -> Self { + self + } +} + + diff --git a/heromodels/src/models/heroledger/membership.rs b/heromodels/src/models/heroledger/membership.rs new file mode 100644 index 0000000..bce12b5 --- /dev/null +++ b/heromodels/src/models/heroledger/membership.rs @@ -0,0 +1,115 @@ +use heromodels_core::{Model, BaseModelData, IndexKey}; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; + +/// Defines the possible roles a member can have +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum MemberRole { + Owner, + Admin, + Moderator, + Member, + Guest, +} + +impl Default for MemberRole { + fn default() -> Self { + MemberRole::Member + } +} + +/// Represents the current status of membership +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum MemberStatus { + Active, + Pending, + Suspended, + Removed, +} + +impl Default for MemberStatus { + fn default() -> Self { + MemberStatus::Pending + } +} + +/// Represents a member within a circle +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct Member { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub user_id: u32, + pub role: MemberRole, + pub status: MemberStatus, + pub joined_at: u64, + pub invited_by: u32, + pub permissions: Vec, +} + +impl Member { + /// Create a new member instance + pub fn new(id: u32) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); + Self { + base_data, + user_id: 0, + role: MemberRole::default(), + status: MemberStatus::default(), + joined_at: 0, + invited_by: 0, + permissions: Vec::new(), + } + } + + /// Set the user ID (fluent) + pub fn user_id(mut self, user_id: u32) -> Self { + self.user_id = user_id; + self + } + + /// Set the member role (fluent) + pub fn role(mut self, role: MemberRole) -> Self { + self.role = role; + self + } + + /// Set the member status (fluent) + pub fn status(mut self, status: MemberStatus) -> Self { + self.status = status; + self + } + + /// Set the joined timestamp (fluent) + pub fn joined_at(mut self, joined_at: u64) -> Self { + self.joined_at = joined_at; + self + } + + /// Set who invited this member (fluent) + pub fn invited_by(mut self, invited_by: u32) -> Self { + self.invited_by = invited_by; + self + } + + /// Add a permission (fluent) + pub fn add_permission(mut self, permission: impl ToString) -> Self { + self.permissions.push(permission.to_string()); + self + } + + /// Set all permissions (fluent) + pub fn permissions(mut self, permissions: Vec) -> Self { + self.permissions = permissions; + self + } + + /// Build the final member instance + pub fn build(self) -> Self { + self + } +} + + diff --git a/heromodels/src/models/heroledger/mod.rs b/heromodels/src/models/heroledger/mod.rs new file mode 100644 index 0000000..4237870 --- /dev/null +++ b/heromodels/src/models/heroledger/mod.rs @@ -0,0 +1,19 @@ +// Export all heroledger model modules +pub mod user; +pub mod group; +pub mod money; +pub mod membership; +pub mod dnsrecord; +pub mod secretbox; +pub mod signature; +pub mod user_kvs; + +// Re-export key types for convenience +pub use user::{User, UserStatus, UserProfile, KYCInfo, KYCStatus, SecretBox}; +pub use group::{Group, UserGroupMembership, GroupStatus, Visibility, GroupConfig}; +pub use money::{Account, Asset, AccountPolicy, AccountPolicyItem, Transaction, AccountStatus, TransactionType, Signature as TransactionSignature}; +pub use membership::{Member, MemberRole, MemberStatus}; +pub use dnsrecord::{DNSZone, DNSRecord, SOARecord, NameType, NameCat, DNSZoneStatus}; +pub use secretbox::{Notary, NotaryStatus, SecretBoxCategory}; +pub use signature::{Signature, SignatureStatus, ObjectType}; +pub use user_kvs::{UserKVS, UserKVSItem}; diff --git a/heromodels/src/models/heroledger/money.rs b/heromodels/src/models/heroledger/money.rs new file mode 100644 index 0000000..9bcb02d --- /dev/null +++ b/heromodels/src/models/heroledger/money.rs @@ -0,0 +1,515 @@ +use heromodels_core::{Model, BaseModelData, IndexKey}; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Represents the status of an account +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum AccountStatus { + Active, + Inactive, + Suspended, + Archived, +} + +impl Default for AccountStatus { + fn default() -> Self { + AccountStatus::Active + } +} + +/// Represents the type of transaction +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum TransactionType { + Transfer, + Clawback, + Freeze, + Unfreeze, + Issue, + Burn, +} + +impl Default for TransactionType { + fn default() -> Self { + TransactionType::Transfer + } +} + +/// Represents a signature for transactions +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Signature { + pub signer_id: u32, + pub signature: String, + pub timestamp: u64, +} + +impl Signature { + pub fn new() -> Self { + Self { + signer_id: 0, + signature: String::new(), + timestamp: 0, + } + } + + pub fn signer_id(mut self, signer_id: u32) -> Self { + self.signer_id = signer_id; + self + } + + pub fn signature(mut self, signature: impl ToString) -> Self { + self.signature = signature.to_string(); + self + } + + pub fn timestamp(mut self, timestamp: u64) -> Self { + self.timestamp = timestamp; + self + } + + pub fn build(self) -> Self { + self + } +} + +/// Policy item for account operations +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct AccountPolicyItem { + pub signers: Vec, + pub min_signatures: u32, + pub enabled: bool, + pub threshold: f64, + pub recipient: u32, +} + +impl AccountPolicyItem { + pub fn new() -> Self { + Self { + signers: Vec::new(), + min_signatures: 0, + enabled: false, + threshold: 0.0, + recipient: 0, + } + } + + pub fn add_signer(mut self, signer_id: u32) -> Self { + self.signers.push(signer_id); + self + } + + pub fn signers(mut self, signers: Vec) -> Self { + self.signers = signers; + self + } + + pub fn min_signatures(mut self, min_signatures: u32) -> Self { + self.min_signatures = min_signatures; + self + } + + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + pub fn threshold(mut self, threshold: f64) -> Self { + self.threshold = threshold; + self + } + + pub fn recipient(mut self, recipient: u32) -> Self { + self.recipient = recipient; + self + } + + pub fn build(self) -> Self { + self + } +} + +/// Represents an account in the financial system +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct Account { + /// Base model data + pub base_data: BaseModelData, + pub owner_id: u32, + #[index] + pub address: String, + pub balance: f64, + pub currency: String, + pub assetid: u32, + pub last_activity: u64, + pub administrators: Vec, + pub accountpolicy: u32, +} + +impl Account { + /// Create a new account instance + pub fn new(id: u32) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); + Self { + base_data, + owner_id: 0, + address: String::new(), + balance: 0.0, + currency: String::new(), + assetid: 0, + last_activity: 0, + administrators: Vec::new(), + accountpolicy: 0, + } + } + + /// Set the owner ID (fluent) + pub fn owner_id(mut self, owner_id: u32) -> Self { + self.owner_id = owner_id; + self + } + + /// Set the blockchain address (fluent) + pub fn address(mut self, address: impl ToString) -> Self { + self.address = address.to_string(); + self + } + + /// Set the balance (fluent) + pub fn balance(mut self, balance: f64) -> Self { + self.balance = balance; + self + } + + /// Set the currency (fluent) + pub fn currency(mut self, currency: impl ToString) -> Self { + self.currency = currency.to_string(); + self + } + + /// Set the asset ID (fluent) + pub fn assetid(mut self, assetid: u32) -> Self { + self.assetid = assetid; + self + } + + /// Set the last activity timestamp (fluent) + pub fn last_activity(mut self, last_activity: u64) -> Self { + self.last_activity = last_activity; + self + } + + /// Add an administrator (fluent) + pub fn add_administrator(mut self, admin_id: u32) -> Self { + self.administrators.push(admin_id); + self + } + + /// Set all administrators (fluent) + pub fn administrators(mut self, administrators: Vec) -> Self { + self.administrators = administrators; + self + } + + /// Set the account policy ID (fluent) + pub fn accountpolicy(mut self, accountpolicy: u32) -> Self { + self.accountpolicy = accountpolicy; + self + } + + /// Build the final account instance + pub fn build(self) -> Self { + self + } +} + + + +/// Represents an asset in the financial system +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct Asset { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub address: String, + pub assetid: u32, + pub asset_type: String, + pub issuer: u32, + pub supply: f64, + pub decimals: u8, + pub is_frozen: bool, + pub metadata: HashMap, + pub administrators: Vec, + pub min_signatures: u32, +} + +impl Asset { + /// Create a new asset instance + pub fn new(id: u32) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); + Self { + base_data, + address: String::new(), + assetid: 0, + asset_type: String::new(), + issuer: 0, + supply: 0.0, + decimals: 0, + is_frozen: false, + metadata: HashMap::new(), + administrators: Vec::new(), + min_signatures: 0, + } + } + + /// Set the blockchain address (fluent) + pub fn address(mut self, address: impl ToString) -> Self { + self.address = address.to_string(); + self + } + + /// Set the asset ID (fluent) + pub fn assetid(mut self, assetid: u32) -> Self { + self.assetid = assetid; + self + } + + /// Set the asset type (fluent) + pub fn asset_type(mut self, asset_type: impl ToString) -> Self { + self.asset_type = asset_type.to_string(); + self + } + + /// Set the issuer (fluent) + pub fn issuer(mut self, issuer: u32) -> Self { + self.issuer = issuer; + self + } + + /// Set the supply (fluent) + pub fn supply(mut self, supply: f64) -> Self { + self.supply = supply; + self + } + + /// Set the decimals (fluent) + pub fn decimals(mut self, decimals: u8) -> Self { + self.decimals = decimals; + self + } + + /// Set the frozen status (fluent) + pub fn is_frozen(mut self, is_frozen: bool) -> Self { + self.is_frozen = is_frozen; + self + } + + /// Add metadata entry (fluent) + pub fn add_metadata(mut self, key: impl ToString, value: impl ToString) -> Self { + self.metadata.insert(key.to_string(), value.to_string()); + self + } + + /// Set all metadata (fluent) + pub fn metadata(mut self, metadata: HashMap) -> Self { + self.metadata = metadata; + self + } + + /// Add an administrator (fluent) + pub fn add_administrator(mut self, admin_id: u32) -> Self { + self.administrators.push(admin_id); + self + } + + /// Set all administrators (fluent) + pub fn administrators(mut self, administrators: Vec) -> Self { + self.administrators = administrators; + self + } + + /// Set minimum signatures required (fluent) + pub fn min_signatures(mut self, min_signatures: u32) -> Self { + self.min_signatures = min_signatures; + self + } + + /// Build the final asset instance + pub fn build(self) -> Self { + self + } +} + + + +/// Represents account policies for various operations +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct AccountPolicy { + /// Base model data + pub base_data: BaseModelData, + pub transferpolicy: AccountPolicyItem, + pub adminpolicy: AccountPolicyItem, + pub clawbackpolicy: AccountPolicyItem, + pub freezepolicy: AccountPolicyItem, +} + +impl AccountPolicy { + /// Create a new account policy instance + pub fn new(id: u32) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); + Self { + base_data, + transferpolicy: AccountPolicyItem::new(), + adminpolicy: AccountPolicyItem::new(), + clawbackpolicy: AccountPolicyItem::new(), + freezepolicy: AccountPolicyItem::new(), + } + } + + /// Set the transfer policy (fluent) + pub fn transferpolicy(mut self, transferpolicy: AccountPolicyItem) -> Self { + self.transferpolicy = transferpolicy; + self + } + + /// Set the admin policy (fluent) + pub fn adminpolicy(mut self, adminpolicy: AccountPolicyItem) -> Self { + self.adminpolicy = adminpolicy; + self + } + + /// Set the clawback policy (fluent) + pub fn clawbackpolicy(mut self, clawbackpolicy: AccountPolicyItem) -> Self { + self.clawbackpolicy = clawbackpolicy; + self + } + + /// Set the freeze policy (fluent) + pub fn freezepolicy(mut self, freezepolicy: AccountPolicyItem) -> Self { + self.freezepolicy = freezepolicy; + self + } + + /// Build the final account policy instance + pub fn build(self) -> Self { + self + } +} + + + +/// Represents a financial transaction +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct Transaction { + /// Base model data + pub base_data: BaseModelData, + pub txid: u32, + pub source: u32, + pub destination: u32, + pub assetid: u32, + pub amount: f64, + pub timestamp: u64, + pub status: String, + pub memo: String, + pub tx_type: TransactionType, + pub signatures: Vec, +} + +impl Transaction { + /// Create a new transaction instance + pub fn new(id: u32) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); + Self { + base_data, + txid: 0, + source: 0, + destination: 0, + assetid: 0, + amount: 0.0, + timestamp: 0, + status: String::new(), + memo: String::new(), + tx_type: TransactionType::default(), + signatures: Vec::new(), + } + } + + /// Set the transaction ID (fluent) + pub fn txid(mut self, txid: u32) -> Self { + self.txid = txid; + self + } + + /// Set the source account (fluent) + pub fn source(mut self, source: u32) -> Self { + self.source = source; + self + } + + /// Set the destination account (fluent) + pub fn destination(mut self, destination: u32) -> Self { + self.destination = destination; + self + } + + /// Set the asset ID (fluent) + pub fn assetid(mut self, assetid: u32) -> Self { + self.assetid = assetid; + self + } + + /// Set the amount (fluent) + pub fn amount(mut self, amount: f64) -> Self { + self.amount = amount; + self + } + + /// Set the timestamp (fluent) + pub fn timestamp(mut self, timestamp: u64) -> Self { + self.timestamp = timestamp; + self + } + + /// Set the status (fluent) + pub fn status(mut self, status: impl ToString) -> Self { + self.status = status.to_string(); + self + } + + /// Set the memo (fluent) + pub fn memo(mut self, memo: impl ToString) -> Self { + self.memo = memo.to_string(); + self + } + + /// Set the transaction type (fluent) + pub fn tx_type(mut self, tx_type: TransactionType) -> Self { + self.tx_type = tx_type; + self + } + + /// Add a signature (fluent) + pub fn add_signature(mut self, signature: Signature) -> Self { + self.signatures.push(signature); + self + } + + /// Set all signatures (fluent) + pub fn signatures(mut self, signatures: Vec) -> Self { + self.signatures = signatures; + self + } + + /// Build the final transaction instance + pub fn build(self) -> Self { + self + } +} + + diff --git a/heromodels/src/models/heroledger/secretbox.rs b/heromodels/src/models/heroledger/secretbox.rs new file mode 100644 index 0000000..4f93e39 --- /dev/null +++ b/heromodels/src/models/heroledger/secretbox.rs @@ -0,0 +1,142 @@ +use heromodels_core::{Model, BaseModelData, IndexKey}; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; + +/// Category of the secret box +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum SecretBoxCategory { + Profile, +} + +impl Default for SecretBoxCategory { + fn default() -> Self { + SecretBoxCategory::Profile + } +} + +/// Status of a notary +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum NotaryStatus { + Active, + Inactive, + Suspended, + Archived, + Error, +} + +impl Default for NotaryStatus { + fn default() -> Self { + NotaryStatus::Active + } +} + +/// Represents an encrypted secret box for storing sensitive data +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct SecretBox { + pub notary_id: u32, + pub value: String, + pub version: u16, + pub timestamp: u64, + pub cat: SecretBoxCategory, +} + +impl SecretBox { + pub fn new() -> Self { + Self { + notary_id: 0, + value: String::new(), + version: 1, + timestamp: 0, + cat: SecretBoxCategory::default(), + } + } + + pub fn notary_id(mut self, notary_id: u32) -> Self { + self.notary_id = notary_id; + self + } + + pub fn value(mut self, value: impl ToString) -> Self { + self.value = value.to_string(); + self + } + + pub fn version(mut self, version: u16) -> Self { + self.version = version; + self + } + + pub fn timestamp(mut self, timestamp: u64) -> Self { + self.timestamp = timestamp; + self + } + + pub fn cat(mut self, cat: SecretBoxCategory) -> Self { + self.cat = cat; + self + } + + pub fn build(self) -> Self { + self + } +} + +/// Represents a notary who can decrypt secret boxes +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct Notary { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub userid: u32, + pub status: NotaryStatus, + pub myceliumaddress: String, + #[index] + pub pubkey: String, +} + +impl Notary { + /// Create a new notary instance + pub fn new(id: u32) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); + Self { + base_data, + userid: 0, + status: NotaryStatus::default(), + myceliumaddress: String::new(), + pubkey: String::new(), + } + } + + /// Set the user ID (fluent) + pub fn userid(mut self, userid: u32) -> Self { + self.userid = userid; + self + } + + /// Set the notary status (fluent) + pub fn status(mut self, status: NotaryStatus) -> Self { + self.status = status; + self + } + + /// Set the mycelium address (fluent) + pub fn myceliumaddress(mut self, myceliumaddress: impl ToString) -> Self { + self.myceliumaddress = myceliumaddress.to_string(); + self + } + + /// Set the public key (fluent) + pub fn pubkey(mut self, pubkey: impl ToString) -> Self { + self.pubkey = pubkey.to_string(); + self + } + + /// Build the final notary instance + pub fn build(self) -> Self { + self + } +} + + diff --git a/heromodels/src/models/heroledger/signature.rs b/heromodels/src/models/heroledger/signature.rs new file mode 100644 index 0000000..c97cf89 --- /dev/null +++ b/heromodels/src/models/heroledger/signature.rs @@ -0,0 +1,120 @@ +use heromodels_core::{Model, BaseModelData, IndexKey}; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; + +/// Status of a signature +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum SignatureStatus { + Active, + Inactive, + Pending, + Revoked, +} + +impl Default for SignatureStatus { + fn default() -> Self { + SignatureStatus::Pending + } +} + +/// Type of object being signed +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum ObjectType { + Account, + DNSRecord, + Membership, + User, + Transaction, + KYC, +} + +impl Default for ObjectType { + fn default() -> Self { + ObjectType::User + } +} + +/// Represents a cryptographic signature for various objects +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct Signature { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub signature_id: u32, + #[index] + pub user_id: u32, + pub value: String, + #[index] + pub objectid: u32, + pub objecttype: ObjectType, + pub status: SignatureStatus, + pub timestamp: u64, +} + +impl Signature { + /// Create a new signature instance + pub fn new(id: u32) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); + Self { + base_data, + signature_id: 0, + user_id: 0, + value: String::new(), + objectid: 0, + objecttype: ObjectType::default(), + status: SignatureStatus::default(), + timestamp: 0, + } + } + + /// Set the signature ID (fluent) + pub fn signature_id(mut self, signature_id: u32) -> Self { + self.signature_id = signature_id; + self + } + + /// Set the user ID (fluent) + pub fn user_id(mut self, user_id: u32) -> Self { + self.user_id = user_id; + self + } + + /// Set the signature value (fluent) + pub fn value(mut self, value: impl ToString) -> Self { + self.value = value.to_string(); + self + } + + /// Set the object ID (fluent) + pub fn objectid(mut self, objectid: u32) -> Self { + self.objectid = objectid; + self + } + + /// Set the object type (fluent) + pub fn objecttype(mut self, objecttype: ObjectType) -> Self { + self.objecttype = objecttype; + self + } + + /// Set the signature status (fluent) + pub fn status(mut self, status: SignatureStatus) -> Self { + self.status = status; + self + } + + /// Set the timestamp (fluent) + pub fn timestamp(mut self, timestamp: u64) -> Self { + self.timestamp = timestamp; + self + } + + /// Build the final signature instance + pub fn build(self) -> Self { + self + } +} + + diff --git a/heromodels/src/models/heroledger/user.rs b/heromodels/src/models/heroledger/user.rs new file mode 100644 index 0000000..b9d34d3 --- /dev/null +++ b/heromodels/src/models/heroledger/user.rs @@ -0,0 +1,370 @@ +use heromodels_core::{Model, BaseModelData, IndexKey}; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Represents the status of a user in the system +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum UserStatus { + Active, + Inactive, + Suspended, + Archived, +} + +impl Default for UserStatus { + fn default() -> Self { + UserStatus::Active + } +} + +/// Represents the KYC status of a user +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum KYCStatus { + Pending, + Approved, + Rejected, +} + +impl Default for KYCStatus { + fn default() -> Self { + KYCStatus::Pending + } +} + +/// User profile information +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct UserProfile { + pub user_id: u32, + pub full_name: String, + pub bio: String, + pub profile_pic: String, + pub links: HashMap, + pub metadata: HashMap, +} + +impl UserProfile { + pub fn new() -> Self { + Self { + user_id: 0, + full_name: String::new(), + bio: String::new(), + profile_pic: String::new(), + links: HashMap::new(), + metadata: HashMap::new(), + } + } + + pub fn user_id(mut self, user_id: u32) -> Self { + self.user_id = user_id; + self + } + + pub fn full_name(mut self, full_name: impl ToString) -> Self { + self.full_name = full_name.to_string(); + self + } + + pub fn bio(mut self, bio: impl ToString) -> Self { + self.bio = bio.to_string(); + self + } + + pub fn profile_pic(mut self, profile_pic: impl ToString) -> Self { + self.profile_pic = profile_pic.to_string(); + self + } + + pub fn add_link(mut self, key: impl ToString, value: impl ToString) -> Self { + self.links.insert(key.to_string(), value.to_string()); + self + } + + pub fn links(mut self, links: HashMap) -> Self { + self.links = links; + self + } + + pub fn add_metadata(mut self, key: impl ToString, value: impl ToString) -> Self { + self.metadata.insert(key.to_string(), value.to_string()); + self + } + + pub fn metadata(mut self, metadata: HashMap) -> Self { + self.metadata = metadata; + self + } + + pub fn build(self) -> Self { + self + } +} + +/// KYC (Know Your Customer) information for a user +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct KYCInfo { + pub user_id: u32, + pub full_name: String, + pub date_of_birth: u64, + pub address: String, + pub phone_number: String, + pub id_number: String, + pub id_type: String, + pub id_expiry: u64, + pub kyc_status: KYCStatus, + pub kyc_verified: bool, + pub kyc_verified_by: u32, + pub kyc_verified_at: u64, + pub kyc_rejected_reason: String, + pub kyc_signature: u32, + pub metadata: HashMap, +} + +impl KYCInfo { + pub fn new() -> Self { + Self { + user_id: 0, + full_name: String::new(), + date_of_birth: 0, + address: String::new(), + phone_number: String::new(), + id_number: String::new(), + id_type: String::new(), + id_expiry: 0, + kyc_status: KYCStatus::default(), + kyc_verified: false, + kyc_verified_by: 0, + kyc_verified_at: 0, + kyc_rejected_reason: String::new(), + kyc_signature: 0, + metadata: HashMap::new(), + } + } + + pub fn user_id(mut self, user_id: u32) -> Self { + self.user_id = user_id; + self + } + + pub fn full_name(mut self, full_name: impl ToString) -> Self { + self.full_name = full_name.to_string(); + self + } + + pub fn date_of_birth(mut self, date_of_birth: u64) -> Self { + self.date_of_birth = date_of_birth; + self + } + + pub fn address(mut self, address: impl ToString) -> Self { + self.address = address.to_string(); + self + } + + pub fn phone_number(mut self, phone_number: impl ToString) -> Self { + self.phone_number = phone_number.to_string(); + self + } + + pub fn id_number(mut self, id_number: impl ToString) -> Self { + self.id_number = id_number.to_string(); + self + } + + pub fn id_type(mut self, id_type: impl ToString) -> Self { + self.id_type = id_type.to_string(); + self + } + + pub fn id_expiry(mut self, id_expiry: u64) -> Self { + self.id_expiry = id_expiry; + self + } + + pub fn kyc_status(mut self, kyc_status: KYCStatus) -> Self { + self.kyc_status = kyc_status; + self + } + + pub fn kyc_verified(mut self, kyc_verified: bool) -> Self { + self.kyc_verified = kyc_verified; + self + } + + pub fn kyc_verified_by(mut self, kyc_verified_by: u32) -> Self { + self.kyc_verified_by = kyc_verified_by; + self + } + + pub fn kyc_verified_at(mut self, kyc_verified_at: u64) -> Self { + self.kyc_verified_at = kyc_verified_at; + self + } + + pub fn kyc_rejected_reason(mut self, kyc_rejected_reason: impl ToString) -> Self { + self.kyc_rejected_reason = kyc_rejected_reason.to_string(); + self + } + + pub fn kyc_signature(mut self, kyc_signature: u32) -> Self { + self.kyc_signature = kyc_signature; + self + } + + pub fn add_metadata(mut self, key: impl ToString, value: impl ToString) -> Self { + self.metadata.insert(key.to_string(), value.to_string()); + self + } + + pub fn metadata(mut self, metadata: HashMap) -> Self { + self.metadata = metadata; + self + } + + pub fn build(self) -> Self { + self + } +} + +/// Represents a secret box for storing encrypted data +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct SecretBox { + pub data: Vec, + pub nonce: Vec, +} + +impl SecretBox { + pub fn new() -> Self { + Self { + data: Vec::new(), + nonce: Vec::new(), + } + } + + pub fn data(mut self, data: Vec) -> Self { + self.data = data; + self + } + + pub fn nonce(mut self, nonce: Vec) -> Self { + self.nonce = nonce; + self + } + + pub fn build(self) -> Self { + self + } +} + +/// Represents a user in the heroledger system +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct User { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub username: String, + #[index] + pub pubkey: String, + pub email: Vec, + pub status: UserStatus, + pub userprofile: Vec, + pub kyc: Vec, +} + +impl Default for User { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + username: String::new(), + pubkey: String::new(), + email: Vec::new(), + status: UserStatus::default(), + userprofile: Vec::new(), + kyc: Vec::new(), + } + } +} + +impl User { + /// Create a new user instance + pub fn new(id: u32) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); + Self { + base_data, + username: String::new(), + pubkey: String::new(), + email: Vec::new(), + status: UserStatus::default(), + userprofile: Vec::new(), + kyc: Vec::new(), + } + } + + /// Get the user ID + pub fn id(&self) -> u32 { + self.base_data.id + } + + /// Set the username (fluent) + pub fn username(mut self, username: impl ToString) -> Self { + self.username = username.to_string(); + self + } + + /// Set the public key (fluent) + pub fn pubkey(mut self, pubkey: impl ToString) -> Self { + self.pubkey = pubkey.to_string(); + self + } + + /// Add an email address (fluent) + pub fn add_email(mut self, email: impl ToString) -> Self { + self.email.push(email.to_string()); + self + } + + /// Set all email addresses (fluent) + pub fn email(mut self, email: Vec) -> Self { + self.email = email; + self + } + + /// Set the user status (fluent) + pub fn status(mut self, status: UserStatus) -> Self { + self.status = status; + self + } + + /// Add a user profile secret box (fluent) + pub fn add_userprofile(mut self, profile: SecretBox) -> Self { + self.userprofile.push(profile); + self + } + + /// Set all user profile secret boxes (fluent) + pub fn userprofile(mut self, userprofile: Vec) -> Self { + self.userprofile = userprofile; + self + } + + /// Add a KYC secret box (fluent) + pub fn add_kyc(mut self, kyc: SecretBox) -> Self { + self.kyc.push(kyc); + self + } + + /// Set all KYC secret boxes (fluent) + pub fn kyc(mut self, kyc: Vec) -> Self { + self.kyc = kyc; + self + } + + /// Build the final user instance + pub fn build(self) -> Self { + self + } +} + + diff --git a/heromodels/src/models/heroledger/user_kvs.rs b/heromodels/src/models/heroledger/user_kvs.rs new file mode 100644 index 0000000..da64ecf --- /dev/null +++ b/heromodels/src/models/heroledger/user_kvs.rs @@ -0,0 +1,120 @@ +use heromodels_core::{Model, BaseModelData, IndexKey}; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; +use super::secretbox::SecretBox; + +/// Represents a per-user key-value store +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct UserKVS { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub userid: u32, + pub name: String, +} + +impl UserKVS { + /// Create a new user KVS instance + pub fn new(id: u32) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); + Self { + base_data, + userid: 0, + name: String::new(), + } + } + + /// Set the user ID (fluent) + pub fn userid(mut self, userid: u32) -> Self { + self.userid = userid; + self + } + + /// Set the KVS name (fluent) + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); + self + } + + /// Build the final user KVS instance + pub fn build(self) -> Self { + self + } +} + + + +/// Represents an item in a user's key-value store +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct UserKVSItem { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub userkvs_id: u32, + pub key: String, + pub value: String, + pub secretbox: Vec, + pub timestamp: u64, +} + +impl UserKVSItem { + /// Create a new user KVS item instance + pub fn new(id: u32) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); + Self { + base_data, + userkvs_id: 0, + key: String::new(), + value: String::new(), + secretbox: Vec::new(), + timestamp: 0, + } + } + + /// Set the user KVS ID (fluent) + pub fn userkvs_id(mut self, userkvs_id: u32) -> Self { + self.userkvs_id = userkvs_id; + self + } + + /// Set the key (fluent) + pub fn key(mut self, key: impl ToString) -> Self { + self.key = key.to_string(); + self + } + + /// Set the value (fluent) + pub fn value(mut self, value: impl ToString) -> Self { + self.value = value.to_string(); + self + } + + /// Add a secret box (fluent) + pub fn add_secretbox(mut self, secretbox: SecretBox) -> Self { + self.secretbox.push(secretbox); + self + } + + /// Set all secret boxes (fluent) + pub fn secretbox(mut self, secretbox: Vec) -> Self { + self.secretbox = secretbox; + self + } + + /// Set the timestamp (fluent) + pub fn timestamp(mut self, timestamp: u64) -> Self { + self.timestamp = timestamp; + self + } + + /// Build the final user KVS item instance + pub fn build(self) -> Self { + self + } +} + + diff --git a/heromodels/src/models/mod.rs b/heromodels/src/models/mod.rs index 305777b..b1cd40f 100644 --- a/heromodels/src/models/mod.rs +++ b/heromodels/src/models/mod.rs @@ -10,6 +10,7 @@ pub mod contact; pub mod finance; pub mod flow; pub mod governance; +pub mod heroledger; pub mod legal; pub mod library; pub mod object; From 0cffda37a7816dc5f921aead30b2ed391f2de586 Mon Sep 17 00:00:00 2001 From: Maxime Van Hees Date: Tue, 5 Aug 2025 13:00:09 +0200 Subject: [PATCH 3/8] fixed dependencies issues --- Cargo.lock | 2056 ++++++++++++++++++++++++ Cargo.toml | 9 + heromodels/Cargo.toml | 1 - heromodels/src/models/object/object.rs | 3 +- 4 files changed, 2066 insertions(+), 3 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ebd6a16 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2056 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "ethnum" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "heromodels" +version = "0.1.0" +dependencies = [ + "bincode", + "chrono", + "heromodels-derive", + "heromodels_core", + "jsonb", + "ourdb", + "postgres", + "r2d2", + "r2d2_postgres", + "rhai", + "serde", + "serde_json", + "strum", + "strum_macros", + "tokio", + "tst", + "uuid", +] + +[[package]] +name = "heromodels-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "heromodels_core" +version = "0.1.0" +dependencies = [ + "chrono", + "serde", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", + "windows-sys 0.59.0", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonb" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cbb4fba292867a2d86ed83dbe5f9d036f423bf6a491b7d884058b2fde42fcd" +dependencies = [ + "byteorder", + "ethnum", + "fast-float2", + "itoa", + "jiff", + "nom", + "num-traits", + "ordered-float", + "rand 0.9.2", + "ryu", + "serde", + "serde_json", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" +dependencies = [ + "spin", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "ordered-float" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ourdb" +version = "0.1.0" +dependencies = [ + "crc32fast", + "criterion", + "log", + "rand 0.8.5", + "tempfile", + "thiserror", +] + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "postgres" +version = "0.19.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e6dfbdd780d3aa3597b6eb430db76bb315fa9bad7fae595bb8def808b8470" +dependencies = [ + "bytes", + "fallible-iterator", + "futures-util", + "log", + "tokio", + "tokio-postgres", +] + +[[package]] +name = "postgres-protocol" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" +dependencies = [ + "base64", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand 0.9.2", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", + "serde", + "serde_json", +] + +[[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 = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + +[[package]] +name = "r2d2_postgres" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd4b47636dbca581cd057e2f27a5d39be741ea4f85fd3c29e415c55f71c7595" +dependencies = [ + "postgres", + "r2d2", +] + +[[package]] +name = "radixtree" +version = "0.1.0" +dependencies = [ + "criterion", + "log", + "ourdb", + "tempfile", + "thiserror", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[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 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rhai" +version = "1.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2780e813b755850e50b178931aaf94ed24f6817f46aaaf5d21c13c12d939a249" +dependencies = [ + "ahash", + "bitflags", + "instant", + "no-std-compat", + "num-traits", + "once_cell", + "rhai_codegen", + "rust_decimal", + "smallvec", + "smartstring", + "thin-vec", +] + +[[package]] +name = "rhai_codegen" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rust_decimal" +version = "1.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" +dependencies = [ + "arrayvec", + "num-traits", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thin-vec" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" + +[[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 = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2 0.6.0", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-postgres" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand 0.9.2", + "socket2 0.5.10", + "tokio", + "tokio-util", + "whoami", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tst" +version = "0.1.0" +dependencies = [ + "ourdb", + "thiserror", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +dependencies = [ + "redox_syscall", + "wasite", + "web-sys", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..36b705e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] +members = [ + "heromodels", + "heromodels_core", + "heromodels-derive", + "ourdb", + "radixtree", + "tst", +] \ No newline at end of file diff --git a/heromodels/Cargo.toml b/heromodels/Cargo.toml index 57e5651..161398a 100644 --- a/heromodels/Cargo.toml +++ b/heromodels/Cargo.toml @@ -14,7 +14,6 @@ ourdb = { path = "../ourdb" } tst = { path = "../tst" } heromodels-derive = { path = "../heromodels-derive" } heromodels_core = { path = "../heromodels_core" } -rhailib_derive = { package = "derive", path = "../../rhailib/src/derive" } rhai = { version = "1.21.0", features = [ "std", "sync", diff --git a/heromodels/src/models/object/object.rs b/heromodels/src/models/object/object.rs index 88c4228..b80d8d8 100644 --- a/heromodels/src/models/object/object.rs +++ b/heromodels/src/models/object/object.rs @@ -2,12 +2,11 @@ use heromodels_core::BaseModelData; use heromodels_derive::model; use rhai::CustomType; use rhai::TypeBuilder; -use rhailib_derive::RhaiApi; use serde::{Deserialize, Serialize}; /// Represents an event in a contact #[model] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default, RhaiApi)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] pub struct Object { /// Base model data pub base_data: BaseModelData, From 05259db53dfe7f94e1a0b4983b7d2221f77163ea Mon Sep 17 00:00:00 2001 From: despiegk Date: Wed, 6 Aug 2025 13:44:21 +0200 Subject: [PATCH 4/8] ... --- do.sql | 246 +++++++++++++++++++++++++++ specs/models_heroledger/main/money.v | 16 +- 2 files changed, 254 insertions(+), 8 deletions(-) create mode 100644 do.sql diff --git a/do.sql b/do.sql new file mode 100644 index 0000000..9def15d --- /dev/null +++ b/do.sql @@ -0,0 +1,246 @@ +-- -------------------------------------------------------------- +-- do.sql – create tables for HeroLedger models (PostgreSQL) +-- -------------------------------------------------------------- +BEGIN; + +-- 1. DNSZONE +CREATE TABLE dnszone ( + id SERIAL PRIMARY KEY, + created BIGINT, + updated BIGINT, + deleted BOOLEAN, + version INTEGER, + domain TEXT, -- @[index] + administrators INTEGER[], -- array of user ids + status TEXT, + metadata JSONB, + soarecord JSONB, -- store array of SOARecord structs as JSONB + data JSONB NOT NULL +); +CREATE INDEX idx_dnszone_domain ON dnszone(domain); + +-- 2. DNSRECORD +CREATE TABLE dnsrecord ( + id SERIAL PRIMARY KEY, + created BIGINT, + updated BIGINT, + deleted BOOLEAN, + version INTEGER, + subdomain TEXT, + record_type TEXT, + value TEXT, + priority INTEGER, + ttl INTEGER, + is_active BOOLEAN, + cat TEXT, + is_wildcard BOOLEAN, + data JSONB NOT NULL +); +-- No explicit index required – rarely queried alone + +-- 3. GROUP +CREATE TABLE "group" ( + id SERIAL PRIMARY KEY, + created BIGINT, + updated BIGINT, + deleted BOOLEAN, + version INTEGER, + name TEXT NOT NULL, + description TEXT, + dnsrecords INTEGER[], -- FK → dnsrecord.id (array) + administrators INTEGER[], + config JSONB, -- embedded GroupConfig struct + status TEXT, + visibility TEXT, + created_ts BIGINT, + updated_ts BIGINT, + data JSONB NOT NULL +); +CREATE UNIQUE INDEX idx_group_name ON "group"(name); + +-- 4. USER_GROUP_MEMBERSHIP +CREATE TABLE user_group_membership ( + id SERIAL PRIMARY KEY, + created BIGINT, + updated BIGINT, + deleted BOOLEAN, + version INTEGER, + user_id INTEGER NOT NULL, + group_ids INTEGER[], -- array of group ids + data JSONB NOT NULL +); +CREATE INDEX idx_ugm_user_id ON user_group_membership(user_id); +CREATE INDEX idx_ugm_group_ids ON user_group_membership USING GIN (group_ids); + +-- 5. MEMBER (circle/member.v) +CREATE TABLE member ( + id SERIAL PRIMARY KEY, + created BIGINT, + updated BIGINT, + deleted BOOLEAN, + version INTEGER, + user_id INTEGER NOT NULL, + role TEXT, + status TEXT, + joined_at BIGINT, + invited_by INTEGER, + permissions TEXT[], + data JSONB NOT NULL +); +CREATE INDEX idx_member_user_id ON member(user_id); + +-- 6. ACCOUNT +CREATE TABLE account ( + id SERIAL PRIMARY KEY, + created BIGINT, + updated BIGINT, + deleted BOOLEAN, + version INTEGER, + owner_id INTEGER, + address TEXT NOT NULL, + balance DOUBLE PRECISION, + currency TEXT, + assetid INTEGER, + last_activity BIGINT, + administrators INTEGER[], + accountpolicy INTEGER, + data JSONB NOT NULL +); +CREATE UNIQUE INDEX idx_account_address ON account(address); +CREATE INDEX idx_account_assetid ON account(assetid); + +-- 7. ASSET +CREATE TABLE asset ( + id SERIAL PRIMARY KEY, + created BIGINT, + updated BIGINT, + deleted BOOLEAN, + version INTEGER, + address TEXT NOT NULL, + assetid INTEGER NOT NULL, + asset_type TEXT, + issuer INTEGER, + supply DOUBLE PRECISION, + decimals SMALLINT, + is_frozen BOOLEAN, + metadata JSONB, + administrators INTEGER[], + min_signatures INTEGER, + data JSONB NOT NULL +); +CREATE UNIQUE INDEX idx_asset_address ON asset(address); +CREATE UNIQUE INDEX idx_asset_assetid ON asset(assetid); +CREATE INDEX idx_asset_issuer ON asset(issuer); + +-- 8. ACCOUNT_POLICY (holds three AccountPolicyItem JSONB blobs) +CREATE TABLE account_policy ( + id SERIAL PRIMARY KEY, + created BIGINT, + updated BIGINT, + deleted BOOLEAN, + version INTEGER, + transferpolicy JSONB, + adminpolicy JSONB, + clawbackpolicy JSONB, + freezepolicy JSONB, + data JSONB NOT NULL +); + +-- 9. ACCOUNT_POLICY_ITEM (stand‑alone if you ever need a table) +-- (optional – we store it as JSONB inside account_policy, so not created) + +-- 10. TRANSACTION +CREATE TABLE transaction ( + id SERIAL PRIMARY KEY, + created BIGINT, + updated BIGINT, + deleted BOOLEAN, + version INTEGER, + txid INTEGER NOT NULL, + source INTEGER, + destination INTEGER, + assetid INTEGER, + amount DOUBLE PRECISION, + timestamp BIGINT, + status TEXT, + memo TEXT, + tx_type TEXT, + signatures JSONB, -- array of Signature JSON objects + data JSONB NOT NULL +); +CREATE UNIQUE INDEX idx_transaction_txid ON transaction(txid); +CREATE INDEX idx_transaction_source ON transaction(source); +CREATE INDEX idx_transaction_destination ON transaction(destination); +CREATE INDEX idx_transaction_assetid ON transaction(assetid); + +-- 11. SIGNATURE +CREATE TABLE signature ( + id SERIAL PRIMARY KEY, + created BIGINT, + updated BIGINT, + deleted BOOLEAN, + version INTEGER, + signature_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + value TEXT, + objectid INTEGER, + objecttype TEXT, + status TEXT, + timestamp BIGINT, + data JSONB NOT NULL +); +CREATE INDEX idx_signature_signature_id ON signature(signature_id); +CREATE INDEX idx_signature_user_id ON signature(user_id); +CREATE INDEX idx_signature_objectid ON signature(objectid); + +-- 12. USER_KVS +CREATE TABLE user_kvs ( + id SERIAL PRIMARY KEY, + created BIGINT, + updated BIGINT, + deleted BOOLEAN, + version INTEGER, + userid INTEGER NOT NULL, + name TEXT, + data JSONB NOT NULL +); +CREATE INDEX idx_userkvs_userid ON user_kvs(userid); + +-- 13. USER_KVS_ITEM +CREATE TABLE user_kvs_item ( + id SERIAL PRIMARY KEY, + created BIGINT, + updated BIGINT, + deleted BOOLEAN, + version INTEGER, + userkvs_id INTEGER NOT NULL, + key TEXT NOT NULL, + value TEXT, + secretbox JSONB, + timestamp BIGINT, + data JSONB NOT NULL +); +CREATE INDEX idx_userkvs_item_userkvs_id ON user_kvs_item(userkvs_id); +CREATE INDEX idx_userkvs_item_key ON user_kvs_item(key); + +-- 14. USER +CREATE TABLE "user" ( + id SERIAL PRIMARY KEY, + created BIGINT, + updated BIGINT, + deleted BOOLEAN, + version INTEGER, + username TEXT NOT NULL, + pubkey TEXT NOT NULL, + email TEXT[] NOT NULL, + status TEXT, + userprofile JSONB, + kyc JSONB, + data JSONB NOT NULL +); +CREATE UNIQUE INDEX idx_user_username ON "user"(username); +CREATE UNIQUE INDEX idx_user_pubkey ON "user"(pubkey); +-- Email array index – use GIN for fast containment queries +CREATE INDEX idx_user_email ON "user" USING GIN (email); + +COMMIT; \ No newline at end of file diff --git a/specs/models_heroledger/main/money.v b/specs/models_heroledger/main/money.v index e86796d..143edb5 100644 --- a/specs/models_heroledger/main/money.v +++ b/specs/models_heroledger/main/money.v @@ -10,7 +10,7 @@ pub mut: address string // Blockchain address for this wallet @[index] balance f64 // Current balance in the wallet currency string // Currency type (e.g., "USD", "BTC", "ETH") - assetid u32 + assetid u32 @[index] last_activity u64 // Unix timestamp of last transaction administrators []u32 // List of user IDs who are super admins, they have all rights, without any treashold accountpolicy u32 // Policy for signing transactions, 0 means none @@ -21,9 +21,9 @@ pub struct Asset { core.Base pub mut: address string // Blockchain address for this asset @[index] - assetid u32 // Unique identifier for the asset (e.g., "USD", "BTC", "ETH") + assetid u32 @[index] // Unique identifier for the asset (e.g., "USD", "BTC", "ETH") asset_type string // "fiat", "crypto", "stablecoin", etc. - issuer u32 // Issuer account + issuer u32 @[index] // Issuer account supply f64 // Total circulating supply decimals u8 // Decimal precision (e.g., 2 for cents) is_frozen bool // Whether the asset is frozen globally @@ -50,7 +50,7 @@ pub mut: min_signatures u32 // Minimum number of signatures required for a transaction enabled bool // Whether clawback is enabled for this account threshold f64 // Threshold amount for triggering clawback - recipient u32 // Account ID of the recipient for clawback funds + recipient u32 @[index] // Account ID of the recipient for clawback funds } pub enum AccountStatus { @@ -65,10 +65,10 @@ pub enum AccountStatus { pub struct Transaction { core.Base pub mut: - txid u32 // Unique identifier for the transaction - source u32 - destination u32 - assetid u32 + txid u32 @[index] // Unique identifier for the transaction + source u32 @[index] + destination u32 @[index] + assetid u32 @[index] amount f64 timestamp u64 status string // pending, confirmed, failed From 993fa2adcdee4fa60c2c1f78a4793e3c2a842071 Mon Sep 17 00:00:00 2001 From: despiegk Date: Fri, 8 Aug 2025 08:53:49 +0200 Subject: [PATCH 5/8] ... --- specs/models_threefold/aiinstruct.md | 7 ++++++ specs/models_threefold/core | 1 + specs/models_threefold/main/secretbox.v | 31 ++++++++++++++++++++++++ specs/models_threefold/main/signature.v | 32 +++++++++++++++++++++++++ specs/models_threefold/main/user.v | 29 ++++++++++++++++++++++ specs/models_threefold/main/user_kvs.v | 22 +++++++++++++++++ 6 files changed, 122 insertions(+) create mode 100644 specs/models_threefold/aiinstruct.md create mode 120000 specs/models_threefold/core create mode 100644 specs/models_threefold/main/secretbox.v create mode 100644 specs/models_threefold/main/signature.v create mode 100644 specs/models_threefold/main/user.v create mode 100644 specs/models_threefold/main/user_kvs.v diff --git a/specs/models_threefold/aiinstruct.md b/specs/models_threefold/aiinstruct.md new file mode 100644 index 0000000..4075370 --- /dev/null +++ b/specs/models_threefold/aiinstruct.md @@ -0,0 +1,7 @@ +make SQL script to populate DB + +only models with base class are put in tables + +the data itself is in data field + +the fields marked with @index go as separate fields in tables \ No newline at end of file diff --git a/specs/models_threefold/core b/specs/models_threefold/core new file mode 120000 index 0000000..6f98b7c --- /dev/null +++ b/specs/models_threefold/core @@ -0,0 +1 @@ +../models_heroledger/core \ No newline at end of file diff --git a/specs/models_threefold/main/secretbox.v b/specs/models_threefold/main/secretbox.v new file mode 100644 index 0000000..9f23887 --- /dev/null +++ b/specs/models_threefold/main/secretbox.v @@ -0,0 +1,31 @@ +module main + +pub struct SecretBox { +pub mut: + notary_id u32 // person who is allowed to decrypt this info + value string //the actual incrypted value + version u16 //version of the schema used to encrypt this value + timestamp u64 + cat SecretBoxCategory //category of the secret box, e.g. profile +} + +pub enum SecretBoxCategory { + profile +} + +pub struct Notary { + core.Base +pub mut: + userid u32 // Reference to the user entity @[index] + status NotaryStatus // Current user status + myceliumaddress string // Mycelium address of the notary + pubkey string // Public key for cryptographic operations @[index] +} + +pub enum NotaryStatus { + active + inactive + suspended + archived + error +} diff --git a/specs/models_threefold/main/signature.v b/specs/models_threefold/main/signature.v new file mode 100644 index 0000000..7eb627b --- /dev/null +++ b/specs/models_threefold/main/signature.v @@ -0,0 +1,32 @@ +module circle + +import freeflowuniverse.herolib.hero.models.core + +// Wallet represents a wallet associated with a circle for financial operations +pub struct Signature { + core.Base +pub mut: + signature_id u32 // Reference to the user who created the signature @[index] + user_id u32 // Reference to the user who created the signature @[index] + value string // The actual signature value + objectid u32 // Reference to the user who created the signature @[index] + objecttype ObjectType // Type of object being signed (e.g., + status SignatureStatus + timestamp u64 +} + +pub enum SignatureStatus { + active + inactive + pending + revoked +} + +pub enum ObjectType { + account + dnsrecord + membership + user + transaction + kyc +} \ No newline at end of file diff --git a/specs/models_threefold/main/user.v b/specs/models_threefold/main/user.v new file mode 100644 index 0000000..1dcd811 --- /dev/null +++ b/specs/models_threefold/main/user.v @@ -0,0 +1,29 @@ +module circle + +import freeflowuniverse.herolib.hero.models.core + +//is a user in the system, most of info is in models_heroledger + + +pub struct User { + core.Base +pub mut: + username string // Unique username for the user @[index] + pubkey string // Public key for cryptographic operations @[index] + status UserStatus // Current user status + kyc KYCStatus // Know Your Customer status + } + + +pub enum UserStatus { + active + inactive + suspended + archived +} + +pub enum KYCStatus { + pending + approved + rejected +} \ No newline at end of file diff --git a/specs/models_threefold/main/user_kvs.v b/specs/models_threefold/main/user_kvs.v new file mode 100644 index 0000000..7c072ed --- /dev/null +++ b/specs/models_threefold/main/user_kvs.v @@ -0,0 +1,22 @@ +module circle + +import freeflowuniverse.herolib.hero.models.core + +//a per user db +pub struct UserKVS { + core.Base +pub mut: + userid u32 // Reference to the user entity @[index] + name string // Name of the key-value store + } + +pub struct UserKVSItem { + core.Base +pub mut: + userkvs_id u32 // Reference to the user entity @[index] + key string + value string // Value associated with the key + secretbox []SecretBox // Optional secret boxes for sensitive data + timestamp u64 // Timestamp when the item was created or last updated + } + From fc7e327f071d373f545baf5512430682147a7943 Mon Sep 17 00:00:00 2001 From: despiegk Date: Fri, 8 Aug 2025 09:42:47 +0200 Subject: [PATCH 6/8] ... --- specs/models_marketplace/core/base.v | 11 + specs/models_marketplace/core/mod.v | 1 + specs/models_marketplace/main/config.v | 23 + specs/models_marketplace/main/currency.v | 64 ++ specs/models_marketplace/main/order.v | 115 ++++ specs/models_marketplace/main/pool.v | 93 +++ specs/models_marketplace/main/product.v | 177 ++++++ specs/models_marketplace/main/user.v | 730 +++++++++++++++++++++++ specs/models_marketplace/mod.v | 4 + 9 files changed, 1218 insertions(+) create mode 100644 specs/models_marketplace/core/base.v create mode 100644 specs/models_marketplace/core/mod.v create mode 100644 specs/models_marketplace/main/config.v create mode 100644 specs/models_marketplace/main/currency.v create mode 100644 specs/models_marketplace/main/order.v create mode 100644 specs/models_marketplace/main/pool.v create mode 100644 specs/models_marketplace/main/product.v create mode 100644 specs/models_marketplace/main/user.v create mode 100644 specs/models_marketplace/mod.v diff --git a/specs/models_marketplace/core/base.v b/specs/models_marketplace/core/base.v new file mode 100644 index 0000000..b7f04d7 --- /dev/null +++ b/specs/models_marketplace/core/base.v @@ -0,0 +1,11 @@ +module core + +// BaseData provides common fields for all models +pub struct Base { +pub mut: + id u32 + created u64 // Unix timestamp of creation + updated u64 // Unix timestamp of last update + deleted bool + version u32 +} diff --git a/specs/models_marketplace/core/mod.v b/specs/models_marketplace/core/mod.v new file mode 100644 index 0000000..1566dd2 --- /dev/null +++ b/specs/models_marketplace/core/mod.v @@ -0,0 +1 @@ +module core diff --git a/specs/models_marketplace/main/config.v b/specs/models_marketplace/main/config.v new file mode 100644 index 0000000..361fa90 --- /dev/null +++ b/specs/models_marketplace/main/config.v @@ -0,0 +1,23 @@ +module main + +import freeflowuniverse.herolib.hero.models.marketplace.core + + +pub struct MarketplaceCurrencyConfig { +pub mut: + base_currency string + supported_currencies []string + default_display_currency string + auto_update_rates bool + update_interval_minutes u32 + fallback_rates map[string]f64 // Using f64 for Decimal +} + + +// User currency preferences +pub struct UserCurrencyPreference { +pub mut: + user_id string + preferred_currency string + updated_at u64 // Unix timestamp +} diff --git a/specs/models_marketplace/main/currency.v b/specs/models_marketplace/main/currency.v new file mode 100644 index 0000000..1c54e89 --- /dev/null +++ b/specs/models_marketplace/main/currency.v @@ -0,0 +1,64 @@ +module main + +import freeflowuniverse.herolib.hero.models.marketplace.core + +// Configurable currency support for any currency type +pub struct Currency { + core.Base +pub mut: + code string // USD, EUR, BTC, ETH, etc. + name string + symbol string + currency_type CurrencyType + exchange_rate_to_base f64 // Using f64 for Decimal + is_base_currency bool + decimal_places u8 + is_active bool +} + +pub enum CurrencyType { + fiat + cryptocurrency + token + points + custom +} + +pub struct Price { +pub mut: + base_amount f64 // Using f64 for Decimal + base_currency string + display_currency string + display_amount f64 // Using f64 for Decimal + formatted_display string + conversion_rate f64 // Using f64 for Decimal + conversion_timestamp u64 // Unix timestamp +} + +pub struct MarketplaceCurrencyConfig { +pub mut: + base_currency string + supported_currencies []string + default_display_currency string + auto_update_rates bool + update_interval_minutes u32 + fallback_rates map[string]f64 // Using f64 for Decimal +} + +// Exchange rate history for tracking changes over time +pub struct ExchangeRateHistory { +pub mut: + from_currency string + to_currency string + rate f64 // Using f64 for Decimal + timestamp u64 // Unix timestamp + provider string +} + +// User currency preferences +pub struct UserCurrencyPreference { +pub mut: + user_id string + preferred_currency string + updated_at u64 // Unix timestamp +} diff --git a/specs/models_marketplace/main/order.v b/specs/models_marketplace/main/order.v new file mode 100644 index 0000000..2bb42af --- /dev/null +++ b/specs/models_marketplace/main/order.v @@ -0,0 +1,115 @@ +module main + +import freeflowuniverse.herolib.hero.models.marketplace.core + +pub struct Order { + core.Base +pub mut: + id string + user_id string + items []OrderItem + subtotal_base f64 // Using f64 for Decimal + total_base f64 // Using f64 for Decimal + base_currency string + currency_used string + currency_total f64 // Using f64 for Decimal + conversion_rate f64 // Using f64 for Decimal + status OrderStatus + payment_method string + payment_details PaymentDetails + billing_address Address + shipping_address Address + notes string + purchase_type PurchaseType + created_at u64 // Unix timestamp + updated_at u64 // Unix timestamp +} + +pub struct OrderItem { +pub mut: + product_id string + product_name string + product_category string + quantity u32 + unit_price_base f64 // Using f64 for Decimal + total_price_base f64 // Using f64 for Decimal + specifications map[string]string // Using map[string]string for HashMap + provider_id string + provider_name string +} + +pub enum OrderStatus { + pending + confirmed + processing + deployed + completed + cancelled + refunded + failed +} + +// Purchase type to distinguish between cart-based and instant purchases +pub enum PurchaseType { + cart + instant +} + +pub struct PaymentDetails { +pub mut: + payment_id string + payment_method PaymentMethod + transaction_id string + payment_status PaymentStatus + payment_timestamp u64 // Unix timestamp + failure_reason string +} + +pub enum PaymentMethod { + credit_card // CreditCard { last_four: String, card_type: String } + bank_transfer // BankTransfer { bank_name: String, account_last_four: String } + cryptocurrency // Cryptocurrency { currency: String, wallet_address: String } + token // Token { token_type: String, wallet_address: String } + mock // Mock { method_name: String } +} + +pub struct Address { +pub mut: + street string + city string + state string + postal_code string + country string + company string +} + +// Shopping Cart Models +pub struct CartItem { +pub mut: + product_id string + quantity u32 + selected_specifications map[string]string // Using map[string]string for HashMap + added_at u64 // Unix timestamp + updated_at u64 // Unix timestamp +} + +pub struct Cart { +pub mut: + user_id string + items []CartItem + session_id string + created_at u64 // Unix timestamp + updated_at u64 // Unix timestamp +} + +// Order summary for display purposes +pub struct OrderSummary { +pub mut: + subtotal f64 // Using f64 for Decimal + tax f64 // Using f64 for Decimal + shipping f64 // Using f64 for Decimal + discount f64 // Using f64 for Decimal + total f64 // Using f64 for Decimal + currency string + item_count u32 +} diff --git a/specs/models_marketplace/main/pool.v b/specs/models_marketplace/main/pool.v new file mode 100644 index 0000000..c43159f --- /dev/null +++ b/specs/models_marketplace/main/pool.v @@ -0,0 +1,93 @@ +module main + +import freeflowuniverse.herolib.hero.models.marketplace.core + +pub struct LiquidityPool { + core.Base +pub mut: + id string + name string + token_a string + token_b string + reserve_a f64 // Using f64 for Decimal + reserve_b f64 // Using f64 for Decimal + exchange_rate f64 // Using f64 for Decimal + liquidity f64 // Using f64 for Decimal + volume_24h f64 // Using f64 for Decimal + fee_percentage f64 // Using f64 for Decimal + status PoolStatus +} + +pub enum PoolStatus { + active + paused + maintenance +} + +pub struct ExchangeRequest { +pub mut: + pool_id string + from_token string + to_token string + amount f64 // Using f64 for Decimal + min_receive f64 // Using f64 for Decimal + slippage_tolerance f64 // Using f64 for Decimal +} + +pub struct ExchangeResponse { +pub mut: + success bool + message string + transaction_id string + from_amount f64 // Using f64 for Decimal + to_amount f64 // Using f64 for Decimal + exchange_rate f64 // Using f64 for Decimal + fee f64 // Using f64 for Decimal +} + +pub struct StakeRequest { +pub mut: + amount f64 // Using f64 for Decimal + duration_months u32 +} + +pub struct StakePosition { + core.Base +pub mut: + id string + user_id string + amount f64 // Using f64 for Decimal + start_date u64 // Unix timestamp + end_date u64 // Unix timestamp + discount_percentage f64 // Using f64 for Decimal + reputation_bonus int + status StakeStatus +} + +pub enum StakeStatus { + active + completed + withdrawn +} + +// Pool analytics data +pub struct PoolAnalytics { +pub mut: + price_history []PricePoint + volume_history []VolumePoint + liquidity_distribution map[string]f64 // Using f64 for Decimal + staking_distribution map[string]int +} + +pub struct PricePoint { +pub mut: + timestamp u64 // Unix timestamp + price f64 // Using f64 for Decimal + volume f64 // Using f64 for Decimal +} + +pub struct VolumePoint { +pub mut: + date string + volume f64 // Using f64 for Decimal +} diff --git a/specs/models_marketplace/main/product.v b/specs/models_marketplace/main/product.v new file mode 100644 index 0000000..cbebdb2 --- /dev/null +++ b/specs/models_marketplace/main/product.v @@ -0,0 +1,177 @@ +module main + +import freeflowuniverse.herolib.hero.models.marketplace.core + +// Generic product structure that can represent any marketplace item +pub struct Product { + core.Base +pub mut: + id string + name string + category_id string // References ProductCategory config + description string + base_price f64 // Using f64 for Decimal + base_currency string + attributes map[string]ProductAttribute // Generic attributes + provider_id string + provider_name string + availability ProductAvailability + metadata ProductMetadata // Extensible metadata + created_at u64 // Unix timestamp + updated_at u64 // Unix timestamp +} + +// Configurable product categories +pub struct ProductCategory { +pub mut: + id string + name string + display_name string + description string + attribute_schema []AttributeDefinition // Defines allowed attributes + parent_category string + is_active bool +} + +// Generic attribute system for any product type +pub struct ProductAttribute { +pub mut: + key string + value string // Using string for serde_json::Value, consider map[string]string or specific types + attribute_type AttributeType + is_searchable bool + is_filterable bool + display_order u32 +} + +pub enum AttributeType { + text + number + slice_configuration + boolean + select // Select(Vec) + multi_select // MultiSelect(Vec) + range // Range { min: f64, max: f64 } + custom +} + +pub struct AttributeDefinition { +pub mut: + key string + name string + attribute_type AttributeType + is_required bool + is_searchable bool + is_filterable bool + validation_rules []ValidationRule +} + +pub enum ValidationRule { + min_length // MinLength(usize) + max_length // MaxLength(usize) + min_value // MinValue(f64) + max_value // MaxValue(f64) + pattern // Pattern(String) + custom +} + +pub enum ProductAvailability { + available + limited + unavailable + pre_order + custom +} + +pub struct ProductMetadata { +pub mut: + tags []string + location string + rating f32 + review_count u32 + featured bool + custom_fields map[string]string // Using map[string]string for HashMap +} + +// Support for different pricing models +pub enum PricingModel { + one_time + recurring // Recurring { interval: String } + usage_based // UsageBased { unit: String } + tiered // Tiered(Vec) + custom +} + +pub struct PriceTier { +pub mut: + min_quantity u32 + max_quantity u32 + price_per_unit f64 // Using f64 for Decimal + discount_percentage f32 +} + +// Slice configuration data structure for product attributes +pub struct SliceConfiguration { +pub mut: + cpu_cores int + memory_gb int + storage_gb int + bandwidth_mbps int + min_uptime_sla f32 + public_ips int + node_id string + slice_type SliceType + pricing SlicePricing +} + +// Enhanced pricing structure for slices with multiple time periods +pub struct SlicePricing { +pub mut: + hourly f64 // Using f64 for Decimal + daily f64 // Using f64 for Decimal + monthly f64 // Using f64 for Decimal + yearly f64 // Using f64 for Decimal +} + +pub enum SliceType { + basic + standard + premium + custom +} + +// Placeholder for SliceAllocation and SliceCombination +// These are not directly from product.rs but are referenced in user.rs +pub struct SliceAllocation { +pub mut: + slice_id string + node_id string + user_id string + allocated_cpu_cores int + allocated_memory_gb int + allocated_storage_gb int + allocated_bandwidth_mbps int + start_time u64 + end_time u64 +} + +pub struct SliceCombination { +pub mut: + cpu_cores int + memory_gb int + storage_gb int + bandwidth_mbps int + price_per_hour f64 +} + +// Placeholder for DefaultSliceFormat +// This is not directly from product.rs but is referenced in user.rs +pub struct DefaultSliceFormat { +pub mut: + name string + cpu_cores int + memory_gb int + storage_gb int + bandwidth_mbps int + price_per_hour f64 +} diff --git a/specs/models_marketplace/main/user.v b/specs/models_marketplace/main/user.v new file mode 100644 index 0000000..3126dc3 --- /dev/null +++ b/specs/models_marketplace/main/user.v @@ -0,0 +1,730 @@ +module main + +import freeflowuniverse.herolib.hero.models.marketplace.core + +// Represents a user in the system +pub struct User { + core.Base +pub mut: + id u32 // Unique identifier for the user, using u32 for consistency with VLang + name string // User's full name + email string // User's email address + role UserRole // User's role in the system + country string // User's country + timezone string // User's timezone + created_at u64 // Unix timestamp of creation + updated_at u64 // Unix timestamp of last update + mock_data MockUserData // Mock data for dashboard +} + +// Represents the possible roles a user can have +pub enum UserRole { + user + admin +} + +// Represents user login credentials +pub struct LoginCredentials { +pub mut: + email string + password string +} + +// Represents user registration data +pub struct RegistrationData { +pub mut: + name string + email string + password string + password_confirmation string +} + +// Mock user data for testing +pub struct MockUserData { +pub mut: + active_deployments int + active_slices int + current_cost int + balance int + wallet_balance_usd f64 // Using f64 for Decimal, consider string for high precision + owned_product_ids []string + active_rentals []string + transaction_history []Transaction + resource_utilization ResourceUtilization + usd_usage_trend []int + user_activity UserActivityStats + recent_activities []RecentActivity + deployment_distribution DeploymentDistribution + farmer_data FarmerData // Farmer-specific data + app_provider_data AppProviderData // App Provider-specific data + service_provider_data ServiceProviderData // Service Provider-specific data + customer_service_data CustomerServiceData // Customer Service-specific data +} + +pub struct ResourceUtilization { +pub mut: + cpu int + memory int + storage int + network int +} + +pub struct UserActivityStats { +pub mut: + deployments []int + resource_reservations []int +} + +pub struct RecentActivity { +pub mut: + date string + action string + status string + details string +} + +pub struct DeploymentDistribution { +pub mut: + regions []RegionDeployments +} + +pub struct RegionDeployments { +pub mut: + region string + nodes int + slices int + apps int + gateways int +} + +// Farmer-specific data +pub struct FarmerData { +pub mut: + total_nodes int + online_nodes int + total_capacity NodeCapacity + used_capacity NodeCapacity + monthly_earnings_usd int + total_earnings_usd int + uptime_percentage f32 + nodes []FarmNode + earnings_history []EarningsRecord + slice_templates []Product + active_slices int +} + +pub struct NodeCapacity { +pub mut: + cpu_cores int + memory_gb int + storage_gb int + bandwidth_mbps int + ssd_storage_gb int + hdd_storage_gb int +} + +// Enhanced Node structure for farmer dashboard with modern types +pub struct FarmNode { +pub mut: + id string + name string + location string + status NodeStatus + capacity NodeCapacity + used_capacity NodeCapacity + uptime_percentage f32 + earnings_today_usd f64 // Using f64 for Decimal + last_seen u64 // Unix timestamp + health_score f32 + region string + node_type string + slice_formats []string + rental_options NodeRentalOptions + availability_status NodeAvailabilityStatus + grid_node_id u32 + grid_data GridNodeData + node_group_id string + group_assignment_date u64 // Unix timestamp + group_slice_format string + group_slice_price f64 // Using f64 for Decimal + staking_options NodeStakingOptions + marketplace_sla MarketplaceSLA + total_base_slices u32 + allocated_base_slices u32 + slice_allocations []SliceAllocation // Assuming SliceAllocation is defined elsewhere or will be translated + available_combinations []SliceCombination // Assuming SliceCombination is defined elsewhere or will be translated + slice_pricing SlicePricing // Assuming SlicePricing is defined elsewhere or will be translated + slice_last_calculated u64 // Unix timestamp +} + +pub struct EarningsRecord { +pub mut: + date string + amount int + source string +} + +pub enum NodeStatus { + online + offline + maintenance + error + standby +} + +pub struct FarmerSettings { +pub mut: + auto_accept_deployments bool + maintenance_window string + notification_preferences NotificationSettings + minimum_deployment_duration int + preferred_regions []string + default_slice_customizations map[string]DefaultSliceFormat // Assuming DefaultSliceFormat is defined elsewhere or will be translated +} + +pub struct NotificationSettings { +pub mut: + email_enabled bool + sms_enabled bool + push bool + node_offline_alerts bool + earnings_reports bool + maintenance_reminders bool +} + +// Marketplace SLA configuration - what the farmer promises to customers +pub struct MarketplaceSLA { +pub mut: + uptime_guarantee_percentage f32 + bandwidth_guarantee_mbps int + base_slice_price f64 // Using f64 for Decimal + last_updated u64 // Unix timestamp +} + +// Node rental options that farmers can configure +pub struct NodeRentalOptions { +pub mut: + slice_rental_enabled bool + full_node_rental_enabled bool + full_node_pricing FullNodePricing + minimum_rental_days u32 + maximum_rental_days u32 + auto_renewal_enabled bool +} + +// Node staking options that farmers can configure +pub struct NodeStakingOptions { +pub mut: + staking_enabled bool + staked_amount f64 // Using f64 for Decimal + staking_start_date u64 // Unix timestamp + staking_period_months u32 + early_withdrawal_allowed bool + early_withdrawal_penalty_percent f32 +} + +// Full node rental pricing configuration with auto-calculation support +pub struct FullNodePricing { +pub mut: + hourly f64 // Using f64 for Decimal + daily f64 // Using f64 for Decimal + monthly f64 // Using f64 for Decimal + yearly f64 // Using f64 for Decimal + auto_calculate bool + daily_discount_percent f32 + monthly_discount_percent f32 + yearly_discount_percent f32 +} + +// Node availability status for rental management +pub enum NodeAvailabilityStatus { + available + partially_rented + fully_rented + unavailable + reserved +} + +// Individual node rental record +pub struct NodeRental { +pub mut: + id string + node_id string + renter_email string + rental_type NodeRentalType + monthly_cost f64 // Using f64 for Decimal + start_date u64 // Unix timestamp + end_date u64 // Unix timestamp + status NodeRentalStatus + auto_renewal bool + payment_method string + metadata map[string]string // Using map[string]string for HashMap +} + +// Type of node rental +pub enum NodeRentalType { + slice // Slice { slice_ids: Vec, total_cpu_cores: u32, total_memory_gb: u32, total_storage_gb: u32 } + full_node +} + +// Status of a node rental +pub enum NodeRentalStatus { + active + pending + expired + cancelled + suspended +} + +// Farmer earnings from node rentals +pub struct FarmerRentalEarning { +pub mut: + id string + node_id string + rental_id string + renter_email string + amount f64 // Using f64 for Decimal + currency string + earning_date u64 // Unix timestamp + rental_type NodeRentalType + payment_status PaymentStatus +} + +pub enum PaymentStatus { + pending + completed + failed + refunded +} + +// User Activity Tracking +pub struct UserActivity { +pub mut: + id string + activity_type ActivityType + description string + timestamp u64 // Unix timestamp + metadata map[string]string // Using map[string]string for HashMap + category string + importance ActivityImportance +} + +pub enum ActivityType { + login + purchase + deployment + service_created + app_published + node_added + node_updated + wallet_transaction + profile_update + settings_change + marketplace_view + slice_created + slice_allocated + slice_released + slice_rental_started + slice_rental_stopped + slice_rental_restarted + slice_rental_cancelled +} + +pub enum ActivityImportance { + low + medium + high + critical +} + +// Enhanced User Statistics +pub struct UsageStatistics { +pub mut: + total_deployments int + active_services int + total_spent f64 // Using f64 for Decimal + favorite_categories []string + usage_trends []UsageTrend + login_frequency f32 + preferred_regions []string + account_age_days int + last_activity u64 // Unix timestamp +} + +pub struct UsageTrend { +pub mut: + period string + metric string + value f32 + change_percentage f32 +} + +pub struct UserPreferences { +pub mut: + preferred_currency string + preferred_language string + timezone string + dashboard_layout string + notification_settings NotificationSettings + privacy_settings PrivacySettings + theme string + last_payment_method string +} + +pub struct PrivacySettings { +pub mut: + profile_visibility string + activity_tracking bool + marketing_emails bool + data_sharing bool +} + +// ThreeFold Grid Node Data fetched from gridproxy/graphql +pub struct GridNodeData { +pub mut: + grid_node_id u32 + city string + country string + farm_name string + farm_id u32 + public_ips u32 + total_resources NodeCapacity + used_resources NodeCapacity + certification_type string + farming_policy_id u32 + last_updated u64 // Unix timestamp +} + +// Node Group for managing multiple nodes together +pub struct NodeGroup { + core.Base +pub mut: + id string + name string + description string + group_type NodeGroupType + node_ids []string + group_config NodeGroupConfig + created_at u64 // Unix timestamp + updated_at u64 // Unix timestamp +} + +// Type of node group - default or custom +pub enum NodeGroupType { + default_compute + default_storage + default_ai_gpu + custom +} + +// Configuration for node groups +pub struct NodeGroupConfig { +pub mut: + preferred_slice_formats []string + default_pricing map[string]f64 // Using f64 for Decimal + resource_optimization ResourceOptimization + auto_scaling bool +} + +// Resource optimization settings for groups +pub enum ResourceOptimization { + balanced + performance + efficiency + custom +} + +// Statistics for a node group +pub struct GroupStatistics { +pub mut: + group_id string + total_nodes int + online_nodes int + total_capacity NodeCapacity + average_uptime f32 + group_type NodeGroupType +} + +// Enhanced User Dashboard Data +pub struct UserDashboardData { +pub mut: + user_info UserInfo + recent_activities []UserActivity + usage_statistics UsageStatistics + active_services []Service // Assuming Service is defined elsewhere or will be translated + active_deployments int + wallet_summary WalletSummary + recommendations []Recommendation + quick_actions []QuickAction +} + +pub struct UserInfo { +pub mut: + name string + email string + member_since string + account_type string + verification_status string +} + +pub struct WalletSummary { +pub mut: + balance f64 // Using f64 for Decimal + currency string + recent_transactions int + pending_transactions int +} + +pub struct Recommendation { +pub mut: + id string + title string + description string + action_url string + priority string + category string +} + +pub struct QuickAction { +pub mut: + id string + title string + description string + action_url string + icon string + enabled bool +} + +// App Provider-specific data +pub struct AppProviderData { +pub mut: + published_apps int + total_deployments int + active_deployments int + monthly_revenue_usd int + total_revenue_usd int + apps []PublishedApp + deployment_stats []DeploymentStat + revenue_history []RevenueRecord +} + +pub struct PublishedApp { +pub mut: + id string + name string + category string + version string + status string + deployments int + rating f32 + monthly_revenue_usd int + last_updated string + auto_healing bool +} + +pub struct DeploymentStat { +pub mut: + app_name string + region string + instances int + status string + resource_usage ResourceUtilization + customer_name string + deployed_date string + deployment_id string + auto_healing bool +} + +pub struct RevenueRecord { +pub mut: + date string + amount int + app_name string +} + +// Service Provider-specific data +pub struct ServiceProviderData { +pub mut: + active_services int + total_clients int + monthly_revenue_usd int + total_revenue_usd int + service_rating f32 + services []Service + client_requests []ServiceRequest + revenue_history []RevenueRecord +} + +pub struct Service { +pub mut: + id string + name string + category string + description string + price_per_hour_usd int + status string + clients int + rating f32 + total_hours int +} + +pub struct ServiceRequest { +pub mut: + id string + client_name string + service_name string + status string + requested_date string + estimated_hours int + budget int + priority string + progress int + completed_date string + client_email string + client_phone string + description string + created_date string +} + +// Service booking record for customers who purchase services +pub struct ServiceBooking { +pub mut: + id string + service_id string + service_name string + provider_email string + customer_email string + budget int + estimated_hours int + status string + requested_date string + priority string + description string + booking_date string + client_phone string + progress int + completed_date string +} + +// Customer Service-specific data (for users who book services) +pub struct CustomerServiceData { +pub mut: + active_bookings int + completed_bookings int + total_spent int + monthly_spending int + average_rating_given f32 + service_bookings []ServiceBooking + favorite_providers []string + spending_history []SpendingRecord +} + +pub struct SpendingRecord { +pub mut: + date string + amount int + service_name string + provider_name string +} + +// Transaction record for wallet operations +pub struct Transaction { +pub mut: + id string + user_id string + transaction_type TransactionType + amount f64 // Using f64 for Decimal + timestamp u64 // Unix timestamp + status TransactionStatus +} + +// Types of transactions +pub enum TransactionType { + purchase + rental + transfer + earning + instant_purchase + exchange + stake + unstake + auto_top_up + credits_purchase + credits_sale + credits_transfer +} + +// Transaction status +pub enum TransactionStatus { + pending + completed + failed + cancelled +} + +// Rental record +pub struct Rental { +pub mut: + id string + user_id string + product_id string + start_date u64 // Unix timestamp + end_date u64 // Unix timestamp + status RentalStatus + monthly_cost f64 // Using f64 for Decimal +} + +// Rental status +pub enum RentalStatus { + active + expired + cancelled + pending +} + +// User deployment information for dashboard +pub struct UserDeployment { +pub mut: + id string + app_name string + status DeploymentStatus + cost_per_month f64 // Using f64 for Decimal + deployed_at u64 // Unix timestamp + provider string + region string + resource_usage ResourceUtilization +} + +// Deployment status enum +pub enum DeploymentStatus { + active + pending + stopped + error + maintenance +} + +// Comprehensive user metrics for dashboard +pub struct UserMetrics { +pub mut: + total_spent_this_month f64 // Using f64 for Decimal + active_deployments_count int + resource_utilization ResourceUtilization + cost_trend []int + wallet_balance f64 // Using f64 for Decimal + total_transactions int +} + +// User compute resource for dashboard display +pub struct UserComputeResource { +pub mut: + id string + resource_type string + specs string + location string + status string + sla string + monthly_cost f64 // Using f64 for Decimal + provider string + resource_usage ResourceUtilization +} diff --git a/specs/models_marketplace/mod.v b/specs/models_marketplace/mod.v new file mode 100644 index 0000000..a2cb557 --- /dev/null +++ b/specs/models_marketplace/mod.v @@ -0,0 +1,4 @@ +module models_marketplace + +import models_marketplace.core +import models_marketplace.main From 6727c7498d219db3a810a300c8e5c06aaf00f287 Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Fri, 8 Aug 2025 09:46:30 +0200 Subject: [PATCH 7/8] add heroledger models --- Cargo.lock | 462 +--------- Cargo.toml | 3 - heromodels/Cargo.toml | 4 +- .../prompts/v_specs_to_rust_heromodels.md | 1 - .../examples/heroledger/heroledger.rhai | 53 ++ heromodels/examples/heroledger/main.rs | 50 ++ heromodels/src/models/heroledger/mod.rs | 1 + heromodels/src/models/heroledger/rhai.rs | 330 +++++++ heromodels/src/models/object/mod.rs | 5 +- .../src/models/object/object_rhai_dsl.rs | 56 ++ ourdb/API.md | 277 ------ ourdb/Cargo.lock | 806 ----------------- ourdb/Cargo.toml | 32 - ourdb/README.md | 135 --- ourdb/architecture.md | 439 ---------- ourdb/examples/advanced_usage.rs | 231 ----- ourdb/examples/basic_usage.rs | 89 -- ourdb/examples/benchmark.rs | 124 --- ourdb/examples/main.rs | 83 -- ourdb/examples/standalone_ourdb_example.rs | 83 -- ourdb/src/backend.rs | 366 -------- ourdb/src/error.rs | 41 - ourdb/src/lib.rs | 293 ------- ourdb/src/location.rs | 178 ---- ourdb/src/lookup.rs | 540 ------------ ourdb/tests/integration_tests.rs | 369 -------- radixtree/ARCHITECTURE.md | 787 ----------------- radixtree/Cargo.lock | 815 ------------------ radixtree/Cargo.toml | 27 - radixtree/MIGRATION.md | 265 ------ radixtree/README.md | 189 ---- radixtree/benches/radixtree_benchmarks.rs | 141 --- radixtree/examples/basic_usage.rs | 51 -- radixtree/examples/large_scale_test.rs | 121 --- radixtree/examples/performance_test.rs | 134 --- radixtree/examples/prefix_operations.rs | 97 --- radixtree/src/error.rs | 35 - radixtree/src/lib.rs | 133 --- radixtree/src/node.rs | 59 -- radixtree/src/operations.rs | 508 ----------- radixtree/src/serialize.rs | 156 ---- radixtree/tests/basic_test.rs | 144 ---- radixtree/tests/getall_test.rs | 153 ---- radixtree/tests/prefix_test.rs | 185 ---- radixtree/tests/serialize_test.rs | 180 ---- tst/Cargo.lock | 179 ---- tst/Cargo.toml | 30 - tst/README.md | 185 ---- tst/examples/basic_usage.rs | 75 -- tst/examples/performance.rs | 167 ---- tst/examples/prefix_ops.rs | 184 ---- tst/src/error.rs | 36 - tst/src/lib.rs | 122 --- tst/src/node.rs | 49 -- tst/src/operations.rs | 453 ---------- tst/src/serialize.rs | 129 --- tst/tests/basic_test.rs | 294 ------- tst/tests/prefix_test.rs | 267 ------ 58 files changed, 507 insertions(+), 10894 deletions(-) create mode 100644 heromodels/examples/heroledger/heroledger.rhai create mode 100644 heromodels/examples/heroledger/main.rs create mode 100644 heromodels/src/models/heroledger/rhai.rs create mode 100644 heromodels/src/models/object/object_rhai_dsl.rs delete mode 100644 ourdb/API.md delete mode 100644 ourdb/Cargo.lock delete mode 100644 ourdb/Cargo.toml delete mode 100644 ourdb/README.md delete mode 100644 ourdb/architecture.md delete mode 100644 ourdb/examples/advanced_usage.rs delete mode 100644 ourdb/examples/basic_usage.rs delete mode 100644 ourdb/examples/benchmark.rs delete mode 100644 ourdb/examples/main.rs delete mode 100644 ourdb/examples/standalone_ourdb_example.rs delete mode 100644 ourdb/src/backend.rs delete mode 100644 ourdb/src/error.rs delete mode 100644 ourdb/src/lib.rs delete mode 100644 ourdb/src/location.rs delete mode 100644 ourdb/src/lookup.rs delete mode 100644 ourdb/tests/integration_tests.rs delete mode 100644 radixtree/ARCHITECTURE.md delete mode 100644 radixtree/Cargo.lock delete mode 100644 radixtree/Cargo.toml delete mode 100644 radixtree/MIGRATION.md delete mode 100644 radixtree/README.md delete mode 100644 radixtree/benches/radixtree_benchmarks.rs delete mode 100644 radixtree/examples/basic_usage.rs delete mode 100644 radixtree/examples/large_scale_test.rs delete mode 100644 radixtree/examples/performance_test.rs delete mode 100644 radixtree/examples/prefix_operations.rs delete mode 100644 radixtree/src/error.rs delete mode 100644 radixtree/src/lib.rs delete mode 100644 radixtree/src/node.rs delete mode 100644 radixtree/src/operations.rs delete mode 100644 radixtree/src/serialize.rs delete mode 100644 radixtree/tests/basic_test.rs delete mode 100644 radixtree/tests/getall_test.rs delete mode 100644 radixtree/tests/prefix_test.rs delete mode 100644 radixtree/tests/serialize_test.rs delete mode 100644 tst/Cargo.lock delete mode 100644 tst/Cargo.toml delete mode 100644 tst/README.md delete mode 100644 tst/examples/basic_usage.rs delete mode 100644 tst/examples/performance.rs delete mode 100644 tst/examples/prefix_ops.rs delete mode 100644 tst/src/error.rs delete mode 100644 tst/src/lib.rs delete mode 100644 tst/src/node.rs delete mode 100644 tst/src/operations.rs delete mode 100644 tst/src/serialize.rs delete mode 100644 tst/tests/basic_test.rs delete mode 100644 tst/tests/prefix_test.rs diff --git a/Cargo.lock b/Cargo.lock index ebd6a16..6409f95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,15 +31,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -55,18 +46,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - -[[package]] -name = "anstyle" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" - [[package]] name = "arrayvec" version = "0.7.6" @@ -102,7 +81,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -164,12 +143,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - [[package]] name = "cc" version = "1.2.31" @@ -200,58 +173,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "clap" -version = "4.5.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" -dependencies = [ - "anstyle", - "clap_lex", -] - -[[package]] -name = "clap_lex" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" - [[package]] name = "const-random" version = "0.1.18" @@ -296,67 +217,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "criterion" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" -dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "is-terminal", - "itertools", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[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.4" @@ -384,28 +244,12 @@ dependencies = [ "subtle", ] -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "errno" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - [[package]] name = "ethnum" version = "1.5.2" @@ -424,12 +268,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - [[package]] name = "futures-channel" version = "0.3.31" @@ -523,16 +361,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "half" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" -dependencies = [ - "cfg-if", - "crunchy", -] - [[package]] name = "hashbrown" version = "0.15.4" @@ -545,12 +373,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - [[package]] name = "heromodels" version = "0.1.0" @@ -655,26 +477,6 @@ dependencies = [ "libc", ] -[[package]] -name = "is-terminal" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.15" @@ -758,12 +560,6 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - [[package]] name = "lock_api" version = "0.4.13" @@ -861,12 +657,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "oorandom" -version = "11.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" - [[package]] name = "ordered-float" version = "5.0.0" @@ -881,10 +671,8 @@ name = "ourdb" version = "0.1.0" dependencies = [ "crc32fast", - "criterion", "log", "rand 0.8.5", - "tempfile", "thiserror", ] @@ -908,7 +696,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -947,34 +735,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "plotters" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] - [[package]] name = "portable-atomic" version = "1.11.1" @@ -1089,17 +849,6 @@ dependencies = [ "r2d2", ] -[[package]] -name = "radixtree" -version = "0.1.0" -dependencies = [ - "criterion", - "log", - "ourdb", - "tempfile", - "thiserror", -] - [[package]] name = "rand" version = "0.8.5" @@ -1159,26 +908,6 @@ dependencies = [ "getrandom 0.3.3", ] -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" version = "0.5.17" @@ -1188,35 +917,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - [[package]] name = "rhai" version = "1.22.2" @@ -1263,19 +963,6 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" -[[package]] -name = "rustix" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.60.2", -] - [[package]] name = "rustversion" version = "1.0.21" @@ -1288,15 +975,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "scheduled-thread-pool" version = "0.2.7" @@ -1479,19 +1157,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tempfile" -version = "3.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - [[package]] name = "thin-vec" version = "0.2.14" @@ -1527,16 +1192,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "tinyvec" version = "1.9.0" @@ -1692,16 +1347,6 @@ version = "0.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1802,15 +1447,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "windows-core" version = "0.61.2" @@ -1876,7 +1512,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1885,16 +1521,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.3", + "windows-targets", ] [[package]] @@ -1903,31 +1530,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -1936,96 +1546,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/Cargo.toml b/Cargo.toml index 36b705e..313c621 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,4 @@ members = [ "heromodels", "heromodels_core", "heromodels-derive", - "ourdb", - "radixtree", - "tst", ] \ No newline at end of file diff --git a/heromodels/Cargo.toml b/heromodels/Cargo.toml index 161398a..99be424 100644 --- a/heromodels/Cargo.toml +++ b/heromodels/Cargo.toml @@ -10,8 +10,8 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" bincode = { version = "2", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] } -ourdb = { path = "../ourdb" } -tst = { path = "../tst" } +ourdb = { path = "../../herolib_rust/packages/data/ourdb" } +tst = { path = "../../herolib_rust/packages/data/tst" } heromodels-derive = { path = "../heromodels-derive" } heromodels_core = { path = "../heromodels_core" } rhai = { version = "1.21.0", features = [ diff --git a/heromodels/docs/prompts/v_specs_to_rust_heromodels.md b/heromodels/docs/prompts/v_specs_to_rust_heromodels.md index 2f97961..4b2c958 100644 --- a/heromodels/docs/prompts/v_specs_to_rust_heromodels.md +++ b/heromodels/docs/prompts/v_specs_to_rust_heromodels.md @@ -52,7 +52,6 @@ pub mut: use heromodels_core::{Model, BaseModelData, IndexKey}; use heromodels_derive::model; use rhai::CustomType; -use rhailib_derive::RhaiApi; use serde::{Deserialize, Serialize}; use chrono::{DateTime, Utc}; ``` diff --git a/heromodels/examples/heroledger/heroledger.rhai b/heromodels/examples/heroledger/heroledger.rhai new file mode 100644 index 0000000..3d4c72f --- /dev/null +++ b/heromodels/examples/heroledger/heroledger.rhai @@ -0,0 +1,53 @@ +// heroledger.rhai - Demonstration of HeroLedger models in Rhai + +print("=== HeroLedger Models Demo ==="); + +// Create a new user +print("\n--- Creating User ---"); +let new_user = new_user() + .name("Alice Johnson") + .email("alice@herocode.com") + .pubkey("0x1234567890abcdef") + .status("Active") + .save_user(); + +print("Created user: " + new_user.get_name()); +print("User ID: " + new_user.get_id()); +print("User email: " + new_user.get_email()); +print("User pubkey: " + new_user.get_pubkey()); + +// Create a new group +print("\n--- Creating Group ---"); +let new_group = new_group() + .name("HeroCode Developers") + .description("A group for HeroCode development team members") + .visibility("Public") + .save_group(); + +print("Created group: " + new_group.get_name()); +print("Group ID: " + new_group.get_id()); +print("Group description: " + new_group.get_description()); + +// Create a new account +print("\n--- Creating Account ---"); +let new_account = new_account() + .name("Alice's Main Account") + .description("Primary account for Alice Johnson") + .currency("USD") + .save_account(); + +print("Created account: " + new_account.get_name()); +print("Account ID: " + new_account.get_id()); +print("Account currency: " + new_account.get_currency()); + +// Create a new DNS zone +print("\n--- Creating DNS Zone ---"); +let new_dns_zone = new_dns_zone() + .name("herocode.com") + .description("Main domain for HeroCode") + .save_dns_zone(); + +print("Created DNS zone: " + new_dns_zone.get_name()); +print("DNS zone ID: " + new_dns_zone.get_id()); + +print("\n=== Demo Complete ==="); diff --git a/heromodels/examples/heroledger/main.rs b/heromodels/examples/heroledger/main.rs new file mode 100644 index 0000000..0c28941 --- /dev/null +++ b/heromodels/examples/heroledger/main.rs @@ -0,0 +1,50 @@ +use heromodels_core::db::hero::OurDB; +use rhai::{Dynamic, Engine}; +use heromodels::models::heroledger::rhai::register_heroledger_rhai_modules; +use std::sync::Arc; +use std::{fs, path::Path}; + +const CALLER_ID: &str = "example_caller"; + +fn main() -> Result<(), Box> { + // Initialize Rhai engine + let mut engine = Engine::new(); + + // Initialize database with OurDB + let db_path = "temp_heroledger_db"; + // Clean up previous database file if it exists + if Path::new(db_path).exists() { + fs::remove_dir_all(db_path)?; + } + let _db = Arc::new(OurDB::new(db_path, true).expect("Failed to create database")); + + // Register the heroledger modules with Rhai + register_heroledger_rhai_modules(&mut engine); + + let mut db_config = rhai::Map::new(); + db_config.insert("DB_PATH".into(), db_path.into()); + db_config.insert("CALLER_ID".into(), CALLER_ID.into()); + db_config.insert("CONTEXT_ID".into(), CALLER_ID.into()); + engine.set_default_tag(Dynamic::from(db_config)); // Or pass via CallFnOptions + + // Load and evaluate the Rhai script + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let script_path = Path::new(manifest_dir) + .join("examples") + .join("heroledger") + .join("heroledger.rhai"); + println!("Script path: {}", script_path.display()); + let script = fs::read_to_string(&script_path)?; + + println!("--- Running HeroLedger Rhai Script ---"); + match engine.eval::<()>(&script) { + Ok(_) => println!("\n--- Script executed successfully! ---"), + Err(e) => eprintln!("\n--- Script execution failed: {} ---", e), + } + + // Clean up the database file + fs::remove_dir_all(db_path)?; + println!("--- Cleaned up temporary database. ---"); + + Ok(()) +} diff --git a/heromodels/src/models/heroledger/mod.rs b/heromodels/src/models/heroledger/mod.rs index 4237870..3b1f795 100644 --- a/heromodels/src/models/heroledger/mod.rs +++ b/heromodels/src/models/heroledger/mod.rs @@ -7,6 +7,7 @@ pub mod dnsrecord; pub mod secretbox; pub mod signature; pub mod user_kvs; +pub mod rhai; // Re-export key types for convenience pub use user::{User, UserStatus, UserProfile, KYCInfo, KYCStatus, SecretBox}; diff --git a/heromodels/src/models/heroledger/rhai.rs b/heromodels/src/models/heroledger/rhai.rs new file mode 100644 index 0000000..28f20ad --- /dev/null +++ b/heromodels/src/models/heroledger/rhai.rs @@ -0,0 +1,330 @@ +use ::rhai::plugin::*; +use ::rhai::{Array, Dynamic, Engine, EvalAltResult, Map, Module}; +use std::mem; + +use crate::models::heroledger::*; + +// ============================================================================ +// User Module +// ============================================================================ + +type RhaiUser = User; + +#[export_module] +mod rhai_user_module { + use super::RhaiUser; + + #[rhai_fn(name = "new_user", return_raw)] + pub fn new_user() -> Result> { + Ok(User::new(0)) + } + + #[rhai_fn(name = "username", return_raw)] + pub fn set_username( + user: &mut RhaiUser, + username: String, + ) -> Result> { + let owned = std::mem::take(user); + *user = owned.username(username); + Ok(user.clone()) + } + + #[rhai_fn(name = "add_email", return_raw)] + pub fn add_email( + user: &mut RhaiUser, + email: String, + ) -> Result> { + let owned = std::mem::take(user); + *user = owned.add_email(email); + Ok(user.clone()) + } + + #[rhai_fn(name = "pubkey", return_raw)] + pub fn set_pubkey( + user: &mut RhaiUser, + pubkey: String, + ) -> Result> { + let owned = std::mem::take(user); + *user = owned.pubkey(pubkey); + Ok(user.clone()) + } + + #[rhai_fn(name = "status", return_raw)] + pub fn set_status( + user: &mut RhaiUser, + status: String, + ) -> Result> { + let status_enum = match status.as_str() { + "Active" => UserStatus::Active, + "Inactive" => UserStatus::Inactive, + "Suspended" => UserStatus::Suspended, + "Archived" => UserStatus::Archived, + _ => return Err(format!("Invalid user status: {}", status).into()), + }; + let owned = std::mem::take(user); + *user = owned.status(status_enum); + Ok(user.clone()) + } + + #[rhai_fn(name = "save_user", return_raw)] + pub fn save_user(user: &mut RhaiUser) -> Result> { + // This would integrate with the database save functionality + // For now, just return the user as-is + Ok(user.clone()) + } + + // Getters + #[rhai_fn(name = "get_id")] + pub fn get_id(user: &mut RhaiUser) -> i64 { + user.base_data.id as i64 + } + + #[rhai_fn(name = "get_username")] + pub fn get_username(user: &mut RhaiUser) -> String { + user.username.clone().unwrap_or_else(|| String::new()) + } + + #[rhai_fn(name = "get_email")] + pub fn get_email(user: &mut RhaiUser) -> String { + if let Some(first_email) = user.email.first() { + first_email.clone() + } else { + String::new() + } + } + + #[rhai_fn(name = "get_pubkey")] + pub fn get_pubkey(user: &mut RhaiUser) -> String { + user.pubkey.clone().unwrap_or_else(|| String::new()) + } +} + +// ============================================================================ +// Group Module +// ============================================================================ + +type RhaiGroup = Group; + +#[export_module] +mod rhai_group_module { + use super::RhaiGroup; + + #[rhai_fn(name = "new_group", return_raw)] + pub fn new_group() -> Result> { + Ok(Group::new(0)) + } + + #[rhai_fn(name = "name", return_raw)] + pub fn set_name( + group: &mut RhaiGroup, + name: String, + ) -> Result> { + let owned = std::mem::take(group); + *group = owned.name(name); + Ok(group.clone()) + } + + #[rhai_fn(name = "description", return_raw)] + pub fn set_description( + group: &mut RhaiGroup, + description: String, + ) -> Result> { + let owned = std::mem::take(group); + *group = owned.description(description); + Ok(group.clone()) + } + + #[rhai_fn(name = "visibility", return_raw)] + pub fn set_visibility( + group: &mut RhaiGroup, + visibility: String, + ) -> Result> { + let visibility_enum = match visibility.as_str() { + "Public" => Visibility::Public, + "Private" => Visibility::Private, + _ => return Err(format!("Invalid visibility: {}", visibility).into()), + }; + let owned = std::mem::take(group); + *group = owned.visibility(visibility_enum); + Ok(group.clone()) + } + + #[rhai_fn(name = "save_group", return_raw)] + pub fn save_group(group: &mut RhaiGroup) -> Result> { + Ok(group.clone()) + } + + // Getters + #[rhai_fn(name = "get_id")] + pub fn get_id(group: &mut RhaiGroup) -> i64 { + group.base_data.id as i64 + } + + #[rhai_fn(name = "get_name")] + pub fn get_name(group: &mut RhaiGroup) -> String { + group.name.clone().unwrap_or_else(|| String::new()) + } + + #[rhai_fn(name = "get_description")] + pub fn get_description(group: &mut RhaiGroup) -> String { + group.description.clone().unwrap_or_else(|| String::new()) + } +} + +// ============================================================================ +// Account Module (from money.rs) +// ============================================================================ + +type RhaiAccount = Account; + +#[export_module] +mod rhai_account_module { + use super::RhaiAccount; + + #[rhai_fn(name = "new_account", return_raw)] + pub fn new_account() -> Result> { + Ok(Account::new(0)) + } + + #[rhai_fn(name = "owner_id", return_raw)] + pub fn set_owner_id( + account: &mut RhaiAccount, + owner_id: i64, + ) -> Result> { + let owned = std::mem::take(account); + *account = owned.owner_id(owner_id as u32); + Ok(account.clone()) + } + + #[rhai_fn(name = "address", return_raw)] + pub fn set_address( + account: &mut RhaiAccount, + address: String, + ) -> Result> { + let owned = std::mem::take(account); + *account = owned.address(address); + Ok(account.clone()) + } + + #[rhai_fn(name = "currency", return_raw)] + pub fn set_currency( + account: &mut RhaiAccount, + currency: String, + ) -> Result> { + let owned = std::mem::take(account); + *account = owned.currency(currency); + Ok(account.clone()) + } + + #[rhai_fn(name = "save_account", return_raw)] + pub fn save_account(account: &mut RhaiAccount) -> Result> { + Ok(account.clone()) + } + + // Getters + #[rhai_fn(name = "get_id")] + pub fn get_id(account: &mut RhaiAccount) -> i64 { + account.base_data.id as i64 + } + + #[rhai_fn(name = "get_address")] + pub fn get_address(account: &mut RhaiAccount) -> String { + account.address.clone() + } + + #[rhai_fn(name = "get_currency")] + pub fn get_currency(account: &mut RhaiAccount) -> String { + account.currency.clone() + } +} + +// ============================================================================ +// DNS Zone Module +// ============================================================================ + +type RhaiDNSZone = DNSZone; + +#[export_module] +mod rhai_dns_zone_module { + use super::RhaiDNSZone; + + #[rhai_fn(name = "new_dns_zone", return_raw)] + pub fn new_dns_zone() -> Result> { + Ok(DNSZone::new(0)) + } + + #[rhai_fn(name = "name", return_raw)] + pub fn set_name( + zone: &mut RhaiDNSZone, + name: String, + ) -> Result> { + let owned = std::mem::take(zone); + *zone = owned.name(name); + Ok(zone.clone()) + } + + #[rhai_fn(name = "description", return_raw)] + pub fn set_description( + zone: &mut RhaiDNSZone, + description: String, + ) -> Result> { + let owned = std::mem::take(zone); + *zone = owned.description(description); + Ok(zone.clone()) + } + + #[rhai_fn(name = "save_dns_zone", return_raw)] + pub fn save_dns_zone(zone: &mut RhaiDNSZone) -> Result> { + Ok(zone.clone()) + } + + // Setters + #[rhai_fn(name = "set_domain")] + pub fn set_domain(zone: &mut RhaiDNSZone, domain: &str) { + let owned = std::mem::take(zone); + *zone = owned.domain(domain); + } + + // Getters + #[rhai_fn(name = "get_id")] + pub fn get_id(zone: &mut RhaiDNSZone) -> i64 { + zone.base_data.id as i64 + } + + #[rhai_fn(name = "get_domain")] + pub fn get_domain(zone: &mut RhaiDNSZone) -> String { + zone.domain.clone() + } +} + +// ============================================================================ +// Registration Functions +// ============================================================================ +// Registration functions +pub fn register_user_functions(engine: &mut Engine) { + let module = exported_module!(user_module); + engine.register_static_module("user", module.into()); +} + +pub fn register_group_functions(engine: &mut Engine) { + let module = exported_module!(group_module); + engine.register_static_module("group", module.into()); +} + +pub fn register_account_functions(engine: &mut Engine) { + let module = exported_module!(account_module); + engine.register_static_module("account", module.into()); +} + +pub fn register_dnszone_functions(engine: &mut Engine) { + let module = exported_module!(dnszone_module); + engine.register_static_module("dnszone", module.into()); +} + +/// Register all heroledger Rhai modules with the engine +pub fn register_heroledger_rhai_modules(engine: &mut Engine) { + register_user_functions(engine); + register_group_functions(engine); + register_account_functions(engine); + register_dnszone_functions(engine); +} diff --git a/heromodels/src/models/object/mod.rs b/heromodels/src/models/object/mod.rs index 16a1ca7..5516674 100644 --- a/heromodels/src/models/object/mod.rs +++ b/heromodels/src/models/object/mod.rs @@ -1,5 +1,6 @@ -// Export contact module +// Export object module pub mod object; +pub mod object_rhai_dsl; -// Re-export contact, Group from the inner contact module (contact.rs) within src/models/contact/mod.rs +// Re-export Object from the inner object module (object.rs) within src/models/object/mod.rs pub use self::object::Object; diff --git a/heromodels/src/models/object/object_rhai_dsl.rs b/heromodels/src/models/object/object_rhai_dsl.rs new file mode 100644 index 0000000..13a8578 --- /dev/null +++ b/heromodels/src/models/object/object_rhai_dsl.rs @@ -0,0 +1,56 @@ +use rhai::plugin::*; +use rhai::{CustomType, Dynamic, Engine, EvalAltResult, Module}; +use super::Object; + +type RhaiObject = Object; + +#[export_module] +pub mod generated_rhai_module { + use super::*; + + /// Create a new Object + #[rhai_fn(name = "new_object")] + pub fn new_object() -> RhaiObject { + Object::new() + } + + /// Set the title of an Object + #[rhai_fn(name = "object_title")] + pub fn object_title( + object: &mut RhaiObject, + title: String, + ) -> RhaiObject { + let mut result = object.clone(); + result.title = title; + result + } + + /// Set the description of an Object + #[rhai_fn(name = "object_description")] + pub fn object_description( + object: &mut RhaiObject, + description: String, + ) -> RhaiObject { + let mut result = object.clone(); + result.description = description; + result + } + + /// Get the ID of an Object + #[rhai_fn(name = "get_object_id")] + pub fn get_object_id(object: &mut RhaiObject) -> i64 { + object.id() as i64 + } + + /// Get the title of an Object + #[rhai_fn(name = "get_object_title")] + pub fn get_object_title(object: &mut RhaiObject) -> String { + object.title.clone() + } + + /// Get the description of an Object + #[rhai_fn(name = "get_object_description")] + pub fn get_object_description(object: &mut RhaiObject) -> String { + object.description.clone() + } +} diff --git a/ourdb/API.md b/ourdb/API.md deleted file mode 100644 index f3d56ca..0000000 --- a/ourdb/API.md +++ /dev/null @@ -1,277 +0,0 @@ -# OurDB API Reference - -This document provides a comprehensive reference for the OurDB Rust API. - -## Table of Contents - -1. [Configuration](#configuration) -2. [Database Operations](#database-operations) - - [Creating and Opening](#creating-and-opening) - - [Setting Data](#setting-data) - - [Getting Data](#getting-data) - - [Deleting Data](#deleting-data) - - [History Tracking](#history-tracking) -3. [Error Handling](#error-handling) -4. [Advanced Usage](#advanced-usage) - - [Custom File Size](#custom-file-size) - - [Custom Key Size](#custom-key-size) -5. [Performance Considerations](#performance-considerations) - -## Configuration - -### OurDBConfig - -The `OurDBConfig` struct is used to configure a new OurDB instance. - -```rust -pub struct OurDBConfig { - pub path: PathBuf, - pub incremental_mode: bool, - pub file_size: Option, - pub keysize: Option, -} -``` - -| Field | Type | Description | -|-------|------|-------------| -| `path` | `PathBuf` | Path to the database directory | -| `incremental_mode` | `bool` | Whether to use auto-incremented IDs (true) or user-provided IDs (false) | -| `file_size` | `Option` | Maximum size of each database file in bytes (default: 500MB) | -| `keysize` | `Option` | Size of keys in bytes (default: 4, valid values: 2, 3, 4, 6) | - -Example: -```rust -let config = OurDBConfig { - path: PathBuf::from("/path/to/db"), - incremental_mode: true, - file_size: Some(1024 * 1024 * 100), // 100MB - keysize: Some(4), // 4-byte keys -}; -``` - -## Database Operations - -### Creating and Opening - -#### `OurDB::new` - -Creates a new OurDB instance or opens an existing one. - -```rust -pub fn new(config: OurDBConfig) -> Result -``` - -Example: -```rust -let mut db = OurDB::new(config)?; -``` - -### Setting Data - -#### `OurDB::set` - -Sets a value in the database. In incremental mode, if no ID is provided, a new ID is generated. - -```rust -pub fn set(&mut self, args: OurDBSetArgs) -> Result -``` - -The `OurDBSetArgs` struct has the following fields: - -```rust -pub struct OurDBSetArgs<'a> { - pub id: Option, - pub data: &'a [u8], -} -``` - -Example with auto-generated ID: -```rust -let id = db.set(OurDBSetArgs { - id: None, - data: b"Hello, World!", -})?; -``` - -Example with explicit ID: -```rust -db.set(OurDBSetArgs { - id: Some(42), - data: b"Hello, World!", -})?; -``` - -### Getting Data - -#### `OurDB::get` - -Retrieves a value from the database by ID. - -```rust -pub fn get(&mut self, id: u32) -> Result, Error> -``` - -Example: -```rust -let data = db.get(42)?; -``` - -### Deleting Data - -#### `OurDB::delete` - -Deletes a value from the database by ID. - -```rust -pub fn delete(&mut self, id: u32) -> Result<(), Error> -``` - -Example: -```rust -db.delete(42)?; -``` - -### History Tracking - -#### `OurDB::get_history` - -Retrieves the history of values for a given ID, up to the specified depth. - -```rust -pub fn get_history(&mut self, id: u32, depth: u8) -> Result>, Error> -``` - -Example: -```rust -// Get the last 5 versions of the record -let history = db.get_history(42, 5)?; - -// Process each version (most recent first) -for (i, version) in history.iter().enumerate() { - println!("Version {}: {:?}", i, version); -} -``` - -### Other Operations - -#### `OurDB::get_next_id` - -Returns the next ID that will be assigned in incremental mode. - -```rust -pub fn get_next_id(&self) -> Result -``` - -Example: -```rust -let next_id = db.get_next_id()?; -``` - -#### `OurDB::close` - -Closes the database, ensuring all data is flushed to disk. - -```rust -pub fn close(&mut self) -> Result<(), Error> -``` - -Example: -```rust -db.close()?; -``` - -#### `OurDB::destroy` - -Closes the database and deletes all database files. - -```rust -pub fn destroy(&mut self) -> Result<(), Error> -``` - -Example: -```rust -db.destroy()?; -``` - -## Error Handling - -OurDB uses the `thiserror` crate to define error types. The main error type is `ourdb::Error`. - -```rust -pub enum Error { - IoError(std::io::Error), - InvalidKeySize, - InvalidId, - RecordNotFound, - InvalidCrc, - NotIncrementalMode, - DatabaseClosed, - // ... -} -``` - -All OurDB operations that can fail return a `Result` which can be handled using Rust's standard error handling mechanisms. - -Example: -```rust -match db.get(42) { - Ok(data) => println!("Found data: {:?}", data), - Err(ourdb::Error::RecordNotFound) => println!("Record not found"), - Err(e) => eprintln!("Error: {}", e), -} -``` - -## Advanced Usage - -### Custom File Size - -You can configure the maximum size of each database file: - -```rust -let config = OurDBConfig { - path: PathBuf::from("/path/to/db"), - incremental_mode: true, - file_size: Some(1024 * 1024 * 10), // 10MB per file - keysize: None, -}; -``` - -Smaller file sizes can be useful for: -- Limiting memory usage when reading files -- Improving performance on systems with limited memory -- Easier backup and file management - -### Custom Key Size - -OurDB supports different key sizes (2, 3, 4, or 6 bytes): - -```rust -let config = OurDBConfig { - path: PathBuf::from("/path/to/db"), - incremental_mode: true, - file_size: None, - keysize: Some(6), // 6-byte keys -}; -``` - -Key size considerations: -- 2 bytes: Up to 65,536 records -- 3 bytes: Up to 16,777,216 records -- 4 bytes: Up to 4,294,967,296 records (default) -- 6 bytes: Up to 281,474,976,710,656 records - -## Performance Considerations - -For optimal performance: - -1. **Choose appropriate key size**: Use the smallest key size that can accommodate your expected number of records. - -2. **Configure file size**: For large databases, consider using smaller file sizes to improve memory usage. - -3. **Batch operations**: When inserting or updating many records, consider batching operations to minimize disk I/O. - -4. **Close properly**: Always call `close()` when you're done with the database to ensure data is properly flushed to disk. - -5. **Reuse OurDB instance**: Creating a new OurDB instance has overhead, so reuse the same instance for multiple operations when possible. - -6. **Consider memory usage**: The lookup table is loaded into memory, so very large databases may require significant RAM. diff --git a/ourdb/Cargo.lock b/ourdb/Cargo.lock deleted file mode 100644 index 6126ce6..0000000 --- a/ourdb/Cargo.lock +++ /dev/null @@ -1,806 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "bumpalo" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "clap" -version = "4.5.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" -dependencies = [ - "anstyle", - "clap_lex", -] - -[[package]] -name = "clap_lex" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "criterion" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" -dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "is-terminal", - "itertools", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "errno" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", -] - -[[package]] -name = "half" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" -dependencies = [ - "cfg-if", - "crunchy", -] - -[[package]] -name = "hermit-abi" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" - -[[package]] -name = "is-terminal" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "libc" -version = "0.2.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "oorandom" -version = "11.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" - -[[package]] -name = "ourdb" -version = "0.1.0" -dependencies = [ - "crc32fast", - "criterion", - "log", - "rand", - "tempfile", - "thiserror", -] - -[[package]] -name = "plotters" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] - -[[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.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" -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 = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - -[[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 0.2.15", -] - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "rustix" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "rustversion" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[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 = "tempfile" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" -dependencies = [ - "fastrand", - "getrandom 0.3.2", - "once_cell", - "rustix", - "windows-sys", -] - -[[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 = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] - -[[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/Cargo.toml b/ourdb/Cargo.toml deleted file mode 100644 index 6ff8e8e..0000000 --- a/ourdb/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "ourdb" -version = "0.1.0" -edition = "2021" -description = "A lightweight, efficient key-value database with history tracking capabilities" -authors = ["OurWorld Team"] - -[dependencies] -crc32fast = "1.3.2" -thiserror = "1.0.40" -log = "0.4.17" -rand = "0.8.5" - -[dev-dependencies] -criterion = "0.5.1" -tempfile = "3.8.0" - -# [[bench]] -# name = "ourdb_benchmarks" -# harness = false - -[[example]] -name = "basic_usage" -path = "examples/basic_usage.rs" - -[[example]] -name = "advanced_usage" -path = "examples/advanced_usage.rs" - -[[example]] -name = "benchmark" -path = "examples/benchmark.rs" diff --git a/ourdb/README.md b/ourdb/README.md deleted file mode 100644 index 8e68bbe..0000000 --- a/ourdb/README.md +++ /dev/null @@ -1,135 +0,0 @@ -# OurDB - -OurDB is a lightweight, efficient key-value database implementation that provides data persistence with history tracking capabilities. This Rust implementation offers a robust and performant solution for applications requiring simple but reliable data storage. - -## Features - -- Simple key-value storage with history tracking -- Data integrity verification using CRC32 -- Support for multiple backend files for large datasets -- Lookup table for fast data retrieval -- Incremental mode for auto-generated IDs -- Memory and disk-based lookup tables - -## Limitations - -- Maximum data size per entry is 65,535 bytes (~64KB) due to the 2-byte size field in the record header - -## Usage - -### Basic Example - -```rust -use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; -use std::path::PathBuf; - -fn main() -> Result<(), ourdb::Error> { - // Create a new database - let config = OurDBConfig { - path: PathBuf::from("/tmp/ourdb"), - incremental_mode: true, - file_size: None, // Use default (500MB) - keysize: None, // Use default (4 bytes) - }; - - let mut db = OurDB::new(config)?; - - // Store data (with auto-generated ID in incremental mode) - let data = b"Hello, OurDB!"; - let id = db.set(OurDBSetArgs { id: None, data })?; - println!("Stored data with ID: {}", id); - - // Retrieve data - let retrieved = db.get(id)?; - println!("Retrieved: {}", String::from_utf8_lossy(&retrieved)); - - // Update data - let updated_data = b"Updated data"; - db.set(OurDBSetArgs { id: Some(id), data: updated_data })?; - - // Get history (returns most recent first) - let history = db.get_history(id, 2)?; - for (i, entry) in history.iter().enumerate() { - println!("History {}: {}", i, String::from_utf8_lossy(entry)); - } - - // Delete data - db.delete(id)?; - - // Close the database - db.close()?; - - Ok(()) -} -``` - -### Key-Value Mode vs Incremental Mode - -OurDB supports two operating modes: - -1. **Key-Value Mode** (`incremental_mode: false`): You must provide IDs explicitly when storing data. -2. **Incremental Mode** (`incremental_mode: true`): IDs are auto-generated when not provided. - -### Configuration Options - -- `path`: Directory for database storage -- `incremental_mode`: Whether to use auto-increment mode -- `file_size`: Maximum file size (default: 500MB) -- `keysize`: Size of lookup table entries (2-6 bytes) - - 2: For databases with < 65,536 records - - 3: For databases with < 16,777,216 records - - 4: For databases with < 4,294,967,296 records (default) - - 6: For large databases requiring multiple files - -## Architecture - -OurDB consists of three main components: - -1. **Frontend API**: Provides the public interface for database operations -2. **Lookup Table**: Maps keys to physical locations in the backend storage -3. **Backend Storage**: Manages the actual data persistence in files - -### Record Format - -Each record in the backend storage includes: -- 2 bytes: Data size -- 4 bytes: CRC32 checksum -- 6 bytes: Previous record location (for history) -- N bytes: Actual data - -## Documentation - -Additional documentation is available in the repository: - -- [API Reference](API.md): Detailed API documentation -- [Migration Guide](MIGRATION.md): Guide for migrating from the V implementation -- [Architecture](architecture.md): Design and implementation details - -## Examples - -The repository includes several examples to demonstrate OurDB usage: - -- `basic_usage.rs`: Simple operations with OurDB -- `advanced_usage.rs`: More complex features including both operation modes -- `benchmark.rs`: Performance benchmarking tool - -Run an example with: - -```bash -cargo run --example basic_usage -cargo run --example advanced_usage -cargo run --example benchmark -``` - -## Performance - -OurDB is designed for efficiency and minimal overhead. The benchmark example can be used to evaluate performance on your specific hardware and workload. - -Typical performance metrics on modern hardware: - -- **Write**: 10,000+ operations per second -- **Read**: 50,000+ operations per second - -## License - -This project is licensed under the MIT License. diff --git a/ourdb/architecture.md b/ourdb/architecture.md deleted file mode 100644 index d6072f7..0000000 --- a/ourdb/architecture.md +++ /dev/null @@ -1,439 +0,0 @@ -# OurDB: Architecture for V to Rust Port - -## 1. Overview - -OurDB is a lightweight, efficient key-value database implementation that provides data persistence with history tracking capabilities. This document outlines the architecture for porting OurDB from its original V implementation to Rust, maintaining all existing functionality while leveraging Rust's memory safety, performance, and ecosystem. - -## 2. Current Architecture (V Implementation) - -The current V implementation of OurDB consists of three main components in a layered architecture: - -```mermaid -graph TD - A[Client Code] --> B[Frontend API] - B --> C[Lookup Table] - B --> D[Backend Storage] - C --> D -``` - -### 2.1 Frontend (db.v) - -The frontend provides the public API for database operations and coordinates between the lookup table and backend storage components. - -Key responsibilities: -- Exposing high-level operations (set, get, delete, history) -- Managing incremental ID generation in auto-increment mode -- Coordinating data flow between lookup and backend components -- Handling database lifecycle (open, close, destroy) - -### 2.2 Lookup Table (lookup.v) - -The lookup table maps keys to physical locations in the backend storage. - -Key responsibilities: -- Maintaining key-to-location mapping -- Optimizing key sizes based on database configuration -- Supporting both memory and disk-based lookup tables -- Handling sparse data efficiently -- Providing next ID generation for incremental mode - -### 2.3 Backend Storage (backend.v) - -The backend storage manages the actual data persistence in files. - -Key responsibilities: -- Managing physical data storage in files -- Ensuring data integrity with CRC32 checksums -- Supporting multiple file backends for large datasets -- Implementing low-level read/write operations -- Tracking record history through linked locations - -### 2.4 Core Data Structures - -#### OurDB -```v -@[heap] -pub struct OurDB { -mut: - lookup &LookupTable -pub: - path string // directory for storage - incremental_mode bool - file_size u32 = 500 * (1 << 20) // 500MB -pub mut: - file os.File - file_nr u16 // the file which is open - last_used_file_nr u16 -} -``` - -#### LookupTable -```v -pub struct LookupTable { - keysize u8 - lookuppath string -mut: - data []u8 - incremental ?u32 // points to next empty slot if incremental mode is enabled -} -``` - -#### Location -```v -pub struct Location { -pub mut: - file_nr u16 - position u32 -} -``` - -### 2.5 Storage Format - -#### Record Format -Each record in the backend storage includes: -- 2 bytes: Data size -- 4 bytes: CRC32 checksum -- 6 bytes: Previous record location (for history) -- N bytes: Actual data - -#### Lookup Table Optimization -The lookup table automatically optimizes its key size based on the database configuration: -- 2 bytes: For databases with < 65,536 records -- 3 bytes: For databases with < 16,777,216 records -- 4 bytes: For databases with < 4,294,967,296 records -- 6 bytes: For large databases requiring multiple files - -## 3. Proposed Rust Architecture - -The Rust implementation will maintain the same layered architecture while leveraging Rust's type system, ownership model, and error handling. - -```mermaid -graph TD - A[Client Code] --> B[OurDB API] - B --> C[LookupTable] - B --> D[Backend] - C --> D - E[Error Handling] --> B - E --> C - E --> D - F[Configuration] --> B -``` - -### 3.1 Core Components - -#### 3.1.1 OurDB (API Layer) - -```rust -pub struct OurDB { - path: String, - incremental_mode: bool, - file_size: u32, - lookup: LookupTable, - file: Option, - file_nr: u16, - last_used_file_nr: u16, -} - -impl OurDB { - pub fn new(config: OurDBConfig) -> Result; - pub fn set(&mut self, id: Option, data: &[u8]) -> Result; - pub fn get(&mut self, id: u32) -> Result, Error>; - pub fn get_history(&mut self, id: u32, depth: u8) -> Result>, Error>; - pub fn delete(&mut self, id: u32) -> Result<(), Error>; - pub fn get_next_id(&mut self) -> Result; - pub fn close(&mut self) -> Result<(), Error>; - pub fn destroy(&mut self) -> Result<(), Error>; -} -``` - -#### 3.1.2 LookupTable - -```rust -pub struct LookupTable { - keysize: u8, - lookuppath: String, - data: Vec, - incremental: Option, -} - -impl LookupTable { - fn new(config: LookupConfig) -> Result; - fn get(&self, id: u32) -> Result; - fn set(&mut self, id: u32, location: Location) -> Result<(), Error>; - fn delete(&mut self, id: u32) -> Result<(), Error>; - fn get_next_id(&self) -> Result; - fn increment_index(&mut self) -> Result<(), Error>; - fn export_data(&self, path: &str) -> Result<(), Error>; - fn import_data(&mut self, path: &str) -> Result<(), Error>; - fn export_sparse(&self, path: &str) -> Result<(), Error>; - fn import_sparse(&mut self, path: &str) -> Result<(), Error>; -} -``` - -#### 3.1.3 Location - -```rust -pub struct Location { - file_nr: u16, - position: u32, -} - -impl Location { - fn new(bytes: &[u8], keysize: u8) -> Result; - fn to_bytes(&self) -> Result, Error>; - fn to_u64(&self) -> u64; -} -``` - -#### 3.1.4 Backend - -The backend functionality will be implemented as methods on the OurDB struct: - -```rust -impl OurDB { - fn db_file_select(&mut self, file_nr: u16) -> Result<(), Error>; - fn create_new_db_file(&mut self, file_nr: u16) -> Result<(), Error>; - fn get_file_nr(&mut self) -> Result; - fn set_(&mut self, id: u32, old_location: Location, data: &[u8]) -> Result<(), Error>; - fn get_(&mut self, location: Location) -> Result, Error>; - fn get_prev_pos_(&mut self, location: Location) -> Result; - fn delete_(&mut self, id: u32, location: Location) -> Result<(), Error>; - fn close_(&mut self); -} -``` - -#### 3.1.5 Configuration - -```rust -pub struct OurDBConfig { - pub record_nr_max: u32, - pub record_size_max: u32, - pub file_size: u32, - pub path: String, - pub incremental_mode: bool, - pub reset: bool, -} - -struct LookupConfig { - size: u32, - keysize: u8, - lookuppath: String, - incremental_mode: bool, -} -``` - -#### 3.1.6 Error Handling - -```rust -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("I/O error: {0}")] - Io(#[from] std::io::Error), - - #[error("Invalid key size: {0}")] - InvalidKeySize(u8), - - #[error("Record not found: {0}")] - RecordNotFound(u32), - - #[error("Data corruption: CRC mismatch")] - DataCorruption, - - #[error("Index out of bounds: {0}")] - IndexOutOfBounds(u32), - - #[error("Incremental mode not enabled")] - IncrementalNotEnabled, - - #[error("Lookup table is full")] - LookupTableFull, - - #[error("Invalid file number: {0}")] - InvalidFileNumber(u16), - - #[error("Invalid operation: {0}")] - InvalidOperation(String), -} -``` - -## 4. Implementation Strategy - -### 4.1 Phase 1: Core Data Structures - -1. Implement the `Location` struct with serialization/deserialization -2. Implement the `Error` enum for error handling -3. Implement the configuration structures - -### 4.2 Phase 2: Lookup Table - -1. Implement the `LookupTable` struct with memory-based storage -2. Add disk-based storage support -3. Implement key size optimization -4. Add incremental ID support -5. Implement import/export functionality - -### 4.3 Phase 3: Backend Storage - -1. Implement file management functions -2. Implement record serialization/deserialization with CRC32 -3. Implement history tracking through linked locations -4. Add support for multiple backend files - -### 4.4 Phase 4: Frontend API - -1. Implement the `OurDB` struct with core operations -2. Add high-level API methods (set, get, delete, history) -3. Implement database lifecycle management - -### 4.5 Phase 5: Testing and Optimization - -1. Port existing tests from V to Rust -2. Add new tests for Rust-specific functionality -3. Benchmark and optimize performance -4. Ensure compatibility with existing OurDB files - -## 5. Implementation Considerations - -### 5.1 Memory Management - -Leverage Rust's ownership model for safe and efficient memory management: -- Use `Vec` for data buffers instead of raw pointers -- Implement proper RAII for file handles -- Use references and borrows to avoid unnecessary copying -- Consider using `Bytes` from the `bytes` crate for zero-copy operations - -### 5.2 Error Handling - -Use Rust's `Result` type for comprehensive error handling: -- Define custom error types for OurDB-specific errors -- Propagate errors using the `?` operator -- Provide detailed error messages -- Implement proper error conversion using the `From` trait - -### 5.3 File I/O - -Optimize file operations for performance: -- Use `BufReader` and `BufWriter` for buffered I/O -- Implement proper file locking for concurrent access -- Consider memory-mapped files for lookup tables -- Use `seek` and `read_exact` for precise positioning - -### 5.4 Concurrency - -Consider thread safety for concurrent database access: -- Use interior mutability patterns where appropriate -- Implement `Send` and `Sync` traits for thread safety -- Consider using `RwLock` for shared read access -- Provide clear documentation on thread safety guarantees - -### 5.5 Performance Optimizations - -Identify opportunities for performance improvements: -- Use memory-mapped files for lookup tables -- Implement caching for frequently accessed records -- Use zero-copy operations where possible -- Consider async I/O for non-blocking operations - -## 6. Testing Strategy - -### 6.1 Unit Tests - -Write comprehensive unit tests for each component: -- Test `Location` serialization/deserialization -- Test `LookupTable` operations -- Test backend storage functions -- Test error handling - -### 6.2 Integration Tests - -Write integration tests for the complete system: -- Test database creation and configuration -- Test basic CRUD operations -- Test history tracking -- Test incremental ID generation -- Test file management - -### 6.3 Compatibility Tests - -Ensure compatibility with existing OurDB files: -- Test reading existing V-created OurDB files -- Test writing files that can be read by the V implementation -- Test migration scenarios - -### 6.4 Performance Tests - -Benchmark performance against the V implementation: -- Measure throughput for set/get operations -- Measure latency for different operations -- Test with different database sizes -- Test with different record sizes - -## 7. Project Structure - -``` -ourdb/ -├── Cargo.toml -├── src/ -│ ├── lib.rs # Public API and re-exports -│ ├── ourdb.rs # OurDB implementation (frontend) -│ ├── lookup.rs # Lookup table implementation -│ ├── location.rs # Location struct implementation -│ ├── backend.rs # Backend storage implementation -│ ├── error.rs # Error types -│ ├── config.rs # Configuration structures -│ └── utils.rs # Utility functions -├── tests/ -│ ├── unit/ # Unit tests -│ ├── integration/ # Integration tests -│ └── compatibility/ # Compatibility tests -└── examples/ - ├── basic.rs # Basic usage example - ├── history.rs # History tracking example - └── client_server.rs # Client-server example -``` - -## 8. Dependencies - -The Rust implementation will use the following dependencies: - -- `thiserror` for error handling -- `crc32fast` for CRC32 calculation -- `bytes` for efficient byte manipulation -- `memmap2` for memory-mapped files (optional) -- `serde` for serialization (optional, for future extensions) -- `log` for logging -- `criterion` for benchmarking - -## 9. Compatibility Considerations - -To ensure compatibility with the V implementation: - -1. Maintain the same file format for data storage -2. Preserve the lookup table format -3. Keep the same CRC32 calculation method -4. Ensure identical behavior for incremental ID generation -5. Maintain the same history tracking mechanism - -## 10. Future Extensions - -Potential future extensions to consider: - -1. Async API for non-blocking operations -2. Transactions support -3. Better concurrency control -4. Compression support -5. Encryption support -6. Streaming API for large values -7. Iterators for scanning records -8. Secondary indexes - -## 11. Conclusion - -This architecture provides a roadmap for porting OurDB from V to Rust while maintaining compatibility and leveraging Rust's strengths. The implementation will follow a phased approach, starting with core data structures and gradually building up to the complete system. - -The Rust implementation aims to be: -- **Safe**: Leveraging Rust's ownership model for memory safety -- **Fast**: Maintaining or improving performance compared to V -- **Compatible**: Working with existing OurDB files -- **Extensible**: Providing a foundation for future enhancements -- **Well-tested**: Including comprehensive test coverage \ No newline at end of file diff --git a/ourdb/examples/advanced_usage.rs b/ourdb/examples/advanced_usage.rs deleted file mode 100644 index 831a767..0000000 --- a/ourdb/examples/advanced_usage.rs +++ /dev/null @@ -1,231 +0,0 @@ -use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; -use std::path::PathBuf; -use std::time::Instant; - -fn main() -> Result<(), ourdb::Error> { - // Create a temporary directory for the database - let db_path = std::env::temp_dir().join("ourdb_advanced_example"); - std::fs::create_dir_all(&db_path)?; - - println!("Creating database at: {}", db_path.display()); - - // Demonstrate key-value mode (non-incremental) - key_value_mode_example(&db_path)?; - - // Demonstrate incremental mode - incremental_mode_example(&db_path)?; - - // Demonstrate performance benchmarking - performance_benchmark(&db_path)?; - - // Clean up (optional) - if std::env::var("KEEP_DB").is_err() { - std::fs::remove_dir_all(&db_path)?; - println!("Cleaned up database directory"); - } else { - println!("Database kept at: {}", db_path.display()); - } - - Ok(()) -} - -fn key_value_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> { - println!("\n=== Key-Value Mode Example ==="); - - let db_path = base_path.join("key_value"); - std::fs::create_dir_all(&db_path)?; - - // Create a new database with key-value mode (non-incremental) - let config = OurDBConfig { - path: db_path, - incremental_mode: false, - file_size: Some(1024 * 1024), // 1MB for testing - keysize: Some(2), // Small key size for demonstration - reset: None, // Don't reset existing database - }; - - let mut db = OurDB::new(config)?; - - // In key-value mode, we must provide IDs explicitly - let custom_ids = [100, 200, 300, 400, 500]; - - // Store data with custom IDs - for (i, &id) in custom_ids.iter().enumerate() { - let data = format!("Record with custom ID {}", id); - db.set(OurDBSetArgs { - id: Some(id), - data: data.as_bytes(), - })?; - println!("Stored record {} with custom ID: {}", i + 1, id); - } - - // Retrieve data by custom IDs - for &id in &custom_ids { - let retrieved = db.get(id)?; - println!( - "Retrieved ID {}: {}", - id, - String::from_utf8_lossy(&retrieved) - ); - } - - // Update and track history - let id_to_update = custom_ids[2]; // ID 300 - for i in 1..=3 { - let updated_data = format!("Updated record {} (version {})", id_to_update, i); - db.set(OurDBSetArgs { - id: Some(id_to_update), - data: updated_data.as_bytes(), - })?; - println!("Updated ID {} (version {})", id_to_update, i); - } - - // Get history for the updated record - let history = db.get_history(id_to_update, 5)?; - println!("History for ID {} (most recent first):", id_to_update); - for (i, entry) in history.iter().enumerate() { - println!(" Version {}: {}", i, String::from_utf8_lossy(entry)); - } - - db.close()?; - println!("Key-value mode example completed"); - - Ok(()) -} - -fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> { - println!("\n=== Incremental Mode Example ==="); - - let db_path = base_path.join("incremental"); - std::fs::create_dir_all(&db_path)?; - - // Create a new database with incremental mode - let config = OurDBConfig { - path: db_path, - incremental_mode: true, - file_size: Some(1024 * 1024), // 1MB for testing - keysize: Some(3), // 3-byte keys - reset: None, // Don't reset existing database - }; - - let mut db = OurDB::new(config)?; - - // In incremental mode, IDs are auto-generated - let mut assigned_ids = Vec::new(); - - // Store multiple records and collect assigned IDs - for i in 1..=5 { - let data = format!("Auto-increment record {}", i); - let id = db.set(OurDBSetArgs { - id: None, - data: data.as_bytes(), - })?; - assigned_ids.push(id); - println!("Stored record {} with auto-assigned ID: {}", i, id); - } - - // Check next ID - let next_id = db.get_next_id()?; - println!("Next ID to be assigned: {}", next_id); - - // Retrieve all records - for &id in &assigned_ids { - let retrieved = db.get(id)?; - println!( - "Retrieved ID {}: {}", - id, - String::from_utf8_lossy(&retrieved) - ); - } - - db.close()?; - println!("Incremental mode example completed"); - - Ok(()) -} - -fn performance_benchmark(base_path: &PathBuf) -> Result<(), ourdb::Error> { - println!("\n=== Performance Benchmark ==="); - - let db_path = base_path.join("benchmark"); - std::fs::create_dir_all(&db_path)?; - - // Create a new database - let config = OurDBConfig { - path: db_path, - incremental_mode: true, - file_size: Some(1024 * 1024), // 10MB - keysize: Some(4), // 4-byte keys - reset: None, // Don't reset existing database - }; - - let mut db = OurDB::new(config)?; - - // Number of operations for the benchmark - let num_operations = 1000; - let data_size = 100; // bytes per record - - // Prepare test data - let test_data = vec![b'A'; data_size]; - - // Benchmark write operations - println!("Benchmarking {} write operations...", num_operations); - let start = Instant::now(); - - let mut ids = Vec::with_capacity(num_operations); - for _ in 0..num_operations { - let id = db.set(OurDBSetArgs { - id: None, - data: &test_data, - })?; - ids.push(id); - } - - let write_duration = start.elapsed(); - let writes_per_second = num_operations as f64 / write_duration.as_secs_f64(); - println!( - "Write performance: {:.2} ops/sec ({:.2} ms/op)", - writes_per_second, - write_duration.as_secs_f64() * 1000.0 / num_operations as f64 - ); - - // Benchmark read operations - println!("Benchmarking {} read operations...", num_operations); - let start = Instant::now(); - - for &id in &ids { - let _ = db.get(id)?; - } - - let read_duration = start.elapsed(); - let reads_per_second = num_operations as f64 / read_duration.as_secs_f64(); - println!( - "Read performance: {:.2} ops/sec ({:.2} ms/op)", - reads_per_second, - read_duration.as_secs_f64() * 1000.0 / num_operations as f64 - ); - - // Benchmark update operations - println!("Benchmarking {} update operations...", num_operations); - let start = Instant::now(); - - for &id in &ids { - db.set(OurDBSetArgs { - id: Some(id), - data: &test_data, - })?; - } - - let update_duration = start.elapsed(); - let updates_per_second = num_operations as f64 / update_duration.as_secs_f64(); - println!( - "Update performance: {:.2} ops/sec ({:.2} ms/op)", - updates_per_second, - update_duration.as_secs_f64() * 1000.0 / num_operations as f64 - ); - - db.close()?; - println!("Performance benchmark completed"); - - Ok(()) -} diff --git a/ourdb/examples/basic_usage.rs b/ourdb/examples/basic_usage.rs deleted file mode 100644 index 6d160e7..0000000 --- a/ourdb/examples/basic_usage.rs +++ /dev/null @@ -1,89 +0,0 @@ -use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; - -fn main() -> Result<(), ourdb::Error> { - // Create a temporary directory for the database - let db_path = std::env::temp_dir().join("ourdb_example"); - std::fs::create_dir_all(&db_path)?; - - println!("Creating database at: {}", db_path.display()); - - // Create a new database with incremental mode enabled - let config = OurDBConfig { - path: db_path.clone(), - incremental_mode: true, - file_size: None, // Use default (500MB) - keysize: None, // Use default (4 bytes) - reset: None, // Don't reset existing database - }; - - let mut db = OurDB::new(config)?; - - // Store some data with auto-generated IDs - let data1 = b"First record"; - let id1 = db.set(OurDBSetArgs { - id: None, - data: data1, - })?; - println!("Stored first record with ID: {}", id1); - - let data2 = b"Second record"; - let id2 = db.set(OurDBSetArgs { - id: None, - data: data2, - })?; - println!("Stored second record with ID: {}", id2); - - // Retrieve and print the data - let retrieved1 = db.get(id1)?; - println!( - "Retrieved ID {}: {}", - id1, - String::from_utf8_lossy(&retrieved1) - ); - - let retrieved2 = db.get(id2)?; - println!( - "Retrieved ID {}: {}", - id2, - String::from_utf8_lossy(&retrieved2) - ); - - // Update a record to demonstrate history tracking - let updated_data = b"Updated first record"; - db.set(OurDBSetArgs { - id: Some(id1), - data: updated_data, - })?; - println!("Updated record with ID: {}", id1); - - // Get history for the updated record - let history = db.get_history(id1, 2)?; - println!("History for ID {}:", id1); - for (i, entry) in history.iter().enumerate() { - println!(" Version {}: {}", i, String::from_utf8_lossy(entry)); - } - - // Delete a record - db.delete(id2)?; - println!("Deleted record with ID: {}", id2); - - // Verify deletion - match db.get(id2) { - Ok(_) => println!("Record still exists (unexpected)"), - Err(e) => println!("Verified deletion: {}", e), - } - - // Close the database - db.close()?; - println!("Database closed successfully"); - - // Clean up (optional) - if std::env::var("KEEP_DB").is_err() { - std::fs::remove_dir_all(&db_path)?; - println!("Cleaned up database directory"); - } else { - println!("Database kept at: {}", db_path.display()); - } - - Ok(()) -} diff --git a/ourdb/examples/benchmark.rs b/ourdb/examples/benchmark.rs deleted file mode 100644 index 1004dde..0000000 --- a/ourdb/examples/benchmark.rs +++ /dev/null @@ -1,124 +0,0 @@ -use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; -use std::time::Instant; - -fn main() -> Result<(), ourdb::Error> { - // Parse command-line arguments - let args: Vec = std::env::args().collect(); - - // Default values - let mut incremental_mode = true; - let mut keysize: u8 = 4; - let mut num_operations = 10000; - - // Parse arguments - for i in 1..args.len() { - if args[i] == "--no-incremental" { - incremental_mode = false; - } else if args[i] == "--keysize" && i + 1 < args.len() { - keysize = args[i + 1].parse().unwrap_or(4); - } else if args[i] == "--ops" && i + 1 < args.len() { - num_operations = args[i + 1].parse().unwrap_or(10000); - } - } - - // Create a temporary directory for the database - let db_path = std::env::temp_dir().join("ourdb_benchmark"); - std::fs::create_dir_all(&db_path)?; - - println!("Database path: {}", db_path.display()); - - // Create a new database - let config = OurDBConfig { - path: db_path.clone(), - incremental_mode, - file_size: Some(1024 * 1024), - keysize: Some(keysize), - reset: Some(true), // Reset the database for benchmarking - }; - - let mut db = OurDB::new(config)?; - - // Prepare test data (100 bytes per record) - let test_data = vec![b'A'; 100]; - - // Benchmark write operations - println!( - "Benchmarking {} write operations (incremental: {}, keysize: {})...", - num_operations, incremental_mode, keysize - ); - - let start = Instant::now(); - - let mut ids = Vec::with_capacity(num_operations); - for _ in 0..num_operations { - let id = if incremental_mode { - db.set(OurDBSetArgs { - id: None, - data: &test_data, - })? - } else { - // In non-incremental mode, we need to provide IDs - let id = ids.len() as u32 + 1; - db.set(OurDBSetArgs { - id: Some(id), - data: &test_data, - })?; - id - }; - ids.push(id); - } - - let write_duration = start.elapsed(); - let writes_per_second = num_operations as f64 / write_duration.as_secs_f64(); - - println!( - "Write performance: {:.2} ops/sec ({:.2} ms/op)", - writes_per_second, - write_duration.as_secs_f64() * 1000.0 / num_operations as f64 - ); - - // Benchmark read operations - println!("Benchmarking {} read operations...", num_operations); - - let start = Instant::now(); - - for &id in &ids { - let _ = db.get(id)?; - } - - let read_duration = start.elapsed(); - let reads_per_second = num_operations as f64 / read_duration.as_secs_f64(); - - println!( - "Read performance: {:.2} ops/sec ({:.2} ms/op)", - reads_per_second, - read_duration.as_secs_f64() * 1000.0 / num_operations as f64 - ); - - // Benchmark update operations - println!("Benchmarking {} update operations...", num_operations); - - let start = Instant::now(); - - for &id in &ids { - db.set(OurDBSetArgs { - id: Some(id), - data: &test_data, - })?; - } - - let update_duration = start.elapsed(); - let updates_per_second = num_operations as f64 / update_duration.as_secs_f64(); - - println!( - "Update performance: {:.2} ops/sec ({:.2} ms/op)", - updates_per_second, - update_duration.as_secs_f64() * 1000.0 / num_operations as f64 - ); - - // Clean up - db.close()?; - std::fs::remove_dir_all(&db_path)?; - - Ok(()) -} diff --git a/ourdb/examples/main.rs b/ourdb/examples/main.rs deleted file mode 100644 index 546eff1..0000000 --- a/ourdb/examples/main.rs +++ /dev/null @@ -1,83 +0,0 @@ -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/ourdb/examples/standalone_ourdb_example.rs b/ourdb/examples/standalone_ourdb_example.rs deleted file mode 100644 index 546eff1..0000000 --- a/ourdb/examples/standalone_ourdb_example.rs +++ /dev/null @@ -1,83 +0,0 @@ -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/ourdb/src/backend.rs b/ourdb/src/backend.rs deleted file mode 100644 index 0a8dbe2..0000000 --- a/ourdb/src/backend.rs +++ /dev/null @@ -1,366 +0,0 @@ -use std::fs::{self, File, OpenOptions}; -use std::io::{Read, Seek, SeekFrom, Write}; - -use crc32fast::Hasher; - -use crate::error::Error; -use crate::location::Location; -use crate::OurDB; - -// Header size: 2 bytes (size) + 4 bytes (CRC32) + 6 bytes (previous location) -pub const HEADER_SIZE: usize = 12; - -impl OurDB { - /// Selects and opens a database file for read/write operations - pub(crate) fn db_file_select(&mut self, file_nr: u16) -> Result<(), Error> { - // No need to check if file_nr > 65535 as u16 can't exceed that value - - let path = self.path.join(format!("{}.db", file_nr)); - - // Always close the current file if it's open - self.file = None; - - // Create file if it doesn't exist - if !path.exists() { - self.create_new_db_file(file_nr)?; - } - - // Open the file fresh - let file = OpenOptions::new().read(true).write(true).open(&path)?; - - self.file = Some(file); - self.file_nr = file_nr; - - Ok(()) - } - - /// Creates a new database file - pub(crate) fn create_new_db_file(&mut self, file_nr: u16) -> Result<(), Error> { - let new_file_path = self.path.join(format!("{}.db", file_nr)); - let mut file = File::create(&new_file_path)?; - - // Write a single byte to make all positions start from 1 - file.write_all(&[0u8])?; - - Ok(()) - } - - /// Gets the file number to use for the next write operation - pub(crate) fn get_file_nr(&mut self) -> Result { - // For keysize 2, 3, or 4, we can only use file_nr 0 - if self.lookup.keysize() <= 4 { - let path = self.path.join("0.db"); - - if !path.exists() { - self.create_new_db_file(0)?; - } - - return Ok(0); - } - - // For keysize 6, we can use multiple files - let path = self.path.join(format!("{}.db", self.last_used_file_nr)); - - if !path.exists() { - self.create_new_db_file(self.last_used_file_nr)?; - return Ok(self.last_used_file_nr); - } - - let metadata = fs::metadata(&path)?; - if metadata.len() >= self.file_size as u64 { - self.last_used_file_nr += 1; - self.create_new_db_file(self.last_used_file_nr)?; - } - - Ok(self.last_used_file_nr) - } - - /// Stores data at the specified ID with history tracking - pub(crate) fn set_( - &mut self, - id: u32, - old_location: Location, - data: &[u8], - ) -> Result<(), Error> { - // Validate data size - maximum is u16::MAX (65535 bytes or ~64KB) - if data.len() > u16::MAX as usize { - return Err(Error::InvalidOperation(format!( - "Data size exceeds maximum allowed size of {} bytes", - u16::MAX - ))); - } - - // Get file number to use - let file_nr = self.get_file_nr()?; - - // Select the file - self.db_file_select(file_nr)?; - - // Get current file position for lookup - let file = self - .file - .as_mut() - .ok_or_else(|| Error::Other("No file open".to_string()))?; - file.seek(SeekFrom::End(0))?; - let position = file.stream_position()? as u32; - - // Create new location - let new_location = Location { file_nr, position }; - - // Calculate CRC of data - let crc = calculate_crc(data); - - // Create header - let mut header = vec![0u8; HEADER_SIZE]; - - // Write size (2 bytes) - let size = data.len() as u16; // Safe now because we've validated the size - header[0] = (size & 0xFF) as u8; - header[1] = ((size >> 8) & 0xFF) as u8; - - // Write CRC (4 bytes) - header[2] = (crc & 0xFF) as u8; - header[3] = ((crc >> 8) & 0xFF) as u8; - header[4] = ((crc >> 16) & 0xFF) as u8; - header[5] = ((crc >> 24) & 0xFF) as u8; - - // Write previous location (6 bytes) - let prev_bytes = old_location.to_bytes(); - for (i, &byte) in prev_bytes.iter().enumerate().take(6) { - header[6 + i] = byte; - } - - // Write header - file.write_all(&header)?; - - // Write actual data - file.write_all(data)?; - file.flush()?; - - // Update lookup table with new position - self.lookup.set(id, new_location)?; - - Ok(()) - } - - /// Retrieves data at the specified location - pub(crate) fn get_(&mut self, location: Location) -> Result, Error> { - if location.position == 0 { - return Err(Error::NotFound(format!( - "Record not found, location: {:?}", - location - ))); - } - - // Select the file - self.db_file_select(location.file_nr)?; - - let file = self - .file - .as_mut() - .ok_or_else(|| Error::Other("No file open".to_string()))?; - - // Read header - file.seek(SeekFrom::Start(location.position as u64))?; - let mut header = vec![0u8; HEADER_SIZE]; - file.read_exact(&mut header)?; - - // Parse size (2 bytes) - let size = u16::from(header[0]) | (u16::from(header[1]) << 8); - - // Parse CRC (4 bytes) - let stored_crc = u32::from(header[2]) - | (u32::from(header[3]) << 8) - | (u32::from(header[4]) << 16) - | (u32::from(header[5]) << 24); - - // Read data - let mut data = vec![0u8; size as usize]; - file.read_exact(&mut data)?; - - // Verify CRC - let calculated_crc = calculate_crc(&data); - if calculated_crc != stored_crc { - return Err(Error::DataCorruption( - "CRC mismatch: data corruption detected".to_string(), - )); - } - - Ok(data) - } - - /// Retrieves the previous position for a record (for history tracking) - pub(crate) fn get_prev_pos_(&mut self, location: Location) -> Result { - if location.position == 0 { - return Err(Error::NotFound("Record not found".to_string())); - } - - // Select the file - self.db_file_select(location.file_nr)?; - - let file = self - .file - .as_mut() - .ok_or_else(|| Error::Other("No file open".to_string()))?; - - // Skip size and CRC (6 bytes) - file.seek(SeekFrom::Start(location.position as u64 + 6))?; - - // Read previous location (6 bytes) - let mut prev_bytes = vec![0u8; 6]; - file.read_exact(&mut prev_bytes)?; - - // Create location from bytes - Location::from_bytes(&prev_bytes, 6) - } - - /// Deletes the record at the specified location - pub(crate) fn delete_(&mut self, id: u32, location: Location) -> Result<(), Error> { - if location.position == 0 { - return Err(Error::NotFound("Record not found".to_string())); - } - - // Select the file - self.db_file_select(location.file_nr)?; - - let file = self - .file - .as_mut() - .ok_or_else(|| Error::Other("No file open".to_string()))?; - - // Read size first - file.seek(SeekFrom::Start(location.position as u64))?; - let mut size_bytes = vec![0u8; 2]; - file.read_exact(&mut size_bytes)?; - let size = u16::from(size_bytes[0]) | (u16::from(size_bytes[1]) << 8); - - // Write zeros for the entire record (header + data) - let zeros = vec![0u8; HEADER_SIZE + size as usize]; - file.seek(SeekFrom::Start(location.position as u64))?; - file.write_all(&zeros)?; - - // Clear lookup entry - self.lookup.delete(id)?; - - Ok(()) - } - - /// Condenses the database by removing empty records and updating positions - pub fn condense(&mut self) -> Result<(), Error> { - // Create a temporary directory - let temp_path = self.path.join("temp"); - fs::create_dir_all(&temp_path)?; - - // Get all file numbers - let mut file_numbers = Vec::new(); - for entry in fs::read_dir(&self.path)? { - let entry = entry?; - let path = entry.path(); - - if path.is_file() && path.extension().map_or(false, |ext| ext == "db") { - if let Some(stem) = path.file_stem() { - if let Ok(file_nr) = stem.to_string_lossy().parse::() { - file_numbers.push(file_nr); - } - } - } - } - - // Process each file - for file_nr in file_numbers { - let src_path = self.path.join(format!("{}.db", file_nr)); - let temp_file_path = temp_path.join(format!("{}.db", file_nr)); - - // Create new file - let mut temp_file = File::create(&temp_file_path)?; - temp_file.write_all(&[0u8])?; // Initialize with a byte - - // Open source file - let mut src_file = File::open(&src_path)?; - - // Read and process records - let mut buffer = vec![0u8; 1024]; // Read in chunks - let mut _position = 0; - - while let Ok(bytes_read) = src_file.read(&mut buffer) { - if bytes_read == 0 { - break; - } - - // Process the chunk - // This is a simplified version - in a real implementation, - // you would need to handle records that span chunk boundaries - - _position += bytes_read; - } - - // TODO: Implement proper record copying and position updating - // This would involve: - // 1. Reading each record from the source file - // 2. If not deleted (all zeros), copy to temp file - // 3. Update lookup table with new positions - } - - // TODO: Replace original files with temp files - - // Clean up - fs::remove_dir_all(&temp_path)?; - - Ok(()) - } -} - -/// Calculates CRC32 for the data -fn calculate_crc(data: &[u8]) -> u32 { - let mut hasher = Hasher::new(); - hasher.update(data); - hasher.finalize() -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - - use crate::{OurDB, OurDBConfig, OurDBSetArgs}; - use std::env::temp_dir; - use std::time::{SystemTime, UNIX_EPOCH}; - - fn get_temp_dir() -> PathBuf { - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - temp_dir().join(format!("ourdb_backend_test_{}", timestamp)) - } - - #[test] - fn test_backend_operations() { - let temp_dir = get_temp_dir(); - - let config = OurDBConfig { - path: temp_dir.clone(), - incremental_mode: false, - file_size: None, - keysize: None, - reset: None, // Don't reset existing database - }; - - let mut db = OurDB::new(config).unwrap(); - - // Test set and get - let test_data = b"Test data for backend operations"; - let id = 1; - - db.set(OurDBSetArgs { - id: Some(id), - data: test_data, - }) - .unwrap(); - - let retrieved = db.get(id).unwrap(); - assert_eq!(retrieved, test_data); - - // Clean up - db.destroy().unwrap(); - } -} diff --git a/ourdb/src/error.rs b/ourdb/src/error.rs deleted file mode 100644 index 5b240d2..0000000 --- a/ourdb/src/error.rs +++ /dev/null @@ -1,41 +0,0 @@ -use thiserror::Error; - -/// Error types for OurDB operations -#[derive(Error, Debug)] -pub enum Error { - /// IO errors from file operations - #[error("IO error: {0}")] - Io(#[from] std::io::Error), - - /// Data corruption errors - #[error("Data corruption: {0}")] - DataCorruption(String), - - /// Invalid operation errors - #[error("Invalid operation: {0}")] - InvalidOperation(String), - - /// Lookup table errors - #[error("Lookup error: {0}")] - LookupError(String), - - /// Record not found errors - #[error("Record not found: {0}")] - NotFound(String), - - /// Other errors - #[error("Error: {0}")] - Other(String), -} - -impl From for Error { - fn from(msg: String) -> Self { - Error::Other(msg) - } -} - -impl From<&str> for Error { - fn from(msg: &str) -> Self { - Error::Other(msg.to_string()) - } -} diff --git a/ourdb/src/lib.rs b/ourdb/src/lib.rs deleted file mode 100644 index aee3a4a..0000000 --- a/ourdb/src/lib.rs +++ /dev/null @@ -1,293 +0,0 @@ -mod backend; -mod error; -mod location; -mod lookup; - -pub use error::Error; -pub use location::Location; -pub use lookup::LookupTable; - -use std::fs::File; -use std::path::PathBuf; - -/// OurDB is a lightweight, efficient key-value database implementation that provides -/// data persistence with history tracking capabilities. -pub struct OurDB { - /// Directory path for storage - path: PathBuf, - /// Whether to use auto-increment mode - incremental_mode: bool, - /// Maximum file size (default: 500MB) - file_size: u32, - /// Lookup table for mapping keys to locations - lookup: LookupTable, - /// Currently open file - file: Option, - /// Current file number - file_nr: u16, - /// Last used file number - last_used_file_nr: u16, -} - -/// Configuration for creating a new OurDB instance -pub struct OurDBConfig { - /// Directory path for storage - pub path: PathBuf, - /// Whether to use auto-increment mode - pub incremental_mode: bool, - /// Maximum file size (default: 500MB) - pub file_size: Option, - /// Lookup table key size (default: 4) - /// - 2: For databases with < 65,536 records (single file) - /// - 3: For databases with < 16,777,216 records (single file) - /// - 4: For databases with < 4,294,967,296 records (single file) - /// - 6: For large databases requiring multiple files (default) - pub keysize: Option, - /// Whether to reset the database if it exists (default: false) - pub reset: Option, -} - -/// Arguments for setting a value in OurDB -pub struct OurDBSetArgs<'a> { - /// ID for the record (optional in incremental mode) - pub id: Option, - /// Data to store - pub data: &'a [u8], -} - -impl OurDB { - /// Creates a new OurDB instance with the given configuration - pub fn new(config: OurDBConfig) -> Result { - // If reset is true and the path exists, remove it first - if config.reset.unwrap_or(false) && config.path.exists() { - std::fs::remove_dir_all(&config.path)?; - } - - // Create directory if it doesn't exist - std::fs::create_dir_all(&config.path)?; - - // Create lookup table - let lookup_path = config.path.join("lookup"); - std::fs::create_dir_all(&lookup_path)?; - - let lookup_config = lookup::LookupConfig { - size: 1000000, // Default size - keysize: config.keysize.unwrap_or(4), - lookuppath: lookup_path.to_string_lossy().to_string(), - incremental_mode: config.incremental_mode, - }; - - let lookup = LookupTable::new(lookup_config)?; - - let mut db = OurDB { - path: config.path, - incremental_mode: config.incremental_mode, - file_size: config.file_size.unwrap_or(500 * (1 << 20)), // 500MB default - lookup, - file: None, - file_nr: 0, - last_used_file_nr: 0, - }; - - // Load existing metadata if available - db.load()?; - - Ok(db) - } - - /// Sets a value in the database - /// - /// In incremental mode: - /// - If ID is provided, it updates an existing record - /// - If ID is not provided, it creates a new record with auto-generated ID - /// - /// In key-value mode: - /// - ID must be provided - pub fn set(&mut self, args: OurDBSetArgs) -> Result { - if self.incremental_mode { - if let Some(id) = args.id { - // This is an update - let location = self.lookup.get(id)?; - if location.position == 0 { - return Err(Error::InvalidOperation( - "Cannot set ID for insertions when incremental mode is enabled".to_string(), - )); - } - - self.set_(id, location, args.data)?; - Ok(id) - } else { - // This is an insert - let id = self.lookup.get_next_id()?; - self.set_(id, Location::default(), args.data)?; - Ok(id) - } - } else { - // Using key-value mode - let id = args.id.ok_or_else(|| { - Error::InvalidOperation( - "ID must be provided when incremental is disabled".to_string(), - ) - })?; - - let location = self.lookup.get(id)?; - self.set_(id, location, args.data)?; - Ok(id) - } - } - - /// Retrieves data stored at the specified key position - pub fn get(&mut self, id: u32) -> Result, Error> { - let location = self.lookup.get(id)?; - self.get_(location) - } - - /// Retrieves a list of previous values for the specified key - /// - /// The depth parameter controls how many historical values to retrieve (maximum) - pub fn get_history(&mut self, id: u32, depth: u8) -> Result>, Error> { - let mut result = Vec::new(); - let mut current_location = self.lookup.get(id)?; - - // Traverse the history chain up to specified depth - for _ in 0..depth { - // Get current value - let data = self.get_(current_location)?; - result.push(data); - - // Try to get previous location - match self.get_prev_pos_(current_location) { - Ok(location) => { - if location.position == 0 { - break; - } - current_location = location; - } - Err(_) => break, - } - } - - Ok(result) - } - - /// Deletes the data at the specified key position - pub fn delete(&mut self, id: u32) -> Result<(), Error> { - let location = self.lookup.get(id)?; - self.delete_(id, location)?; - self.lookup.delete(id)?; - Ok(()) - } - - /// Returns the next ID which will be used when storing in incremental mode - pub fn get_next_id(&mut self) -> Result { - if !self.incremental_mode { - return Err(Error::InvalidOperation( - "Incremental mode is not enabled".to_string(), - )); - } - self.lookup.get_next_id() - } - - /// Closes the database, ensuring all data is saved - pub fn close(&mut self) -> Result<(), Error> { - self.save()?; - self.close_(); - Ok(()) - } - - /// Destroys the database, removing all files - pub fn destroy(&mut self) -> Result<(), Error> { - let _ = self.close(); - std::fs::remove_dir_all(&self.path)?; - Ok(()) - } - - // Helper methods - fn lookup_dump_path(&self) -> PathBuf { - self.path.join("lookup_dump.db") - } - - fn load(&mut self) -> Result<(), Error> { - let dump_path = self.lookup_dump_path(); - if dump_path.exists() { - self.lookup.import_sparse(&dump_path.to_string_lossy())?; - } - Ok(()) - } - - fn save(&mut self) -> Result<(), Error> { - self.lookup - .export_sparse(&self.lookup_dump_path().to_string_lossy())?; - Ok(()) - } - - fn close_(&mut self) { - self.file = None; - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::env::temp_dir; - use std::time::{SystemTime, UNIX_EPOCH}; - - fn get_temp_dir() -> PathBuf { - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - temp_dir().join(format!("ourdb_test_{}", timestamp)) - } - - #[test] - fn test_basic_operations() { - let temp_dir = get_temp_dir(); - - let config = OurDBConfig { - path: temp_dir.clone(), - incremental_mode: true, - file_size: None, - keysize: None, - reset: None, // Don't reset existing database - }; - - let mut db = OurDB::new(config).unwrap(); - - // Test set and get - let test_data = b"Hello, OurDB!"; - let id = db - .set(OurDBSetArgs { - id: None, - data: test_data, - }) - .unwrap(); - - let retrieved = db.get(id).unwrap(); - assert_eq!(retrieved, test_data); - - // Test update - let updated_data = b"Updated data"; - db.set(OurDBSetArgs { - id: Some(id), - data: updated_data, - }) - .unwrap(); - - let retrieved = db.get(id).unwrap(); - assert_eq!(retrieved, updated_data); - - // Test history - let history = db.get_history(id, 2).unwrap(); - assert_eq!(history.len(), 2); - assert_eq!(history[0], updated_data); - assert_eq!(history[1], test_data); - - // Test delete - db.delete(id).unwrap(); - assert!(db.get(id).is_err()); - - // Clean up - db.destroy().unwrap(); - } -} diff --git a/ourdb/src/location.rs b/ourdb/src/location.rs deleted file mode 100644 index 06a7a89..0000000 --- a/ourdb/src/location.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::error::Error; - -/// Location represents a physical position in a database file -/// -/// It consists of a file number and a position within that file. -/// This allows OurDB to span multiple files for large datasets. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub struct Location { - /// File number (0-65535) - pub file_nr: u16, - /// Position within the file - pub position: u32, -} - -impl Location { - /// Creates a new Location from bytes based on keysize - /// - /// - keysize = 2: Only position (2 bytes), file_nr = 0 - /// - keysize = 3: Only position (3 bytes), file_nr = 0 - /// - keysize = 4: Only position (4 bytes), file_nr = 0 - /// - keysize = 6: file_nr (2 bytes) + position (4 bytes) - pub fn from_bytes(bytes: &[u8], keysize: u8) -> Result { - // Validate keysize - if ![2, 3, 4, 6].contains(&keysize) { - return Err(Error::InvalidOperation(format!( - "Invalid keysize: {}", - keysize - ))); - } - - // Create padded bytes - let mut padded = vec![0u8; keysize as usize]; - if bytes.len() > keysize as usize { - return Err(Error::InvalidOperation( - "Input bytes exceed keysize".to_string(), - )); - } - let start_idx = keysize as usize - bytes.len(); - - for (i, &b) in bytes.iter().enumerate() { - if i + start_idx < padded.len() { - padded[start_idx + i] = b; - } - } - - let mut location = Location::default(); - - match keysize { - 2 => { - // Only position, 2 bytes big endian - location.position = u32::from(padded[0]) << 8 | u32::from(padded[1]); - location.file_nr = 0; - - // Verify limits - if location.position > 0xFFFF { - return Err(Error::InvalidOperation( - "Position exceeds max value for keysize=2 (max 65535)".to_string(), - )); - } - } - 3 => { - // Only position, 3 bytes big endian - location.position = - u32::from(padded[0]) << 16 | u32::from(padded[1]) << 8 | u32::from(padded[2]); - location.file_nr = 0; - - // Verify limits - if location.position > 0xFFFFFF { - return Err(Error::InvalidOperation( - "Position exceeds max value for keysize=3 (max 16777215)".to_string(), - )); - } - } - 4 => { - // Only position, 4 bytes big endian - location.position = u32::from(padded[0]) << 24 - | u32::from(padded[1]) << 16 - | u32::from(padded[2]) << 8 - | u32::from(padded[3]); - location.file_nr = 0; - } - 6 => { - // 2 bytes file_nr + 4 bytes position, all big endian - location.file_nr = u16::from(padded[0]) << 8 | u16::from(padded[1]); - location.position = u32::from(padded[2]) << 24 - | u32::from(padded[3]) << 16 - | u32::from(padded[4]) << 8 - | u32::from(padded[5]); - } - _ => unreachable!(), - } - - Ok(location) - } - - /// Converts the location to bytes (always 6 bytes) - /// - /// Format: [file_nr (2 bytes)][position (4 bytes)] - pub fn to_bytes(&self) -> Vec { - let mut bytes = Vec::with_capacity(6); - - // Put file_nr first (2 bytes) - bytes.push((self.file_nr >> 8) as u8); - bytes.push(self.file_nr as u8); - - // Put position next (4 bytes) - bytes.push((self.position >> 24) as u8); - bytes.push((self.position >> 16) as u8); - bytes.push((self.position >> 8) as u8); - bytes.push(self.position as u8); - - bytes - } - - /// Converts the location to a u64 value - /// - /// The file_nr is stored in the most significant bits - pub fn to_u64(&self) -> u64 { - (u64::from(self.file_nr) << 32) | u64::from(self.position) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_location_from_bytes_keysize_2() { - let bytes = vec![0x12, 0x34]; - let location = Location::from_bytes(&bytes, 2).unwrap(); - assert_eq!(location.file_nr, 0); - assert_eq!(location.position, 0x1234); - } - - #[test] - fn test_location_from_bytes_keysize_3() { - let bytes = vec![0x12, 0x34, 0x56]; - let location = Location::from_bytes(&bytes, 3).unwrap(); - assert_eq!(location.file_nr, 0); - assert_eq!(location.position, 0x123456); - } - - #[test] - fn test_location_from_bytes_keysize_4() { - let bytes = vec![0x12, 0x34, 0x56, 0x78]; - let location = Location::from_bytes(&bytes, 4).unwrap(); - assert_eq!(location.file_nr, 0); - assert_eq!(location.position, 0x12345678); - } - - #[test] - fn test_location_from_bytes_keysize_6() { - let bytes = vec![0xAB, 0xCD, 0x12, 0x34, 0x56, 0x78]; - let location = Location::from_bytes(&bytes, 6).unwrap(); - assert_eq!(location.file_nr, 0xABCD); - assert_eq!(location.position, 0x12345678); - } - - #[test] - fn test_location_to_bytes() { - let location = Location { - file_nr: 0xABCD, - position: 0x12345678, - }; - let bytes = location.to_bytes(); - assert_eq!(bytes, vec![0xAB, 0xCD, 0x12, 0x34, 0x56, 0x78]); - } - - #[test] - fn test_location_to_u64() { - let location = Location { - file_nr: 0xABCD, - position: 0x12345678, - }; - let value = location.to_u64(); - assert_eq!(value, 0xABCD_0000_0000 | 0x12345678); - } -} diff --git a/ourdb/src/lookup.rs b/ourdb/src/lookup.rs deleted file mode 100644 index 34d4ed4..0000000 --- a/ourdb/src/lookup.rs +++ /dev/null @@ -1,540 +0,0 @@ -use std::fs::{self, File, OpenOptions}; -use std::io::{Read, Seek, SeekFrom, Write}; -use std::path::Path; - -use crate::error::Error; -use crate::location::Location; - -const DATA_FILE_NAME: &str = "data"; -const INCREMENTAL_FILE_NAME: &str = ".inc"; - -/// Configuration for creating a new lookup table -pub struct LookupConfig { - /// Size of the lookup table - pub size: u32, - /// Size of each entry in bytes (2-6) - /// - 2: For databases with < 65,536 records (single file) - /// - 3: For databases with < 16,777,216 records (single file) - /// - 4: For databases with < 4,294,967,296 records (single file) - /// - 6: For large databases requiring multiple files - pub keysize: u8, - /// Path for disk-based lookup - pub lookuppath: String, - /// Whether to use incremental mode - pub incremental_mode: bool, -} - -/// Lookup table maps keys to physical locations in the backend storage -pub struct LookupTable { - /// Size of each entry in bytes (2-6) - keysize: u8, - /// Path for disk-based lookup - lookuppath: String, - /// In-memory data for memory-based lookup - data: Vec, - /// Next empty slot if incremental mode is enabled - incremental: Option, -} - -impl LookupTable { - /// Returns the keysize of this lookup table - pub fn keysize(&self) -> u8 { - self.keysize - } - - /// Creates a new lookup table with the given configuration - pub fn new(config: LookupConfig) -> Result { - // Verify keysize is valid - if ![2, 3, 4, 6].contains(&config.keysize) { - return Err(Error::InvalidOperation(format!( - "Invalid keysize: {}", - config.keysize - ))); - } - - let incremental = if config.incremental_mode { - Some(get_incremental_info(&config)?) - } else { - None - }; - - if !config.lookuppath.is_empty() { - // Create directory if it doesn't exist - fs::create_dir_all(&config.lookuppath)?; - - // For disk-based lookup, create empty file if it doesn't exist - let data_path = Path::new(&config.lookuppath).join(DATA_FILE_NAME); - if !data_path.exists() { - let data = vec![0u8; config.size as usize * config.keysize as usize]; - fs::write(&data_path, &data)?; - } - - Ok(LookupTable { - data: Vec::new(), - keysize: config.keysize, - lookuppath: config.lookuppath, - incremental, - }) - } else { - // For memory-based lookup - Ok(LookupTable { - data: vec![0u8; config.size as usize * config.keysize as usize], - keysize: config.keysize, - lookuppath: String::new(), - incremental, - }) - } - } - - /// Gets a location for the given ID - pub fn get(&self, id: u32) -> Result { - let entry_size = self.keysize as usize; - - if !self.lookuppath.is_empty() { - // Disk-based lookup - let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); - - // Check file size first - let file_size = fs::metadata(&data_path)?.len(); - let start_pos = id as u64 * entry_size as u64; - - if start_pos + entry_size as u64 > file_size { - return Err(Error::LookupError(format!( - "Invalid read for get in lut: {}: {} would exceed file size {}", - self.lookuppath, - start_pos + entry_size as u64, - file_size - ))); - } - - // Read directly from file - let mut file = File::open(&data_path)?; - file.seek(SeekFrom::Start(start_pos))?; - - let mut data = vec![0u8; entry_size]; - let bytes_read = file.read(&mut data)?; - - if bytes_read < entry_size { - return Err(Error::LookupError(format!( - "Incomplete read: expected {} bytes but got {}", - entry_size, bytes_read - ))); - } - - return Location::from_bytes(&data, self.keysize); - } - - // Memory-based lookup - if (id * self.keysize as u32) as usize >= self.data.len() { - return Err(Error::LookupError("Index out of bounds".to_string())); - } - - let start = (id * self.keysize as u32) as usize; - let end = start + entry_size; - - Location::from_bytes(&self.data[start..end], self.keysize) - } - - /// Sets a location for the given ID - pub fn set(&mut self, id: u32, location: Location) -> Result<(), Error> { - let entry_size = self.keysize as usize; - - // Handle incremental mode - if let Some(incremental) = self.incremental { - if id == incremental { - self.increment_index()?; - } - - if id > incremental { - return Err(Error::InvalidOperation( - "Cannot set ID for insertions when incremental mode is enabled".to_string(), - )); - } - } - - // Convert location to bytes based on keysize - let location_bytes = match self.keysize { - 2 => { - if location.file_nr != 0 { - return Err(Error::InvalidOperation( - "file_nr must be 0 for keysize=2".to_string(), - )); - } - if location.position > 0xFFFF { - return Err(Error::InvalidOperation( - "position exceeds max value for keysize=2 (max 65535)".to_string(), - )); - } - vec![(location.position >> 8) as u8, location.position as u8] - } - 3 => { - if location.file_nr != 0 { - return Err(Error::InvalidOperation( - "file_nr must be 0 for keysize=3".to_string(), - )); - } - if location.position > 0xFFFFFF { - return Err(Error::InvalidOperation( - "position exceeds max value for keysize=3 (max 16777215)".to_string(), - )); - } - vec![ - (location.position >> 16) as u8, - (location.position >> 8) as u8, - location.position as u8, - ] - } - 4 => { - if location.file_nr != 0 { - return Err(Error::InvalidOperation( - "file_nr must be 0 for keysize=4".to_string(), - )); - } - vec![ - (location.position >> 24) as u8, - (location.position >> 16) as u8, - (location.position >> 8) as u8, - location.position as u8, - ] - } - 6 => { - // Full location with file_nr and position - location.to_bytes() - } - _ => { - return Err(Error::InvalidOperation(format!( - "Invalid keysize: {}", - self.keysize - ))) - } - }; - - if !self.lookuppath.is_empty() { - // Disk-based lookup - let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); - let mut file = OpenOptions::new().write(true).open(data_path)?; - - let start_pos = id as u64 * entry_size as u64; - file.seek(SeekFrom::Start(start_pos))?; - file.write_all(&location_bytes)?; - } else { - // Memory-based lookup - let start = (id * self.keysize as u32) as usize; - if start + entry_size > self.data.len() { - return Err(Error::LookupError("Index out of bounds".to_string())); - } - - for (i, &byte) in location_bytes.iter().enumerate() { - self.data[start + i] = byte; - } - } - - Ok(()) - } - - /// Deletes an entry for the given ID - pub fn delete(&mut self, id: u32) -> Result<(), Error> { - // Set location to all zeros - self.set(id, Location::default()) - } - - /// Gets the next available ID in incremental mode - pub fn get_next_id(&self) -> Result { - let incremental = self.incremental.ok_or_else(|| { - Error::InvalidOperation("Lookup table not in incremental mode".to_string()) - })?; - - let table_size = if !self.lookuppath.is_empty() { - let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); - fs::metadata(data_path)?.len() as u32 - } else { - self.data.len() as u32 - }; - - if incremental * self.keysize as u32 >= table_size { - return Err(Error::LookupError("Lookup table is full".to_string())); - } - - Ok(incremental) - } - - /// Increments the index in incremental mode - pub fn increment_index(&mut self) -> Result<(), Error> { - let mut incremental = self.incremental.ok_or_else(|| { - Error::InvalidOperation("Lookup table not in incremental mode".to_string()) - })?; - - incremental += 1; - self.incremental = Some(incremental); - - if !self.lookuppath.is_empty() { - let inc_path = Path::new(&self.lookuppath).join(INCREMENTAL_FILE_NAME); - fs::write(inc_path, incremental.to_string())?; - } - - Ok(()) - } - - /// Exports the lookup table to a file - pub fn export_data(&self, path: &str) -> Result<(), Error> { - if !self.lookuppath.is_empty() { - // For disk-based lookup, just copy the file - let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); - fs::copy(data_path, path)?; - } else { - // For memory-based lookup, write the data to file - fs::write(path, &self.data)?; - } - Ok(()) - } - - /// Imports the lookup table from a file - pub fn import_data(&mut self, path: &str) -> Result<(), Error> { - if !self.lookuppath.is_empty() { - // For disk-based lookup, copy the file - let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); - fs::copy(path, data_path)?; - } else { - // For memory-based lookup, read the data from file - self.data = fs::read(path)?; - } - Ok(()) - } - - /// Exports only non-zero entries to save space - pub fn export_sparse(&self, path: &str) -> Result<(), Error> { - let mut output = Vec::new(); - let entry_size = self.keysize as usize; - - if !self.lookuppath.is_empty() { - // For disk-based lookup - let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); - let mut file = File::open(&data_path)?; - let file_size = fs::metadata(&data_path)?.len(); - let max_entries = file_size / entry_size as u64; - - for id in 0..max_entries { - file.seek(SeekFrom::Start(id * entry_size as u64))?; - - let mut buffer = vec![0u8; entry_size]; - let bytes_read = file.read(&mut buffer)?; - - if bytes_read < entry_size { - break; - } - - // Check if entry is non-zero - if buffer.iter().any(|&b| b != 0) { - // Write ID (4 bytes) + entry - output.extend_from_slice(&(id as u32).to_be_bytes()); - output.extend_from_slice(&buffer); - } - } - } else { - // For memory-based lookup - let max_entries = self.data.len() / entry_size; - - for id in 0..max_entries { - let start = id * entry_size; - let entry = &self.data[start..start + entry_size]; - - // Check if entry is non-zero - if entry.iter().any(|&b| b != 0) { - // Write ID (4 bytes) + entry - output.extend_from_slice(&(id as u32).to_be_bytes()); - output.extend_from_slice(entry); - } - } - } - - // Write the output to file - fs::write(path, &output)?; - Ok(()) - } - - /// Imports sparse data (only non-zero entries) - pub fn import_sparse(&mut self, path: &str) -> Result<(), Error> { - let data = fs::read(path)?; - let entry_size = self.keysize as usize; - let record_size = 4 + entry_size; // ID (4 bytes) + entry - - if data.len() % record_size != 0 { - return Err(Error::DataCorruption( - "Invalid sparse data format: size mismatch".to_string(), - )); - } - - for chunk_start in (0..data.len()).step_by(record_size) { - if chunk_start + record_size > data.len() { - break; - } - - // Extract ID (4 bytes) - let id_bytes = &data[chunk_start..chunk_start + 4]; - let id = u32::from_be_bytes([id_bytes[0], id_bytes[1], id_bytes[2], id_bytes[3]]); - - // Extract entry - let entry = &data[chunk_start + 4..chunk_start + record_size]; - - // Create location from entry - let location = Location::from_bytes(entry, self.keysize)?; - - // Set the entry - self.set(id, location)?; - } - - Ok(()) - } - - /// Finds the highest ID with a non-zero entry - pub fn find_last_entry(&mut self) -> Result { - let mut last_id = 0u32; - let entry_size = self.keysize as usize; - - if !self.lookuppath.is_empty() { - // For disk-based lookup - let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); - let mut file = File::open(&data_path)?; - let file_size = fs::metadata(&data_path)?.len(); - - let mut buffer = vec![0u8; entry_size]; - let mut pos = 0u32; - - while (pos as u64 * entry_size as u64) < file_size { - file.seek(SeekFrom::Start(pos as u64 * entry_size as u64))?; - - let bytes_read = file.read(&mut buffer)?; - if bytes_read == 0 || bytes_read < entry_size { - break; - } - - let location = Location::from_bytes(&buffer, self.keysize)?; - if location.position != 0 || location.file_nr != 0 { - last_id = pos; - } - - pos += 1; - } - } else { - // For memory-based lookup - for i in 0..(self.data.len() / entry_size) as u32 { - if let Ok(location) = self.get(i) { - if location.position != 0 || location.file_nr != 0 { - last_id = i; - } - } - } - } - - Ok(last_id) - } -} - -/// Helper function to get the incremental value -fn get_incremental_info(config: &LookupConfig) -> Result { - if !config.incremental_mode { - return Ok(0); - } - - if !config.lookuppath.is_empty() { - let inc_path = Path::new(&config.lookuppath).join(INCREMENTAL_FILE_NAME); - - if !inc_path.exists() { - // Create a separate file for storing the incremental value - fs::write(&inc_path, "1")?; - } - - let inc_str = fs::read_to_string(&inc_path)?; - let incremental = match inc_str.trim().parse::() { - Ok(val) => val, - Err(_) => { - // If the value is invalid, reset it to 1 - fs::write(&inc_path, "1")?; - 1 - } - }; - - Ok(incremental) - } else { - // For memory-based lookup, start with 1 - Ok(1) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::env::temp_dir; - use std::path::PathBuf; - use std::time::{SystemTime, UNIX_EPOCH}; - - fn get_temp_dir() -> PathBuf { - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - temp_dir().join(format!("ourdb_lookup_test_{}", timestamp)) - } - - #[test] - fn test_memory_lookup() { - let config = LookupConfig { - size: 1000, - keysize: 4, - lookuppath: String::new(), - incremental_mode: true, - }; - - let mut lookup = LookupTable::new(config).unwrap(); - - // Test set and get - let location = Location { - file_nr: 0, - position: 12345, - }; - - lookup.set(1, location).unwrap(); - let retrieved = lookup.get(1).unwrap(); - - assert_eq!(retrieved.file_nr, location.file_nr); - assert_eq!(retrieved.position, location.position); - - // Test incremental mode - let next_id = lookup.get_next_id().unwrap(); - assert_eq!(next_id, 2); - - lookup.increment_index().unwrap(); - let next_id = lookup.get_next_id().unwrap(); - assert_eq!(next_id, 3); - } - - #[test] - fn test_disk_lookup() { - let temp_dir = get_temp_dir(); - fs::create_dir_all(&temp_dir).unwrap(); - - let config = LookupConfig { - size: 1000, - keysize: 4, - lookuppath: temp_dir.to_string_lossy().to_string(), - incremental_mode: true, - }; - - let mut lookup = LookupTable::new(config).unwrap(); - - // Test set and get - let location = Location { - file_nr: 0, - position: 12345, - }; - - lookup.set(1, location).unwrap(); - let retrieved = lookup.get(1).unwrap(); - - assert_eq!(retrieved.file_nr, location.file_nr); - assert_eq!(retrieved.position, location.position); - - // Clean up - fs::remove_dir_all(temp_dir).unwrap(); - } -} diff --git a/ourdb/tests/integration_tests.rs b/ourdb/tests/integration_tests.rs deleted file mode 100644 index f4e09f8..0000000 --- a/ourdb/tests/integration_tests.rs +++ /dev/null @@ -1,369 +0,0 @@ -use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; -use rand; -use std::env::temp_dir; -use std::fs; -use std::path::PathBuf; -use std::time::{SystemTime, UNIX_EPOCH}; - -// Helper function to create a unique temporary directory for tests -fn get_temp_dir() -> PathBuf { - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_nanos(); - let random_part = rand::random::(); - let dir = temp_dir().join(format!("ourdb_test_{}_{}", timestamp, random_part)); - - // Ensure the directory exists and is empty - if dir.exists() { - std::fs::remove_dir_all(&dir).unwrap(); - } - std::fs::create_dir_all(&dir).unwrap(); - - dir -} - -#[test] -fn test_basic_operations() { - let temp_dir = get_temp_dir(); - - // Create a new database with incremental mode - let config = OurDBConfig { - path: temp_dir.clone(), - incremental_mode: true, - file_size: None, - keysize: None, - reset: None, - }; - - let mut db = OurDB::new(config).unwrap(); - - // Test set and get - let test_data = b"Hello, OurDB!"; - let id = db - .set(OurDBSetArgs { - id: None, - data: test_data, - }) - .unwrap(); - - let retrieved = db.get(id).unwrap(); - assert_eq!(retrieved, test_data); - - // Test update - let updated_data = b"Updated data"; - db.set(OurDBSetArgs { - id: Some(id), - data: updated_data, - }) - .unwrap(); - - let retrieved = db.get(id).unwrap(); - assert_eq!(retrieved, updated_data); - - // Test history - let history = db.get_history(id, 2).unwrap(); - assert_eq!(history.len(), 2); - assert_eq!(history[0], updated_data); - assert_eq!(history[1], test_data); - - // Test delete - db.delete(id).unwrap(); - assert!(db.get(id).is_err()); - - // Clean up - db.destroy().unwrap(); -} - -#[test] -fn test_key_value_mode() { - let temp_dir = get_temp_dir(); - - // Create a new database with key-value mode - let config = OurDBConfig { - path: temp_dir.clone(), - incremental_mode: false, - file_size: None, - keysize: None, - reset: None, - }; - - let mut db = OurDB::new(config).unwrap(); - - // Test set with explicit ID - let test_data = b"Key-value data"; - let id = 42; - db.set(OurDBSetArgs { - id: Some(id), - data: test_data, - }) - .unwrap(); - - let retrieved = db.get(id).unwrap(); - assert_eq!(retrieved, test_data); - - // Verify next_id fails in key-value mode - assert!(db.get_next_id().is_err()); - - // Clean up - db.destroy().unwrap(); -} - -#[test] -fn test_incremental_mode() { - let temp_dir = get_temp_dir(); - - // Create a new database with incremental mode - let config = OurDBConfig { - path: temp_dir.clone(), - incremental_mode: true, - file_size: None, - keysize: None, - reset: None, - }; - - let mut db = OurDB::new(config).unwrap(); - - // Test auto-increment IDs - let data1 = b"First record"; - let id1 = db - .set(OurDBSetArgs { - id: None, - data: data1, - }) - .unwrap(); - - let data2 = b"Second record"; - let id2 = db - .set(OurDBSetArgs { - id: None, - data: data2, - }) - .unwrap(); - - // IDs should be sequential - assert_eq!(id2, id1 + 1); - - // Verify get_next_id works - let next_id = db.get_next_id().unwrap(); - assert_eq!(next_id, id2 + 1); - - // Clean up - db.destroy().unwrap(); -} - -#[test] -fn test_persistence() { - let temp_dir = get_temp_dir(); - - // Create data in a new database - { - let config = OurDBConfig { - path: temp_dir.clone(), - incremental_mode: true, - file_size: None, - keysize: None, - reset: None, - }; - - let mut db = OurDB::new(config).unwrap(); - - let test_data = b"Persistent data"; - let id = db - .set(OurDBSetArgs { - id: None, - data: test_data, - }) - .unwrap(); - - // Explicitly close the database - db.close().unwrap(); - - // ID should be 1 in a new database - assert_eq!(id, 1); - } - - // Reopen the database and verify data persists - { - let config = OurDBConfig { - path: temp_dir.clone(), - incremental_mode: true, - file_size: None, - keysize: None, - reset: None, - }; - - let mut db = OurDB::new(config).unwrap(); - - // Verify data is still there - let retrieved = db.get(1).unwrap(); - assert_eq!(retrieved, b"Persistent data"); - - // Verify incremental counter persisted - let next_id = db.get_next_id().unwrap(); - assert_eq!(next_id, 2); - - // Clean up - db.destroy().unwrap(); - } -} - -#[test] -fn test_different_keysizes() { - for keysize in [2, 3, 4, 6].iter() { - let temp_dir = get_temp_dir(); - - // Ensure the directory exists - std::fs::create_dir_all(&temp_dir).unwrap(); - - // Create a new database with specified keysize - let config = OurDBConfig { - path: temp_dir.clone(), - incremental_mode: true, - file_size: None, - keysize: Some(*keysize), - reset: None, - }; - - let mut db = OurDB::new(config).unwrap(); - - // Test basic operations - let test_data = b"Keysize test data"; - let id = db - .set(OurDBSetArgs { - id: None, - data: test_data, - }) - .unwrap(); - - let retrieved = db.get(id).unwrap(); - assert_eq!(retrieved, test_data); - - // Clean up - db.destroy().unwrap(); - } -} - -#[test] -fn test_large_data() { - let temp_dir = get_temp_dir(); - - // Create a new database - let config = OurDBConfig { - path: temp_dir.clone(), - incremental_mode: true, - file_size: None, - keysize: None, - reset: None, - }; - - let mut db = OurDB::new(config).unwrap(); - - // Create a large data set (60KB - within the 64KB limit) - let large_data = vec![b'X'; 60 * 1024]; - - // Store and retrieve large data - let id = db - .set(OurDBSetArgs { - id: None, - data: &large_data, - }) - .unwrap(); - let retrieved = db.get(id).unwrap(); - - assert_eq!(retrieved.len(), large_data.len()); - assert_eq!(retrieved, large_data); - - // Clean up - db.destroy().unwrap(); -} - -#[test] -fn test_exceed_size_limit() { - let temp_dir = get_temp_dir(); - - // Create a new database - let config = OurDBConfig { - path: temp_dir.clone(), - incremental_mode: true, - file_size: None, - keysize: None, - reset: None, - }; - - let mut db = OurDB::new(config).unwrap(); - - // Create data larger than the 64KB limit (70KB) - let oversized_data = vec![b'X'; 70 * 1024]; - - // Attempt to store data that exceeds the size limit - let result = db.set(OurDBSetArgs { - id: None, - data: &oversized_data, - }); - - // Verify that an error is returned - assert!( - result.is_err(), - "Expected an error when storing data larger than 64KB" - ); - - // Clean up - db.destroy().unwrap(); -} - -#[test] -fn test_multiple_files() { - let temp_dir = get_temp_dir(); - - // Create a new database with small file size to force multiple files - let config = OurDBConfig { - path: temp_dir.clone(), - incremental_mode: true, - file_size: Some(1024), // Very small file size (1KB) - keysize: Some(6), // 6-byte keysize for multiple files - reset: None, - }; - - let mut db = OurDB::new(config).unwrap(); - - // Store enough data to span multiple files - let data_size = 500; // bytes per record - let test_data = vec![b'A'; data_size]; - - let mut ids = Vec::new(); - for _ in 0..10 { - let id = db - .set(OurDBSetArgs { - id: None, - data: &test_data, - }) - .unwrap(); - ids.push(id); - } - - // Verify all data can be retrieved - for &id in &ids { - let retrieved = db.get(id).unwrap(); - assert_eq!(retrieved.len(), data_size); - } - - // Verify multiple files were created - let files = fs::read_dir(&temp_dir) - .unwrap() - .filter_map(Result::ok) - .filter(|entry| { - let path = entry.path(); - path.is_file() && path.extension().map_or(false, |ext| ext == "db") - }) - .count(); - - assert!( - files > 1, - "Expected multiple database files, found {}", - files - ); - - // Clean up - db.destroy().unwrap(); -} diff --git a/radixtree/ARCHITECTURE.md b/radixtree/ARCHITECTURE.md deleted file mode 100644 index 381dd59..0000000 --- a/radixtree/ARCHITECTURE.md +++ /dev/null @@ -1,787 +0,0 @@ -# RadixTree: Architecture for V to Rust Port - -## 1. Overview - -RadixTree is a space-optimized tree data structure that enables efficient string key operations with persistent storage. This document outlines the architecture for porting the RadixTree module from its original V implementation to Rust, maintaining all existing functionality while leveraging Rust's memory safety, performance, and ecosystem. - -The Rust implementation will integrate with the existing OurDB Rust implementation for persistent storage. - -```mermaid -graph TD - A[Client Code] --> B[RadixTree API] - B --> C[Node Management] - B --> D[Serialization] - B --> E[Tree Operations] - C --> F[OurDB] - D --> F - E --> C -``` - -## 2. Current Architecture (V Implementation) - -The current V implementation of RadixTree consists of the following components: - -### 2.1 Core Data Structures - -#### Node -```v -struct Node { -mut: - key_segment string // The segment of the key stored at this node - value []u8 // Value stored at this node (empty if not a leaf) - children []NodeRef // References to child nodes - is_leaf bool // Whether this node is a leaf node -} -``` - -#### NodeRef -```v -struct NodeRef { -mut: - key_part string // The key segment for this child - node_id u32 // Database ID of the node -} -``` - -#### RadixTree -```v -@[heap] -pub struct RadixTree { -mut: - db &ourdb.OurDB // Database for persistent storage - root_id u32 // Database ID of the root node -} -``` - -### 2.2 Key Operations - -1. **new()**: Creates a new radix tree with a specified database path -2. **set(key, value)**: Sets a key-value pair in the tree -3. **get(key)**: Retrieves a value by key -4. **update(prefix, new_value)**: Updates the value at a given key prefix -5. **delete(key)**: Removes a key from the tree -6. **list(prefix)**: Lists all keys with a given prefix -7. **getall(prefix)**: Gets all values for keys with a given prefix - -### 2.3 Serialization - -The V implementation uses a custom binary serialization format for nodes: -- Version byte (1 byte) -- Key segment (string) -- Value length (2 bytes) followed by value bytes -- Children count (2 bytes) followed by children -- Is leaf flag (1 byte) - -Each child is serialized as: -- Key part (string) -- Node ID (4 bytes) - -### 2.4 Integration with OurDB - -The RadixTree uses OurDB for persistent storage: -- Each node is serialized and stored as a record in OurDB -- Node references use OurDB record IDs -- The tree maintains a root node ID for traversal - -## 3. Proposed Rust Architecture - -The Rust implementation will maintain the same overall architecture while leveraging Rust's type system, ownership model, and error handling. - -### 3.1 Core Data Structures - -#### Node -```rust -pub struct Node { - key_segment: String, - value: Vec, - children: Vec, - is_leaf: bool, -} -``` - -#### NodeRef -```rust -pub struct NodeRef { - key_part: String, - node_id: u32, -} -``` - -#### RadixTree -```rust -pub struct RadixTree { - db: ourdb::OurDB, - root_id: u32, -} -``` - -### 3.2 Public API - -```rust -impl RadixTree { - /// Creates a new radix tree with the specified database path - pub fn new(path: &str, reset: bool) -> Result { - // Implementation - } - - /// Sets a key-value pair in the tree - pub fn set(&mut self, key: &str, value: Vec) -> Result<(), Error> { - // Implementation - } - - /// Gets a value by key from the tree - pub fn get(&mut self, key: &str) -> Result, Error> { - // Implementation - } - - /// Updates the value at a given key prefix - pub fn update(&mut self, prefix: &str, new_value: Vec) -> Result<(), Error> { - // Implementation - } - - /// Deletes a key from the tree - pub fn delete(&mut self, key: &str) -> Result<(), Error> { - // Implementation - } - - /// Lists all keys with a given prefix - pub fn list(&mut self, prefix: &str) -> Result, Error> { - // Implementation - } - - /// Gets all values for keys with a given prefix - pub fn getall(&mut self, prefix: &str) -> Result>, Error> { - // Implementation - } -} -``` - -### 3.3 Error Handling - -```rust -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("OurDB error: {0}")] - OurDB(#[from] ourdb::Error), - - #[error("Key not found: {0}")] - KeyNotFound(String), - - #[error("Prefix not found: {0}")] - PrefixNotFound(String), - - #[error("Serialization error: {0}")] - Serialization(String), - - #[error("Deserialization error: {0}")] - Deserialization(String), - - #[error("Invalid operation: {0}")] - InvalidOperation(String), -} -``` - -### 3.4 Serialization - -The Rust implementation will maintain the same binary serialization format for compatibility: - -```rust -const VERSION: u8 = 1; - -impl Node { - /// Serializes a node to bytes for storage - fn serialize(&self) -> Vec { - // Implementation - } - - /// Deserializes bytes to a node - fn deserialize(data: &[u8]) -> Result { - // Implementation - } -} -``` - -### 3.5 Integration with OurDB - -The Rust implementation will use the existing OurDB Rust implementation: - -```rust -impl RadixTree { - fn get_node(&mut self, node_id: u32) -> Result { - let data = self.db.get(node_id)?; - Node::deserialize(&data) - } - - fn save_node(&mut self, node_id: Option, node: &Node) -> Result { - let data = node.serialize(); - let args = ourdb::OurDBSetArgs { - id: node_id, - data: &data, - }; - Ok(self.db.set(args)?) - } -} -``` - -## 4. Implementation Strategy - -### 4.1 Phase 1: Core Data Structures and Serialization - -1. Implement the `Node` and `NodeRef` structs -2. Implement serialization and deserialization functions -3. Implement the `Error` enum for error handling - -### 4.2 Phase 2: Basic Tree Operations - -1. Implement the `RadixTree` struct with OurDB integration -2. Implement the `new()` function for creating a new tree -3. Implement the `get()` and `set()` functions for basic operations - -### 4.3 Phase 3: Advanced Tree Operations - -1. Implement the `delete()` function for removing keys -2. Implement the `update()` function for updating values -3. Implement the `list()` and `getall()` functions for prefix operations - -### 4.4 Phase 4: Testing and Optimization - -1. Port existing tests from V to Rust -2. Add new tests for Rust-specific functionality -3. Benchmark and optimize performance -4. Ensure compatibility with existing RadixTree data - -## 5. Implementation Considerations - -### 5.1 Memory Management - -Leverage Rust's ownership model for safe and efficient memory management: -- Use `String` and `Vec` for data buffers instead of raw pointers -- Use references and borrows to avoid unnecessary copying -- Implement proper RAII for resource management - -### 5.2 Error Handling - -Use Rust's `Result` type for comprehensive error handling: -- Define custom error types for RadixTree-specific errors -- Propagate errors using the `?` operator -- Provide detailed error messages -- Implement proper error conversion using the `From` trait - -### 5.3 Performance Optimizations - -Identify opportunities for performance improvements: -- Use efficient string operations for prefix matching -- Minimize database operations by caching nodes when appropriate -- Use iterators for efficient traversal -- Consider using `Cow` for string operations to avoid unnecessary cloning - -### 5.4 Compatibility - -Ensure compatibility with the V implementation: -- Maintain the same serialization format -- Ensure identical behavior for all operations -- Support reading existing RadixTree data - -## 6. Testing Strategy - -### 6.1 Unit Tests - -Write comprehensive unit tests for each component: -- Test `Node` serialization/deserialization -- Test string operations (common prefix, etc.) -- Test error handling - -### 6.2 Integration Tests - -Write integration tests for the complete system: -- Test basic CRUD operations -- Test prefix operations -- Test edge cases (empty keys, very long keys, etc.) -- Test with large datasets - -### 6.3 Compatibility Tests - -Ensure compatibility with existing RadixTree data: -- Test reading existing V-created RadixTree data -- Test writing data that can be read by the V implementation - -### 6.4 Performance Tests - -Benchmark performance against the V implementation: -- Measure throughput for set/get operations -- Measure latency for different operations -- Test with different tree sizes and key distributions - -## 7. Project Structure - -``` -radixtree/ -├── Cargo.toml -├── src/ -│ ├── lib.rs # Public API and re-exports -│ ├── node.rs # Node and NodeRef implementations -│ ├── serialize.rs # Serialization and deserialization -│ ├── error.rs # Error types -│ └── operations.rs # Tree operations implementation -├── tests/ -│ ├── basic_test.rs # Basic operations tests -│ ├── prefix_test.rs # Prefix operations tests -│ └── edge_cases.rs # Edge case tests -└── examples/ - ├── basic.rs # Basic usage example - ├── prefix.rs # Prefix operations example - └── performance.rs # Performance benchmark -``` - -## 8. Dependencies - -The Rust implementation will use the following dependencies: - -- `ourdb` for persistent storage -- `thiserror` for error handling -- `log` for logging -- `criterion` for benchmarking (dev dependency) - -## 9. Compatibility Considerations - -To ensure compatibility with the V implementation: - -1. Maintain the same serialization format for nodes -2. Ensure identical behavior for all operations -3. Support reading existing RadixTree data -4. Maintain the same performance characteristics - -## 10. Future Extensions - -Potential future extensions to consider: - -1. Async API for non-blocking operations -2. Iterator interface for efficient traversal -3. Batch operations for improved performance -4. Custom serialization formats for specific use cases -5. Compression support for values -6. Concurrency support for parallel operations - -## 11. Conclusion - -This architecture provides a roadmap for porting RadixTree from V to Rust while maintaining compatibility and leveraging Rust's strengths. The implementation will follow a phased approach, starting with core data structures and gradually building up to the complete system. - -The Rust implementation aims to be: -- **Safe**: Leveraging Rust's ownership model for memory safety -- **Fast**: Maintaining or improving performance compared to V -- **Compatible**: Working with existing RadixTree data -- **Extensible**: Providing a foundation for future enhancements -- **Well-tested**: Including comprehensive test coverage - -## 12. Implementation Files - -### 12.1 Cargo.toml - -```toml -[package] -name = "radixtree" -version = "0.1.0" -edition = "2021" -description = "A persistent radix tree implementation using OurDB for storage" -authors = ["OurWorld Team"] - -[dependencies] -ourdb = { path = "../ourdb" } -thiserror = "1.0.40" -log = "0.4.17" - -[dev-dependencies] -criterion = "0.5.1" - -[[bench]] -name = "radixtree_benchmarks" -harness = false - -[[example]] -name = "basic_usage" -path = "examples/basic_usage.rs" - -[[example]] -name = "prefix_operations" -path = "examples/prefix_operations.rs" -``` - -### 12.2 src/lib.rs - -```rust -//! RadixTree is a space-optimized tree data structure that enables efficient string key operations -//! with persistent storage using OurDB as a backend. -//! -//! This implementation provides a persistent radix tree that can be used for efficient -//! prefix-based key operations, such as auto-complete, routing tables, and more. - -mod error; -mod node; -mod operations; -mod serialize; - -pub use error::Error; -pub use node::{Node, NodeRef}; - -use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; -use std::path::PathBuf; - -/// RadixTree represents a radix tree data structure with persistent storage. -pub struct RadixTree { - db: OurDB, - root_id: u32, -} - -impl RadixTree { - /// Creates a new radix tree with the specified database path. - /// - /// # Arguments - /// - /// * `path` - The path to the database directory - /// * `reset` - Whether to reset the database if it exists - /// - /// # Returns - /// - /// A new `RadixTree` instance - /// - /// # Errors - /// - /// Returns an error if the database cannot be created or opened - pub fn new(path: &str, reset: bool) -> Result { - // Implementation will go here - unimplemented!() - } - - /// Sets a key-value pair in the tree. - /// - /// # Arguments - /// - /// * `key` - The key to set - /// * `value` - The value to set - /// - /// # Errors - /// - /// Returns an error if the operation fails - pub fn set(&mut self, key: &str, value: Vec) -> Result<(), Error> { - // Implementation will go here - unimplemented!() - } - - /// Gets a value by key from the tree. - /// - /// # Arguments - /// - /// * `key` - The key to get - /// - /// # Returns - /// - /// The value associated with the key - /// - /// # Errors - /// - /// Returns an error if the key is not found or the operation fails - pub fn get(&mut self, key: &str) -> Result, Error> { - // Implementation will go here - unimplemented!() - } - - /// Updates the value at a given key prefix. - /// - /// # Arguments - /// - /// * `prefix` - The key prefix to update - /// * `new_value` - The new value to set - /// - /// # Errors - /// - /// Returns an error if the prefix is not found or the operation fails - pub fn update(&mut self, prefix: &str, new_value: Vec) -> Result<(), Error> { - // Implementation will go here - unimplemented!() - } - - /// Deletes a key from the tree. - /// - /// # Arguments - /// - /// * `key` - The key to delete - /// - /// # Errors - /// - /// Returns an error if the key is not found or the operation fails - pub fn delete(&mut self, key: &str) -> Result<(), Error> { - // Implementation will go here - unimplemented!() - } - - /// Lists all keys with a given prefix. - /// - /// # Arguments - /// - /// * `prefix` - The prefix to search for - /// - /// # Returns - /// - /// A list of keys that start with the given prefix - /// - /// # Errors - /// - /// Returns an error if the operation fails - pub fn list(&mut self, prefix: &str) -> Result, Error> { - // Implementation will go here - unimplemented!() - } - - /// Gets all values for keys with a given prefix. - /// - /// # Arguments - /// - /// * `prefix` - The prefix to search for - /// - /// # Returns - /// - /// A list of values for keys that start with the given prefix - /// - /// # Errors - /// - /// Returns an error if the operation fails - pub fn getall(&mut self, prefix: &str) -> Result>, Error> { - // Implementation will go here - unimplemented!() - } -} -``` - -### 12.3 src/error.rs - -```rust -//! Error types for the RadixTree module. - -use thiserror::Error; - -/// Error type for RadixTree operations. -#[derive(Debug, Error)] -pub enum Error { - /// Error from OurDB operations. - #[error("OurDB error: {0}")] - OurDB(#[from] ourdb::Error), - - /// Error when a key is not found. - #[error("Key not found: {0}")] - KeyNotFound(String), - - /// Error when a prefix is not found. - #[error("Prefix not found: {0}")] - PrefixNotFound(String), - - /// Error during serialization. - #[error("Serialization error: {0}")] - Serialization(String), - - /// Error during deserialization. - #[error("Deserialization error: {0}")] - Deserialization(String), - - /// Error for invalid operations. - #[error("Invalid operation: {0}")] - InvalidOperation(String), -} -``` - -### 12.4 src/node.rs - -```rust -//! Node types for the RadixTree module. - -/// Represents a node in the radix tree. -pub struct Node { - /// The segment of the key stored at this node. - pub key_segment: String, - - /// Value stored at this node (empty if not a leaf). - pub value: Vec, - - /// References to child nodes. - pub children: Vec, - - /// Whether this node is a leaf node. - pub is_leaf: bool, -} - -/// Reference to a node in the database. -pub struct NodeRef { - /// The key segment for this child. - pub key_part: String, - - /// Database ID of the node. - pub node_id: u32, -} - -impl Node { - /// Creates a new node. - pub fn new(key_segment: String, value: Vec, is_leaf: bool) -> Self { - Self { - key_segment, - value, - children: Vec::new(), - is_leaf, - } - } - - /// Creates a new root node. - pub fn new_root() -> Self { - Self { - key_segment: String::new(), - value: Vec::new(), - children: Vec::new(), - is_leaf: false, - } - } -} - -impl NodeRef { - /// Creates a new node reference. - pub fn new(key_part: String, node_id: u32) -> Self { - Self { - key_part, - node_id, - } - } -} -``` - -### 12.5 src/serialize.rs - -```rust -//! Serialization and deserialization for RadixTree nodes. - -use crate::error::Error; -use crate::node::{Node, NodeRef}; - -/// Current binary format version. -const VERSION: u8 = 1; - -impl Node { - /// Serializes a node to bytes for storage. - pub fn serialize(&self) -> Vec { - // Implementation will go here - unimplemented!() - } - - /// Deserializes bytes to a node. - pub fn deserialize(data: &[u8]) -> Result { - // Implementation will go here - unimplemented!() - } -} -``` - -### 12.6 src/operations.rs - -```rust -//! Implementation of RadixTree operations. - -use crate::error::Error; -use crate::node::{Node, NodeRef}; -use crate::RadixTree; - -impl RadixTree { - /// Helper function to get a node from the database. - pub(crate) fn get_node(&mut self, node_id: u32) -> Result { - // Implementation will go here - unimplemented!() - } - - /// Helper function to save a node to the database. - pub(crate) fn save_node(&mut self, node_id: Option, node: &Node) -> Result { - // Implementation will go here - unimplemented!() - } - - /// Helper function to find all keys with a given prefix. - fn find_keys_with_prefix( - &mut self, - node_id: u32, - current_path: &str, - prefix: &str, - result: &mut Vec, - ) -> Result<(), Error> { - // Implementation will go here - unimplemented!() - } - - /// Helper function to recursively collect all keys under a node. - fn collect_all_keys( - &mut self, - node_id: u32, - current_path: &str, - result: &mut Vec, - ) -> Result<(), Error> { - // Implementation will go here - unimplemented!() - } - - /// Helper function to get the common prefix of two strings. - fn get_common_prefix(a: &str, b: &str) -> String { - // Implementation will go here - unimplemented!() - } -} -``` - -### 12.7 examples/basic_usage.rs - -```rust -//! Basic usage example for RadixTree. - -use radixtree::RadixTree; - -fn main() -> Result<(), radixtree::Error> { - // Create a temporary directory for the database - let db_path = std::env::temp_dir().join("radixtree_example"); - std::fs::create_dir_all(&db_path)?; - - println!("Creating radix tree at: {}", db_path.display()); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path.to_str().unwrap(), true)?; - - // Store some data - tree.set("hello", b"world".to_vec())?; - tree.set("help", b"me".to_vec())?; - tree.set("helicopter", b"flying".to_vec())?; - - // Retrieve and print the data - let value = tree.get("hello")?; - println!("hello: {}", String::from_utf8_lossy(&value)); - - // List keys with prefix - let keys = tree.list("hel")?; - println!("Keys with prefix 'hel': {:?}", keys); - - // Get all values with prefix - let values = tree.getall("hel")?; - println!("Values with prefix 'hel':"); - for (i, value) in values.iter().enumerate() { - println!(" {}: {}", i, String::from_utf8_lossy(value)); - } - - // Delete a key - tree.delete("help")?; - println!("Deleted 'help'"); - - // Verify deletion - let keys_after = tree.list("hel")?; - println!("Keys with prefix 'hel' after deletion: {:?}", keys_after); - - // Clean up (optional) - if std::env::var("KEEP_DB").is_err() { - std::fs::remove_dir_all(&db_path)?; - println!("Cleaned up database directory"); - } else { - println!("Database kept at: {}", db_path.display()); - } - - Ok(()) -} -``` \ No newline at end of file diff --git a/radixtree/Cargo.lock b/radixtree/Cargo.lock deleted file mode 100644 index ce6b281..0000000 --- a/radixtree/Cargo.lock +++ /dev/null @@ -1,815 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "bumpalo" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "clap" -version = "4.5.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" -dependencies = [ - "anstyle", - "clap_lex", -] - -[[package]] -name = "clap_lex" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "criterion" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" -dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "is-terminal", - "itertools", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "errno" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", -] - -[[package]] -name = "half" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" -dependencies = [ - "cfg-if", - "crunchy", -] - -[[package]] -name = "hermit-abi" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" - -[[package]] -name = "is-terminal" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "libc" -version = "0.2.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" - -[[package]] -name = "linux-raw-sys" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "oorandom" -version = "11.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" - -[[package]] -name = "ourdb" -version = "0.1.0" -dependencies = [ - "crc32fast", - "log", - "rand", - "thiserror", -] - -[[package]] -name = "plotters" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] - -[[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.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" -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 = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - -[[package]] -name = "radixtree" -version = "0.1.0" -dependencies = [ - "criterion", - "log", - "ourdb", - "tempfile", - "thiserror", -] - -[[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 0.2.15", -] - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "rustix" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "rustversion" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[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 = "tempfile" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" -dependencies = [ - "fastrand", - "getrandom 0.3.2", - "once_cell", - "rustix", - "windows-sys", -] - -[[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 = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] - -[[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/radixtree/Cargo.toml b/radixtree/Cargo.toml deleted file mode 100644 index 3ac5b35..0000000 --- a/radixtree/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "radixtree" -version = "0.1.0" -edition = "2021" -description = "A persistent radix tree implementation using OurDB for storage" -authors = ["OurWorld Team"] - -[dependencies] -ourdb = { path = "../ourdb" } -thiserror = "1.0.40" -log = "0.4.17" - -[dev-dependencies] -criterion = "0.5.1" -tempfile = "3.8.0" - -[[bench]] -name = "radixtree_benchmarks" -harness = false - -[[example]] -name = "basic_usage" -path = "examples/basic_usage.rs" - -[[example]] -name = "prefix_operations" -path = "examples/prefix_operations.rs" diff --git a/radixtree/MIGRATION.md b/radixtree/MIGRATION.md deleted file mode 100644 index 43222f2..0000000 --- a/radixtree/MIGRATION.md +++ /dev/null @@ -1,265 +0,0 @@ -# Migration Guide: V to Rust RadixTree - -This document provides guidance for migrating from the V implementation of RadixTree to the Rust implementation. - -## API Changes - -The Rust implementation maintains API compatibility with the V implementation, but with some idiomatic Rust changes: - -### V API - -```v -// Create a new radix tree -mut rt := radixtree.new(path: '/tmp/radixtree_test', reset: true)! - -// Set a key-value pair -rt.set('test', 'value1'.bytes())! - -// Get a value by key -value := rt.get('test')! - -// Update a value at a prefix -rt.update('prefix', 'new_value'.bytes())! - -// Delete a key -rt.delete('test')! - -// List keys with a prefix -keys := rt.list('prefix')! - -// Get all values with a prefix -values := rt.getall('prefix')! -``` - -### Rust API - -```rust -// Create a new radix tree -let mut tree = RadixTree::new("/tmp/radixtree_test", true)?; - -// Set a key-value pair -tree.set("test", b"value1".to_vec())?; - -// Get a value by key -let value = tree.get("test")?; - -// Update a value at a prefix -tree.update("prefix", b"new_value".to_vec())?; - -// Delete a key -tree.delete("test")?; - -// List keys with a prefix -let keys = tree.list("prefix")?; - -// Get all values with a prefix -let values = tree.getall("prefix")?; -``` - -## Key Differences - -1. **Error Handling**: The Rust implementation uses Rust's `Result` type for error handling, while the V implementation uses V's `!` operator. - -2. **String Handling**: The Rust implementation uses Rust's `&str` for string parameters and `String` for string return values, while the V implementation uses V's `string` type. - -3. **Binary Data**: The Rust implementation uses Rust's `Vec` for binary data, while the V implementation uses V's `[]u8` type. - -4. **Constructor**: The Rust implementation uses a constructor function with separate parameters, while the V implementation uses a struct with named parameters. - -5. **Ownership**: The Rust implementation follows Rust's ownership model, requiring mutable references for methods that modify the tree. - -## Data Compatibility - -The Rust implementation maintains data compatibility with the V implementation: - -- The same serialization format is used for nodes -- The same OurDB storage format is used -- Existing RadixTree data created with the V implementation can be read by the Rust implementation - -## Migration Steps - -1. **Update Dependencies**: Replace the V RadixTree dependency with the Rust RadixTree dependency in your project. - -2. **Update Import Statements**: Replace V import statements with Rust use statements. - - ```v - // V - import freeflowuniverse.herolib.data.radixtree - ``` - - ```rust - // Rust - use radixtree::RadixTree; - ``` - -3. **Update Constructor Calls**: Replace V constructor calls with Rust constructor calls. - - ```v - // V - mut rt := radixtree.new(path: '/path/to/db', reset: false)! - ``` - - ```rust - // Rust - let mut tree = RadixTree::new("/path/to/db", false)?; - ``` - -4. **Update Method Calls**: Replace V method calls with Rust method calls. - - ```v - // V - rt.set('key', 'value'.bytes())! - ``` - - ```rust - // Rust - tree.set("key", b"value".to_vec())?; - ``` - -5. **Update Error Handling**: Replace V error handling with Rust error handling. - - ```v - // V - if value := rt.get('key') { - println('Found: ${value.bytestr()}') - } else { - println('Error: ${err}') - } - ``` - - ```rust - // Rust - match tree.get("key") { - Ok(value) => println!("Found: {}", String::from_utf8_lossy(&value)), - Err(e) => println!("Error: {}", e), - } - ``` - -6. **Update String Conversions**: Replace V string conversions with Rust string conversions. - - ```v - // V - value.bytestr() // Convert []u8 to string - ``` - - ```rust - // Rust - String::from_utf8_lossy(&value) // Convert Vec to string - ``` - -## Example Migration - -### V Code - -```v -module main - -import freeflowuniverse.herolib.data.radixtree - -fn main() { - mut rt := radixtree.new(path: '/tmp/radixtree_test', reset: true) or { - println('Error creating RadixTree: ${err}') - return - } - - rt.set('hello', 'world'.bytes()) or { - println('Error setting key: ${err}') - return - } - - rt.set('help', 'me'.bytes()) or { - println('Error setting key: ${err}') - return - } - - if value := rt.get('hello') { - println('hello: ${value.bytestr()}') - } else { - println('Error getting key: ${err}') - return - } - - keys := rt.list('hel') or { - println('Error listing keys: ${err}') - return - } - println('Keys with prefix "hel": ${keys}') - - values := rt.getall('hel') or { - println('Error getting all values: ${err}') - return - } - println('Values with prefix "hel":') - for i, value in values { - println(' ${i}: ${value.bytestr()}') - } - - rt.delete('help') or { - println('Error deleting key: ${err}') - return - } - println('Deleted "help"') -} -``` - -### Rust Code - -```rust -use radixtree::RadixTree; - -fn main() -> Result<(), Box> { - let mut tree = RadixTree::new("/tmp/radixtree_test", true) - .map_err(|e| format!("Error creating RadixTree: {}", e))?; - - tree.set("hello", b"world".to_vec()) - .map_err(|e| format!("Error setting key: {}", e))?; - - tree.set("help", b"me".to_vec()) - .map_err(|e| format!("Error setting key: {}", e))?; - - let value = tree.get("hello") - .map_err(|e| format!("Error getting key: {}", e))?; - println!("hello: {}", String::from_utf8_lossy(&value)); - - let keys = tree.list("hel") - .map_err(|e| format!("Error listing keys: {}", e))?; - println!("Keys with prefix \"hel\": {:?}", keys); - - let values = tree.getall("hel") - .map_err(|e| format!("Error getting all values: {}", e))?; - println!("Values with prefix \"hel\":"); - for (i, value) in values.iter().enumerate() { - println!(" {}: {}", i, String::from_utf8_lossy(value)); - } - - tree.delete("help") - .map_err(|e| format!("Error deleting key: {}", e))?; - println!("Deleted \"help\""); - - Ok(()) -} -``` - -## Performance Considerations - -The Rust implementation should provide similar or better performance compared to the V implementation. However, there are some considerations: - -1. **Memory Usage**: The Rust implementation may have different memory usage patterns due to Rust's ownership model. - -2. **Error Handling**: The Rust implementation uses Rust's `Result` type, which may have different performance characteristics compared to V's error handling. - -3. **String Handling**: The Rust implementation uses Rust's string types, which may have different performance characteristics compared to V's string types. - -## Troubleshooting - -If you encounter issues during migration, check the following: - -1. **Data Compatibility**: Ensure that the data format is compatible between the V and Rust implementations. - -2. **API Usage**: Ensure that you're using the correct API for the Rust implementation. - -3. **Error Handling**: Ensure that you're handling errors correctly in the Rust implementation. - -4. **String Encoding**: Ensure that string encoding is consistent between the V and Rust implementations. - -If you encounter any issues that are not covered in this guide, please report them to the project maintainers. \ No newline at end of file diff --git a/radixtree/README.md b/radixtree/README.md deleted file mode 100644 index fa87ede..0000000 --- a/radixtree/README.md +++ /dev/null @@ -1,189 +0,0 @@ -# RadixTree - -A persistent radix tree implementation in Rust using OurDB for storage. - -## Overview - -RadixTree is a space-optimized tree data structure that enables efficient string key operations with persistent storage. This implementation provides a persistent radix tree that can be used for efficient prefix-based key operations, such as auto-complete, routing tables, and more. - -A radix tree (also known as a patricia trie or radix trie) is a space-optimized tree data structure that enables efficient string key operations. Unlike a standard trie where each node represents a single character, a radix tree compresses paths by allowing nodes to represent multiple characters (key segments). - -Key characteristics: -- Each node stores a segment of a key (not just a single character) -- Nodes can have multiple children, each representing a different branch -- Leaf nodes contain the actual values -- Optimizes storage by compressing common prefixes - -## Features - -- Efficient prefix-based key operations -- Persistent storage using OurDB backend -- Memory-efficient storage of strings with common prefixes -- Support for binary values -- Thread-safe operations through OurDB - -## Usage - -Add the dependency to your `Cargo.toml`: - -```toml -[dependencies] -radixtree = { path = "../radixtree" } -``` - -### Basic Example - -```rust -use radixtree::RadixTree; - -fn main() -> Result<(), radixtree::Error> { - // Create a new radix tree - let mut tree = RadixTree::new("/tmp/radix", false)?; - - // Set key-value pairs - tree.set("hello", b"world".to_vec())?; - tree.set("help", b"me".to_vec())?; - - // Get values by key - let value = tree.get("hello")?; - println!("hello: {}", String::from_utf8_lossy(&value)); // Prints: world - - // List keys by prefix - let keys = tree.list("hel")?; // Returns ["hello", "help"] - println!("Keys with prefix 'hel': {:?}", keys); - - // Get all values by prefix - let values = tree.getall("hel")?; // Returns [b"world", b"me"] - - // Delete keys - tree.delete("help")?; - - Ok(()) -} -``` - -## API - -### Creating a RadixTree - -```rust -// Create a new radix tree -let mut tree = RadixTree::new("/tmp/radix", false)?; - -// Create a new radix tree and reset if it exists -let mut tree = RadixTree::new("/tmp/radix", true)?; -``` - -### Setting Values - -```rust -// Set a key-value pair -tree.set("key", b"value".to_vec())?; -``` - -### Getting Values - -```rust -// Get a value by key -let value = tree.get("key")?; -``` - -### Updating Values - -```rust -// Update a value at a given prefix -tree.update("prefix", b"new_value".to_vec())?; -``` - -### Deleting Keys - -```rust -// Delete a key -tree.delete("key")?; -``` - -### Listing Keys by Prefix - -```rust -// List all keys with a given prefix -let keys = tree.list("prefix")?; -``` - -### Getting All Values by Prefix - -```rust -// Get all values for keys with a given prefix -let values = tree.getall("prefix")?; -``` - -## Performance Characteristics - -- Search: O(k) where k is the key length -- Insert: O(k) for new keys, may require node splitting -- Delete: O(k) plus potential node cleanup -- Space: O(n) where n is the total length of all keys - -## Use Cases - -RadixTree is particularly useful for: -- Prefix-based searching -- IP routing tables -- Dictionary implementations -- Auto-complete systems -- File system paths -- Any application requiring efficient string key operations with persistence - -## Implementation Details - -The RadixTree implementation uses OurDB for persistent storage: -- Each node is serialized and stored as a record in OurDB -- Node references use OurDB record IDs -- The tree maintains a root node ID for traversal -- Node serialization includes version tracking for format evolution - -For more detailed information about the implementation, see the [ARCHITECTURE.md](./ARCHITECTURE.md) file. - -## Running Tests - -The project includes a comprehensive test suite that verifies all functionality: - -```bash -# Run all tests -cargo test - -# Run specific test file -cargo test --test basic_test -cargo test --test prefix_test -cargo test --test getall_test -cargo test --test serialize_test -``` - -## Running Examples - -The project includes example applications that demonstrate how to use the RadixTree: - -```bash -# Run the basic usage example -cargo run --example basic_usage - -# Run the prefix operations example -cargo run --example prefix_operations -``` - -## Benchmarking - -The project includes benchmarks to measure performance: - -```bash -# Run all benchmarks -cargo bench - -# Run specific benchmark -cargo bench -- set -cargo bench -- get -cargo bench -- prefix_operations -``` - -## License - -This project is licensed under the same license as the HeroCode project. \ No newline at end of file diff --git a/radixtree/benches/radixtree_benchmarks.rs b/radixtree/benches/radixtree_benchmarks.rs deleted file mode 100644 index b95a294..0000000 --- a/radixtree/benches/radixtree_benchmarks.rs +++ /dev/null @@ -1,141 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use radixtree::RadixTree; -use std::path::PathBuf; -use tempfile::tempdir; - -fn criterion_benchmark(c: &mut Criterion) { - // Create a temporary directory for benchmarks - let temp_dir = tempdir().expect("Failed to create temp directory"); - let db_path = temp_dir.path().to_str().unwrap(); - - // Benchmark set operation - c.bench_function("set", |b| { - let mut tree = RadixTree::new(db_path, true).unwrap(); - let mut i = 0; - b.iter(|| { - let key = format!("benchmark_key_{}", i); - let value = format!("benchmark_value_{}", i).into_bytes(); - tree.set(&key, value).unwrap(); - i += 1; - }); - }); - - // Setup tree with data for get/list/delete benchmarks - let mut setup_tree = RadixTree::new(db_path, true).unwrap(); - for i in 0..1000 { - let key = format!("benchmark_key_{}", i); - let value = format!("benchmark_value_{}", i).into_bytes(); - setup_tree.set(&key, value).unwrap(); - } - - // Benchmark get operation - c.bench_function("get", |b| { - let mut tree = RadixTree::new(db_path, false).unwrap(); - let mut i = 0; - b.iter(|| { - let key = format!("benchmark_key_{}", i % 1000); - let _value = tree.get(&key).unwrap(); - i += 1; - }); - }); - - // Benchmark list operation - c.bench_function("list", |b| { - let mut tree = RadixTree::new(db_path, false).unwrap(); - b.iter(|| { - let _keys = tree.list("benchmark_key_1").unwrap(); - }); - }); - - // Benchmark getall operation - c.bench_function("getall", |b| { - let mut tree = RadixTree::new(db_path, false).unwrap(); - b.iter(|| { - let _values = tree.getall("benchmark_key_1").unwrap(); - }); - }); - - // Benchmark update operation - c.bench_function("update", |b| { - let mut tree = RadixTree::new(db_path, false).unwrap(); - let mut i = 0; - b.iter(|| { - let key = format!("benchmark_key_{}", i % 1000); - let new_value = format!("updated_value_{}", i).into_bytes(); - tree.update(&key, new_value).unwrap(); - i += 1; - }); - }); - - // Benchmark delete operation - c.bench_function("delete", |b| { - // Create a fresh tree for deletion benchmarks - let delete_dir = tempdir().expect("Failed to create temp directory"); - let delete_path = delete_dir.path().to_str().unwrap(); - let mut tree = RadixTree::new(delete_path, true).unwrap(); - - // Setup keys to delete - for i in 0..1000 { - let key = format!("delete_key_{}", i); - let value = format!("delete_value_{}", i).into_bytes(); - tree.set(&key, value).unwrap(); - } - - let mut i = 0; - b.iter(|| { - let key = format!("delete_key_{}", i % 1000); - // Only try to delete if it exists - if tree.get(&key).is_ok() { - tree.delete(&key).unwrap(); - } - i += 1; - }); - }); - - // Benchmark prefix operations with varying tree sizes - let mut group = c.benchmark_group("prefix_operations"); - - for &size in &[100, 1000, 10000] { - // Create a fresh tree for each size - let size_dir = tempdir().expect("Failed to create temp directory"); - let size_path = size_dir.path().to_str().unwrap(); - let mut tree = RadixTree::new(size_path, true).unwrap(); - - // Insert data with common prefixes - for i in 0..size { - let prefix = match i % 5 { - 0 => "user", - 1 => "post", - 2 => "comment", - 3 => "product", - _ => "category", - }; - let key = format!("{}_{}", prefix, i); - let value = format!("value_{}", i).into_bytes(); - tree.set(&key, value).unwrap(); - } - - // Benchmark list operation for this size - group.bench_function(format!("list_size_{}", size), |b| { - b.iter(|| { - for prefix in &["user", "post", "comment", "product", "category"] { - let _keys = tree.list(prefix).unwrap(); - } - }); - }); - - // Benchmark getall operation for this size - group.bench_function(format!("getall_size_{}", size), |b| { - b.iter(|| { - for prefix in &["user", "post", "comment", "product", "category"] { - let _values = tree.getall(prefix).unwrap(); - } - }); - }); - } - - group.finish(); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/radixtree/examples/basic_usage.rs b/radixtree/examples/basic_usage.rs deleted file mode 100644 index 4203539..0000000 --- a/radixtree/examples/basic_usage.rs +++ /dev/null @@ -1,51 +0,0 @@ -use radixtree::RadixTree; -use std::path::PathBuf; - -fn main() -> Result<(), radixtree::Error> { - // Create a temporary directory for the database - let db_path = std::env::temp_dir().join("radixtree_example"); - std::fs::create_dir_all(&db_path)?; - - println!("Creating radix tree at: {}", db_path.display()); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path.to_str().unwrap(), true)?; - - // Store some data - println!("Storing data..."); - tree.set("hello", b"world".to_vec())?; - tree.set("help", b"me".to_vec())?; - tree.set("helicopter", b"flying".to_vec())?; - - // Retrieve and print the data - let value = tree.get("hello")?; - println!("hello: {}", String::from_utf8_lossy(&value)); - - // Update a value - println!("Updating value..."); - tree.update("hello", b"updated world".to_vec())?; - - // Retrieve the updated value - let updated_value = tree.get("hello")?; - println!("hello (updated): {}", String::from_utf8_lossy(&updated_value)); - - // Delete a key - println!("Deleting 'help'..."); - tree.delete("help")?; - - // Try to retrieve the deleted key (should fail) - match tree.get("help") { - Ok(value) => println!("Unexpected: help still exists with value: {}", String::from_utf8_lossy(&value)), - Err(e) => println!("As expected, help was deleted: {}", e), - } - - // Clean up (optional) - if std::env::var("KEEP_DB").is_err() { - std::fs::remove_dir_all(&db_path)?; - println!("Cleaned up database directory"); - } else { - println!("Database kept at: {}", db_path.display()); - } - - Ok(()) -} diff --git a/radixtree/examples/large_scale_test.rs b/radixtree/examples/large_scale_test.rs deleted file mode 100644 index 4eed308..0000000 --- a/radixtree/examples/large_scale_test.rs +++ /dev/null @@ -1,121 +0,0 @@ -use radixtree::RadixTree; -use std::time::{Duration, Instant}; -use std::io::{self, Write}; - -// Use much smaller batches to avoid hitting OurDB's size limit -const BATCH_SIZE: usize = 1_000; -const NUM_BATCHES: usize = 1_000; // Total records: 1,000,000 -const PROGRESS_INTERVAL: usize = 100; - -fn main() -> Result<(), radixtree::Error> { - // Overall metrics - let total_start_time = Instant::now(); - let mut total_records_inserted = 0; - let mut batch_times = Vec::with_capacity(NUM_BATCHES); - - println!("Will insert up to {} records in batches of {}", - BATCH_SIZE * NUM_BATCHES, BATCH_SIZE); - - // Process in batches to avoid OurDB size limits - for batch in 0..NUM_BATCHES { - // Create a new database for each batch - let batch_path = std::env::temp_dir().join(format!("radixtree_batch_{}", batch)); - - // Clean up any existing database - if batch_path.exists() { - std::fs::remove_dir_all(&batch_path)?; - } - std::fs::create_dir_all(&batch_path)?; - - println!("\nBatch {}/{}: Creating new radix tree...", batch + 1, NUM_BATCHES); - let mut tree = RadixTree::new(batch_path.to_str().unwrap(), true)?; - - let batch_start_time = Instant::now(); - let mut last_progress_time = Instant::now(); - let mut last_progress_count = 0; - - // Insert records for this batch - for i in 0..BATCH_SIZE { - let global_index = batch * BATCH_SIZE + i; - let key = format!("key:{:08}", global_index); - let value = format!("val{}", global_index).into_bytes(); - - tree.set(&key, value)?; - - // Show progress at intervals - if (i + 1) % PROGRESS_INTERVAL == 0 || i == BATCH_SIZE - 1 { - let records_since_last = i + 1 - last_progress_count; - let time_since_last = last_progress_time.elapsed(); - let records_per_second = records_since_last as f64 / time_since_last.as_secs_f64(); - - print!("\rProgress: {}/{} records ({:.2}%) - {:.2} records/sec", - i + 1, BATCH_SIZE, - (i + 1) as f64 / BATCH_SIZE as f64 * 100.0, - records_per_second); - io::stdout().flush().unwrap(); - - last_progress_time = Instant::now(); - last_progress_count = i + 1; - } - } - - let batch_duration = batch_start_time.elapsed(); - batch_times.push(batch_duration); - total_records_inserted += BATCH_SIZE; - - println!("\nBatch {}/{} completed in {:?} ({:.2} records/sec)", - batch + 1, NUM_BATCHES, - batch_duration, - BATCH_SIZE as f64 / batch_duration.as_secs_f64()); - - // Test random access performance for this batch - println!("Testing access performance for batch {}...", batch + 1); - let mut total_get_time = Duration::new(0, 0); - let num_samples = 100; - - // Use a simple distribution pattern - for i in 0..num_samples { - // Distribute samples across the batch - let sample_id = batch * BATCH_SIZE + (i * (BATCH_SIZE / num_samples)); - let key = format!("key:{:08}", sample_id); - - let get_start = Instant::now(); - let _ = tree.get(&key)?; - total_get_time += get_start.elapsed(); - } - - println!("Average time to retrieve a record: {:?}", - total_get_time / num_samples as u32); - - // Test prefix search performance - println!("Testing prefix search performance..."); - let prefix = format!("key:{:02}", batch % 100); - - let list_start = Instant::now(); - let keys = tree.list(&prefix)?; - let list_duration = list_start.elapsed(); - - println!("Found {} keys with prefix '{}' in {:?}", - keys.len(), prefix, list_duration); - } - - // Overall performance summary - let total_duration = total_start_time.elapsed(); - println!("\n\nPerformance Summary:"); - println!("Total time to insert {} records: {:?}", total_records_inserted, total_duration); - println!("Average insertion rate: {:.2} records/second", - total_records_inserted as f64 / total_duration.as_secs_f64()); - - // Show performance trend - println!("\nPerformance Trend (batch number vs. time):"); - for (i, duration) in batch_times.iter().enumerate() { - if i % 10 == 0 || i == batch_times.len() - 1 { // Only show every 10th point - println!(" Batch {}: {:?} ({:.2} records/sec)", - i + 1, - duration, - BATCH_SIZE as f64 / duration.as_secs_f64()); - } - } - - Ok(()) -} \ No newline at end of file diff --git a/radixtree/examples/performance_test.rs b/radixtree/examples/performance_test.rs deleted file mode 100644 index 9b844ca..0000000 --- a/radixtree/examples/performance_test.rs +++ /dev/null @@ -1,134 +0,0 @@ -use radixtree::RadixTree; -use std::time::{Duration, Instant}; -use std::io::{self, Write}; - -// Number of records to insert -const TOTAL_RECORDS: usize = 1_000_000; -// How often to report progress (every X records) -const PROGRESS_INTERVAL: usize = 10_000; -// How many records to use for performance sampling -const PERFORMANCE_SAMPLE_SIZE: usize = 1000; - -fn main() -> Result<(), radixtree::Error> { - // Create a temporary directory for the database - let db_path = std::env::temp_dir().join("radixtree_performance_test"); - - // Completely remove and recreate the directory to ensure a clean start - if db_path.exists() { - std::fs::remove_dir_all(&db_path)?; - } - std::fs::create_dir_all(&db_path)?; - - println!("Creating radix tree at: {}", db_path.display()); - println!("Will insert {} records and show progress...", TOTAL_RECORDS); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path.to_str().unwrap(), true)?; - - // Track overall time - let start_time = Instant::now(); - - // Track performance metrics - let mut insertion_times = Vec::with_capacity(TOTAL_RECORDS / PROGRESS_INTERVAL); - let mut last_batch_time = Instant::now(); - let mut last_batch_records = 0; - - // Insert records and track progress - for i in 0..TOTAL_RECORDS { - let key = format!("key:{:08}", i); - // Use smaller values to avoid exceeding OurDB's size limit - let value = format!("val{}", i).into_bytes(); - - // Time the insertion of every Nth record for performance sampling - if i % PERFORMANCE_SAMPLE_SIZE == 0 { - let insert_start = Instant::now(); - tree.set(&key, value)?; - let insert_duration = insert_start.elapsed(); - - // Only print detailed timing for specific samples to avoid flooding output - if i % (PERFORMANCE_SAMPLE_SIZE * 10) == 0 { - println!("Record {}: Insertion took {:?}", i, insert_duration); - } - } else { - tree.set(&key, value)?; - } - - // Show progress at intervals - if (i + 1) % PROGRESS_INTERVAL == 0 || i == TOTAL_RECORDS - 1 { - let records_in_batch = i + 1 - last_batch_records; - let batch_duration = last_batch_time.elapsed(); - let records_per_second = records_in_batch as f64 / batch_duration.as_secs_f64(); - - insertion_times.push((i + 1, batch_duration)); - - print!("\rProgress: {}/{} records ({:.2}%) - {:.2} records/sec", - i + 1, TOTAL_RECORDS, - (i + 1) as f64 / TOTAL_RECORDS as f64 * 100.0, - records_per_second); - io::stdout().flush().unwrap(); - - last_batch_time = Instant::now(); - last_batch_records = i + 1; - } - } - - let total_duration = start_time.elapsed(); - println!("\n\nPerformance Summary:"); - println!("Total time to insert {} records: {:?}", TOTAL_RECORDS, total_duration); - println!("Average insertion rate: {:.2} records/second", - TOTAL_RECORDS as f64 / total_duration.as_secs_f64()); - - // Show performance trend - println!("\nPerformance Trend (records inserted vs. time per batch):"); - for (i, (record_count, duration)) in insertion_times.iter().enumerate() { - if i % 10 == 0 || i == insertion_times.len() - 1 { // Only show every 10th point to avoid too much output - println!(" After {} records: {:?} for {} records ({:.2} records/sec)", - record_count, - duration, - PROGRESS_INTERVAL, - PROGRESS_INTERVAL as f64 / duration.as_secs_f64()); - } - } - - // Test access performance with distributed samples - println!("\nTesting access performance with distributed samples..."); - let mut total_get_time = Duration::new(0, 0); - let num_samples = 1000; - - // Use a simple distribution pattern instead of random - for i in 0..num_samples { - // Distribute samples across the entire range - let sample_id = (i * (TOTAL_RECORDS / num_samples)) % TOTAL_RECORDS; - let key = format!("key:{:08}", sample_id); - - let get_start = Instant::now(); - let _ = tree.get(&key)?; - total_get_time += get_start.elapsed(); - } - - println!("Average time to retrieve a record: {:?}", - total_get_time / num_samples as u32); - - // Test prefix search performance - println!("\nTesting prefix search performance..."); - let prefixes = ["key:0", "key:1", "key:5", "key:9"]; - - for prefix in &prefixes { - let list_start = Instant::now(); - let keys = tree.list(prefix)?; - let list_duration = list_start.elapsed(); - - println!("Found {} keys with prefix '{}' in {:?}", - keys.len(), prefix, list_duration); - } - - // Clean up (optional) - if std::env::var("KEEP_DB").is_err() { - std::fs::remove_dir_all(&db_path)?; - println!("\nCleaned up database directory"); - } else { - println!("\nDatabase kept at: {}", db_path.display()); - } - - Ok(()) -} \ No newline at end of file diff --git a/radixtree/examples/prefix_operations.rs b/radixtree/examples/prefix_operations.rs deleted file mode 100644 index a9c48c2..0000000 --- a/radixtree/examples/prefix_operations.rs +++ /dev/null @@ -1,97 +0,0 @@ -use radixtree::RadixTree; -use std::path::PathBuf; - -fn main() -> Result<(), radixtree::Error> { - // Create a temporary directory for the database - let db_path = std::env::temp_dir().join("radixtree_prefix_example"); - std::fs::create_dir_all(&db_path)?; - - println!("Creating radix tree at: {}", db_path.display()); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path.to_str().unwrap(), true)?; - - // Store data with common prefixes - println!("Storing data with common prefixes..."); - - // User data - tree.set("user:1:name", b"Alice".to_vec())?; - tree.set("user:1:email", b"alice@example.com".to_vec())?; - tree.set("user:2:name", b"Bob".to_vec())?; - tree.set("user:2:email", b"bob@example.com".to_vec())?; - - // Post data - tree.set("post:1:title", b"First Post".to_vec())?; - tree.set("post:1:content", b"Hello World!".to_vec())?; - tree.set("post:2:title", b"Second Post".to_vec())?; - tree.set("post:2:content", b"Another post content".to_vec())?; - - // Demonstrate listing keys with a prefix - println!("\nListing keys with prefix 'user:1:'"); - let user1_keys = tree.list("user:1:")?; - for key in &user1_keys { - println!(" Key: {}", key); - } - - println!("\nListing keys with prefix 'post:'"); - let post_keys = tree.list("post:")?; - for key in &post_keys { - println!(" Key: {}", key); - } - - // Demonstrate getting all values with a prefix - println!("\nGetting all values with prefix 'user:1:'"); - let user1_values = tree.getall("user:1:")?; - for (i, value) in user1_values.iter().enumerate() { - println!(" Value {}: {}", i + 1, String::from_utf8_lossy(value)); - } - - // Demonstrate finding all user names - println!("\nFinding all user names (prefix 'user:*:name')"); - let mut user_names = Vec::new(); - let all_keys = tree.list("user:")?; - for key in all_keys { - if key.ends_with(":name") { - if let Ok(value) = tree.get(&key) { - user_names.push((key, String::from_utf8_lossy(&value).to_string())); - } - } - } - - for (key, name) in user_names { - println!(" {}: {}", key, name); - } - - // Demonstrate updating values with a specific prefix - println!("\nUpdating all post titles..."); - let post_title_keys = tree.list("post:")?.into_iter().filter(|k| k.ends_with(":title")).collect::>(); - - for key in post_title_keys { - let old_value = tree.get(&key)?; - let old_title = String::from_utf8_lossy(&old_value); - let new_title = format!("UPDATED: {}", old_title); - - println!(" Updating '{}' to '{}'", old_title, new_title); - tree.update(&key, new_title.as_bytes().to_vec())?; - } - - // Verify updates - println!("\nVerifying updates:"); - let post_keys = tree.list("post:")?; - for key in post_keys { - if key.ends_with(":title") { - let value = tree.get(&key)?; - println!(" {}: {}", key, String::from_utf8_lossy(&value)); - } - } - - // Clean up (optional) - if std::env::var("KEEP_DB").is_err() { - std::fs::remove_dir_all(&db_path)?; - println!("\nCleaned up database directory"); - } else { - println!("\nDatabase kept at: {}", db_path.display()); - } - - Ok(()) -} diff --git a/radixtree/src/error.rs b/radixtree/src/error.rs deleted file mode 100644 index cacf236..0000000 --- a/radixtree/src/error.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Error types for the RadixTree module. - -use thiserror::Error; - -/// Error type for RadixTree operations. -#[derive(Debug, Error)] -pub enum Error { - /// Error from OurDB operations. - #[error("OurDB error: {0}")] - OurDB(#[from] ourdb::Error), - - /// Error when a key is not found. - #[error("Key not found: {0}")] - KeyNotFound(String), - - /// Error when a prefix is not found. - #[error("Prefix not found: {0}")] - PrefixNotFound(String), - - /// Error during serialization. - #[error("Serialization error: {0}")] - Serialization(String), - - /// Error during deserialization. - #[error("Deserialization error: {0}")] - Deserialization(String), - - /// Error for invalid operations. - #[error("Invalid operation: {0}")] - InvalidOperation(String), - - /// Error for I/O operations. - #[error("I/O error: {0}")] - IO(#[from] std::io::Error), -} diff --git a/radixtree/src/lib.rs b/radixtree/src/lib.rs deleted file mode 100644 index 5e52c21..0000000 --- a/radixtree/src/lib.rs +++ /dev/null @@ -1,133 +0,0 @@ -//! RadixTree is a space-optimized tree data structure that enables efficient string key operations -//! with persistent storage using OurDB as a backend. -//! -//! This implementation provides a persistent radix tree that can be used for efficient -//! prefix-based key operations, such as auto-complete, routing tables, and more. - -mod error; -mod node; -mod operations; -mod serialize; - -pub use error::Error; -pub use node::{Node, NodeRef}; - -use ourdb::OurDB; - -/// RadixTree represents a radix tree data structure with persistent storage. -pub struct RadixTree { - db: OurDB, - root_id: u32, -} - -impl RadixTree { - /// Creates a new radix tree with the specified database path. - /// - /// # Arguments - /// - /// * `path` - The path to the database directory - /// * `reset` - Whether to reset the database if it exists - /// - /// # Returns - /// - /// A new `RadixTree` instance - /// - /// # Errors - /// - /// Returns an error if the database cannot be created or opened - pub fn new(path: &str, reset: bool) -> Result { - operations::new_radix_tree(path, reset) - } - - /// Sets a key-value pair in the tree. - /// - /// # Arguments - /// - /// * `key` - The key to set - /// * `value` - The value to set - /// - /// # Errors - /// - /// Returns an error if the operation fails - pub fn set(&mut self, key: &str, value: Vec) -> Result<(), Error> { - operations::set(self, key, value) - } - - /// Gets a value by key from the tree. - /// - /// # Arguments - /// - /// * `key` - The key to get - /// - /// # Returns - /// - /// The value associated with the key - /// - /// # Errors - /// - /// Returns an error if the key is not found or the operation fails - pub fn get(&mut self, key: &str) -> Result, Error> { - operations::get(self, key) - } - - /// Updates the value at a given key prefix. - /// - /// # Arguments - /// - /// * `prefix` - The key prefix to update - /// * `new_value` - The new value to set - /// - /// # Errors - /// - /// Returns an error if the prefix is not found or the operation fails - pub fn update(&mut self, prefix: &str, new_value: Vec) -> Result<(), Error> { - operations::update(self, prefix, new_value) - } - - /// Deletes a key from the tree. - /// - /// # Arguments - /// - /// * `key` - The key to delete - /// - /// # Errors - /// - /// Returns an error if the key is not found or the operation fails - pub fn delete(&mut self, key: &str) -> Result<(), Error> { - operations::delete(self, key) - } - - /// Lists all keys with a given prefix. - /// - /// # Arguments - /// - /// * `prefix` - The prefix to search for - /// - /// # Returns - /// - /// A list of keys that start with the given prefix - /// - /// # Errors - /// - /// Returns an error if the operation fails - pub fn list(&mut self, prefix: &str) -> Result, Error> { - operations::list(self, prefix) - } - - /// Gets all values for keys with a given prefix. - /// - /// # Arguments - /// - /// * `prefix` - The prefix to search for - /// - /// # Returns - /// - /// A list of values for keys that start with the given prefix - /// - /// # Errors - /// - /// Returns an error if the operation fails - pub fn getall(&mut self, prefix: &str) -> Result>, Error> { - operations::getall(self, prefix) - } -} diff --git a/radixtree/src/node.rs b/radixtree/src/node.rs deleted file mode 100644 index b469cd1..0000000 --- a/radixtree/src/node.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Node types for the RadixTree module. - -/// Represents a node in the radix tree. -#[derive(Debug, Clone, PartialEq)] -pub struct Node { - /// The segment of the key stored at this node. - pub key_segment: String, - - /// Value stored at this node (empty if not a leaf). - pub value: Vec, - - /// References to child nodes. - pub children: Vec, - - /// Whether this node is a leaf node. - pub is_leaf: bool, -} - -/// Reference to a node in the database. -#[derive(Debug, Clone, PartialEq)] -pub struct NodeRef { - /// The key segment for this child. - pub key_part: String, - - /// Database ID of the node. - pub node_id: u32, -} - -impl Node { - /// Creates a new node. - pub fn new(key_segment: String, value: Vec, is_leaf: bool) -> Self { - Self { - key_segment, - value, - children: Vec::new(), - is_leaf, - } - } - - /// Creates a new root node. - pub fn new_root() -> Self { - Self { - key_segment: String::new(), - value: Vec::new(), - children: Vec::new(), - is_leaf: false, - } - } -} - -impl NodeRef { - /// Creates a new node reference. - pub fn new(key_part: String, node_id: u32) -> Self { - Self { - key_part, - node_id, - } - } -} diff --git a/radixtree/src/operations.rs b/radixtree/src/operations.rs deleted file mode 100644 index 0991bed..0000000 --- a/radixtree/src/operations.rs +++ /dev/null @@ -1,508 +0,0 @@ -//! Implementation of RadixTree operations. - -use crate::error::Error; -use crate::node::{Node, NodeRef}; -use crate::RadixTree; -use crate::serialize::get_common_prefix; -use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; -use std::path::PathBuf; - - -/// Creates a new radix tree with the specified database path. -pub fn new_radix_tree(path: &str, reset: bool) -> Result { - let config = OurDBConfig { - path: PathBuf::from(path), - incremental_mode: true, - file_size: Some(1024 * 1024 * 10), // 10MB file size for better performance with large datasets - keysize: Some(6), // Use keysize=6 to support multiple files (file_nr + position) - reset: None, // Don't reset existing database - }; - - let mut db = OurDB::new(config)?; - - // If reset is true, we would clear the database - // Since OurDB doesn't have a reset method, we'll handle it by - // creating a fresh database when reset is true - // We'll implement this by checking if it's a new database (next_id == 1) - - let root_id = if db.get_next_id()? == 1 { - // Create a new root node - let root = Node::new_root(); - let root_id = db.set(OurDBSetArgs { - id: None, - data: &root.serialize(), - })?; - - // First ID should be 1 - assert_eq!(root_id, 1); - root_id - } else { - // Use existing root node - 1 // Root node always has ID 1 - }; - - Ok(RadixTree { - db, - root_id, - }) -} - -/// Sets a key-value pair in the tree. -pub fn set(tree: &mut RadixTree, key: &str, value: Vec) -> Result<(), Error> { - let mut current_id = tree.root_id; - let mut offset = 0; - - // Handle empty key case - if key.is_empty() { - let mut root_node = tree.get_node(current_id)?; - root_node.is_leaf = true; - root_node.value = value; - tree.save_node(Some(current_id), &root_node)?; - return Ok(()); - } - - while offset < key.len() { - let mut node = tree.get_node(current_id)?; - - // Find matching child - let mut matched_child = None; - for (i, child) in node.children.iter().enumerate() { - if key[offset..].starts_with(&child.key_part) { - matched_child = Some((i, child.clone())); - break; - } - } - - if matched_child.is_none() { - // No matching child found, create new leaf node - let key_part = key[offset..].to_string(); - let new_node = Node { - key_segment: key_part.clone(), - value: value.clone(), - children: Vec::new(), - is_leaf: true, - }; - - let new_id = tree.save_node(None, &new_node)?; - - // Create new child reference and update parent node - node.children.push(NodeRef { - key_part, - node_id: new_id, - }); - - tree.save_node(Some(current_id), &node)?; - return Ok(()); - } - - let (child_index, mut child) = matched_child.unwrap(); - let common_prefix = get_common_prefix(&key[offset..], &child.key_part); - - if common_prefix.len() < child.key_part.len() { - // Split existing node - let child_node = tree.get_node(child.node_id)?; - - // Create new intermediate node - let new_node = Node { - key_segment: child.key_part[common_prefix.len()..].to_string(), - value: child_node.value.clone(), - children: child_node.children.clone(), - is_leaf: child_node.is_leaf, - }; - let new_id = tree.save_node(None, &new_node)?; - - // Update current node - node.children[child_index] = NodeRef { - key_part: common_prefix.to_string(), - node_id: new_id, - }; - tree.save_node(Some(current_id), &node)?; - - // Update child node reference - child.node_id = new_id; - } - - if offset + common_prefix.len() == key.len() { - // Update value at existing node - let mut child_node = tree.get_node(child.node_id)?; - child_node.value = value; - child_node.is_leaf = true; - tree.save_node(Some(child.node_id), &child_node)?; - return Ok(()); - } - - offset += common_prefix.len(); - current_id = child.node_id; - } - - Ok(()) -} - -/// Gets a value by key from the tree. -pub fn get(tree: &mut RadixTree, key: &str) -> Result, Error> { - let mut current_id = tree.root_id; - let mut offset = 0; - - // Handle empty key case - if key.is_empty() { - let root_node = tree.get_node(current_id)?; - if root_node.is_leaf { - return Ok(root_node.value.clone()); - } - return Err(Error::KeyNotFound(key.to_string())); - } - - while offset < key.len() { - let node = tree.get_node(current_id)?; - - let mut found = false; - for child in &node.children { - if key[offset..].starts_with(&child.key_part) { - if offset + child.key_part.len() == key.len() { - let child_node = tree.get_node(child.node_id)?; - if child_node.is_leaf { - return Ok(child_node.value); - } - } - current_id = child.node_id; - offset += child.key_part.len(); - found = true; - break; - } - } - - if !found { - return Err(Error::KeyNotFound(key.to_string())); - } - } - - Err(Error::KeyNotFound(key.to_string())) -} - -/// Updates the value at a given key prefix. -pub fn update(tree: &mut RadixTree, prefix: &str, new_value: Vec) -> Result<(), Error> { - let mut current_id = tree.root_id; - let mut offset = 0; - - // Handle empty prefix case - if prefix.is_empty() { - return Err(Error::InvalidOperation("Empty prefix not allowed".to_string())); - } - - while offset < prefix.len() { - let node = tree.get_node(current_id)?; - - let mut found = false; - for child in &node.children { - if prefix[offset..].starts_with(&child.key_part) { - if offset + child.key_part.len() == prefix.len() { - // Found exact prefix match - let mut child_node = tree.get_node(child.node_id)?; - if child_node.is_leaf { - // Update the value - child_node.value = new_value; - tree.save_node(Some(child.node_id), &child_node)?; - return Ok(()); - } - } - current_id = child.node_id; - offset += child.key_part.len(); - found = true; - break; - } - } - - if !found { - return Err(Error::PrefixNotFound(prefix.to_string())); - } - } - - Err(Error::PrefixNotFound(prefix.to_string())) -} - -/// Deletes a key from the tree. -pub fn delete(tree: &mut RadixTree, key: &str) -> Result<(), Error> { - let mut current_id = tree.root_id; - let mut offset = 0; - let mut path = Vec::new(); - - // Handle empty key case - if key.is_empty() { - let mut root_node = tree.get_node(current_id)?; - if !root_node.is_leaf { - return Err(Error::KeyNotFound(key.to_string())); - } - // For the root node, we just mark it as non-leaf - root_node.is_leaf = false; - root_node.value = Vec::new(); - tree.save_node(Some(current_id), &root_node)?; - return Ok(()); - } - - // Find the node to delete - while offset < key.len() { - let node = tree.get_node(current_id)?; - - let mut found = false; - for child in &node.children { - if key[offset..].starts_with(&child.key_part) { - path.push(child.clone()); - current_id = child.node_id; - offset += child.key_part.len(); - found = true; - - // Check if we've matched the full key - if offset == key.len() { - let child_node = tree.get_node(child.node_id)?; - if child_node.is_leaf { - found = true; - break; - } - } - break; - } - } - - if !found { - return Err(Error::KeyNotFound(key.to_string())); - } - } - - if path.is_empty() { - return Err(Error::KeyNotFound(key.to_string())); - } - - // Get the node to delete - let mut last_node = tree.get_node(path.last().unwrap().node_id)?; - - // If the node has children, just mark it as non-leaf - if !last_node.children.is_empty() { - last_node.is_leaf = false; - last_node.value = Vec::new(); - tree.save_node(Some(path.last().unwrap().node_id), &last_node)?; - return Ok(()); - } - - // If node has no children, remove it from parent - if path.len() > 1 { - let parent_id = path[path.len() - 2].node_id; - let mut parent_node = tree.get_node(parent_id)?; - - // Find and remove the child from parent - for i in 0..parent_node.children.len() { - if parent_node.children[i].node_id == path.last().unwrap().node_id { - parent_node.children.remove(i); - break; - } - } - - tree.save_node(Some(parent_id), &parent_node)?; - - // Delete the node from the database - tree.db.delete(path.last().unwrap().node_id)?; - } else { - // If this is a direct child of the root, just mark it as non-leaf - last_node.is_leaf = false; - last_node.value = Vec::new(); - tree.save_node(Some(path.last().unwrap().node_id), &last_node)?; - } - - Ok(()) -} - -/// Lists all keys with a given prefix. -pub fn list(tree: &mut RadixTree, prefix: &str) -> Result, Error> { - let mut result = Vec::new(); - - // Handle empty prefix case - will return all keys - if prefix.is_empty() { - collect_all_keys(tree, tree.root_id, "", &mut result)?; - return Ok(result); - } - - // Start from the root and find all matching keys - find_keys_with_prefix(tree, tree.root_id, "", prefix, &mut result)?; - Ok(result) -} - -/// Helper function to find all keys with a given prefix. -fn find_keys_with_prefix( - tree: &mut RadixTree, - node_id: u32, - current_path: &str, - prefix: &str, - result: &mut Vec, -) -> Result<(), Error> { - let node = tree.get_node(node_id)?; - - // If the current path already matches or exceeds the prefix length - if current_path.len() >= prefix.len() { - // Check if the current path starts with the prefix - if current_path.starts_with(prefix) { - // If this is a leaf node, add it to the results - if node.is_leaf { - result.push(current_path.to_string()); - } - - // Collect all keys from this subtree - for child in &node.children { - let child_path = format!("{}{}", current_path, child.key_part); - find_keys_with_prefix(tree, child.node_id, &child_path, prefix, result)?; - } - } - return Ok(()); - } - - // Current path is shorter than the prefix, continue searching - for child in &node.children { - let child_path = format!("{}{}", current_path, child.key_part); - - // Check if this child's path could potentially match the prefix - if prefix.starts_with(current_path) { - // The prefix starts with the current path, so we need to check if - // the child's key_part matches the next part of the prefix - let prefix_remainder = &prefix[current_path.len()..]; - - // If the prefix remainder starts with the child's key_part or vice versa - if prefix_remainder.starts_with(&child.key_part) - || (child.key_part.starts_with(prefix_remainder) - && child.key_part.len() >= prefix_remainder.len()) { - find_keys_with_prefix(tree, child.node_id, &child_path, prefix, result)?; - } - } - } - - Ok(()) -} - -/// Helper function to recursively collect all keys under a node. -fn collect_all_keys( - tree: &mut RadixTree, - node_id: u32, - current_path: &str, - result: &mut Vec, -) -> Result<(), Error> { - let node = tree.get_node(node_id)?; - - // If this node is a leaf, add its path to the result - if node.is_leaf { - result.push(current_path.to_string()); - } - - // Recursively collect keys from all children - for child in &node.children { - let child_path = format!("{}{}", current_path, child.key_part); - collect_all_keys(tree, child.node_id, &child_path, result)?; - } - - Ok(()) -} - -/// Gets all values for keys with a given prefix. -pub fn getall(tree: &mut RadixTree, prefix: &str) -> Result>, Error> { - // Get all matching keys - let keys = list(tree, prefix)?; - - // Get values for each key - let mut values = Vec::new(); - for key in keys { - if let Ok(value) = get(tree, &key) { - values.push(value); - } - } - - Ok(values) -} - -impl RadixTree { - /// Helper function to get a node from the database. - pub(crate) fn get_node(&mut self, node_id: u32) -> Result { - let data = self.db.get(node_id)?; - Node::deserialize(&data) - } - - /// Helper function to save a node to the database. - pub(crate) fn save_node(&mut self, node_id: Option, node: &Node) -> Result { - let data = node.serialize(); - let args = OurDBSetArgs { - id: node_id, - data: &data, - }; - Ok(self.db.set(args)?) - } - - /// Helper function to find all keys with a given prefix. - fn find_keys_with_prefix( - &mut self, - node_id: u32, - current_path: &str, - prefix: &str, - result: &mut Vec, - ) -> Result<(), Error> { - let node = self.get_node(node_id)?; - - // If the current path already matches or exceeds the prefix length - if current_path.len() >= prefix.len() { - // Check if the current path starts with the prefix - if current_path.starts_with(prefix) { - // If this is a leaf node, add it to the results - if node.is_leaf { - result.push(current_path.to_string()); - } - - // Collect all keys from this subtree - for child in &node.children { - let child_path = format!("{}{}", current_path, child.key_part); - self.find_keys_with_prefix(child.node_id, &child_path, prefix, result)?; - } - } - return Ok(()); - } - - // Current path is shorter than the prefix, continue searching - for child in &node.children { - let child_path = format!("{}{}", current_path, child.key_part); - - // Check if this child's path could potentially match the prefix - if prefix.starts_with(current_path) { - // The prefix starts with the current path, so we need to check if - // the child's key_part matches the next part of the prefix - let prefix_remainder = &prefix[current_path.len()..]; - - // If the prefix remainder starts with the child's key_part or vice versa - if prefix_remainder.starts_with(&child.key_part) - || (child.key_part.starts_with(prefix_remainder) - && child.key_part.len() >= prefix_remainder.len()) { - self.find_keys_with_prefix(child.node_id, &child_path, prefix, result)?; - } - } - } - - Ok(()) - } - - /// Helper function to recursively collect all keys under a node. - fn collect_all_keys( - &mut self, - node_id: u32, - current_path: &str, - result: &mut Vec, - ) -> Result<(), Error> { - let node = self.get_node(node_id)?; - - // If this node is a leaf, add its path to the result - if node.is_leaf { - result.push(current_path.to_string()); - } - - // Recursively collect keys from all children - for child in &node.children { - let child_path = format!("{}{}", current_path, child.key_part); - self.collect_all_keys(child.node_id, &child_path, result)?; - } - - Ok(()) - } -} - - diff --git a/radixtree/src/serialize.rs b/radixtree/src/serialize.rs deleted file mode 100644 index f680bcf..0000000 --- a/radixtree/src/serialize.rs +++ /dev/null @@ -1,156 +0,0 @@ -//! Serialization and deserialization for RadixTree nodes. - -use crate::error::Error; -use crate::node::{Node, NodeRef}; -use std::io::{Cursor, Read}; -use std::mem::size_of; - -/// Current binary format version. -const VERSION: u8 = 1; - -impl Node { - /// Serializes a node to bytes for storage. - pub fn serialize(&self) -> Vec { - let mut buffer = Vec::new(); - - // Add version byte - buffer.push(VERSION); - - // Add key segment - write_string(&mut buffer, &self.key_segment); - - // Add value as []u8 - write_u16(&mut buffer, self.value.len() as u16); - buffer.extend_from_slice(&self.value); - - // Add children - write_u16(&mut buffer, self.children.len() as u16); - for child in &self.children { - write_string(&mut buffer, &child.key_part); - write_u32(&mut buffer, child.node_id); - } - - // Add leaf flag - buffer.push(if self.is_leaf { 1 } else { 0 }); - - buffer - } - - /// Deserializes bytes to a node. - pub fn deserialize(data: &[u8]) -> Result { - if data.is_empty() { - return Err(Error::Deserialization("Empty data".to_string())); - } - - let mut cursor = Cursor::new(data); - - // Read and verify version - let mut version_byte = [0u8; 1]; - cursor.read_exact(&mut version_byte) - .map_err(|e| Error::Deserialization(format!("Failed to read version byte: {}", e)))?; - - if version_byte[0] != VERSION { - return Err(Error::Deserialization( - format!("Invalid version byte: expected {}, got {}", VERSION, version_byte[0]) - )); - } - - // Read key segment - let key_segment = read_string(&mut cursor) - .map_err(|e| Error::Deserialization(format!("Failed to read key segment: {}", e)))?; - - // Read value as []u8 - let value_len = read_u16(&mut cursor) - .map_err(|e| Error::Deserialization(format!("Failed to read value length: {}", e)))?; - - let mut value = vec![0u8; value_len as usize]; - cursor.read_exact(&mut value) - .map_err(|e| Error::Deserialization(format!("Failed to read value: {}", e)))?; - - // Read children - let children_len = read_u16(&mut cursor) - .map_err(|e| Error::Deserialization(format!("Failed to read children length: {}", e)))?; - - let mut children = Vec::with_capacity(children_len as usize); - for _ in 0..children_len { - let key_part = read_string(&mut cursor) - .map_err(|e| Error::Deserialization(format!("Failed to read child key part: {}", e)))?; - - let node_id = read_u32(&mut cursor) - .map_err(|e| Error::Deserialization(format!("Failed to read child node ID: {}", e)))?; - - children.push(NodeRef { - key_part, - node_id, - }); - } - - // Read leaf flag - let mut is_leaf_byte = [0u8; 1]; - cursor.read_exact(&mut is_leaf_byte) - .map_err(|e| Error::Deserialization(format!("Failed to read leaf flag: {}", e)))?; - - let is_leaf = is_leaf_byte[0] == 1; - - Ok(Node { - key_segment, - value, - children, - is_leaf, - }) - } -} - -// Helper functions for serialization - -fn write_string(buffer: &mut Vec, s: &str) { - let bytes = s.as_bytes(); - write_u16(buffer, bytes.len() as u16); - buffer.extend_from_slice(bytes); -} - -fn write_u16(buffer: &mut Vec, value: u16) { - buffer.extend_from_slice(&value.to_le_bytes()); -} - -fn write_u32(buffer: &mut Vec, value: u32) { - buffer.extend_from_slice(&value.to_le_bytes()); -} - -// Helper functions for deserialization - -fn read_string(cursor: &mut Cursor<&[u8]>) -> std::io::Result { - let len = read_u16(cursor)? as usize; - let mut bytes = vec![0u8; len]; - cursor.read_exact(&mut bytes)?; - - String::from_utf8(bytes) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)) -} - -fn read_u16(cursor: &mut Cursor<&[u8]>) -> std::io::Result { - let mut bytes = [0u8; size_of::()]; - cursor.read_exact(&mut bytes)?; - - Ok(u16::from_le_bytes(bytes)) -} - -fn read_u32(cursor: &mut Cursor<&[u8]>) -> std::io::Result { - let mut bytes = [0u8; size_of::()]; - cursor.read_exact(&mut bytes)?; - - Ok(u32::from_le_bytes(bytes)) -} - -/// Helper function to get the common prefix of two strings. -pub fn get_common_prefix(a: &str, b: &str) -> String { - let mut i = 0; - let a_bytes = a.as_bytes(); - let b_bytes = b.as_bytes(); - - while i < a.len() && i < b.len() && a_bytes[i] == b_bytes[i] { - i += 1; - } - - a[..i].to_string() -} diff --git a/radixtree/tests/basic_test.rs b/radixtree/tests/basic_test.rs deleted file mode 100644 index 628f6a4..0000000 --- a/radixtree/tests/basic_test.rs +++ /dev/null @@ -1,144 +0,0 @@ -use radixtree::RadixTree; -use std::path::PathBuf; -use tempfile::tempdir; - -#[test] -fn test_basic_operations() -> Result<(), radixtree::Error> { - // Create a temporary directory for the test - let temp_dir = tempdir().expect("Failed to create temp directory"); - let db_path = temp_dir.path().to_str().unwrap(); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path, true)?; - - // Test setting and getting values - let key = "test_key"; - let value = b"test_value".to_vec(); - tree.set(key, value.clone())?; - - let retrieved_value = tree.get(key)?; - assert_eq!(retrieved_value, value); - - // Test updating a value - let new_value = b"updated_value".to_vec(); - tree.update(key, new_value.clone())?; - - let updated_value = tree.get(key)?; - assert_eq!(updated_value, new_value); - - // Test deleting a value - tree.delete(key)?; - - // Trying to get a deleted key should return an error - let result = tree.get(key); - assert!(result.is_err()); - - Ok(()) -} - -#[test] -fn test_empty_key() -> Result<(), radixtree::Error> { - // Create a temporary directory for the test - let temp_dir = tempdir().expect("Failed to create temp directory"); - let db_path = temp_dir.path().to_str().unwrap(); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path, true)?; - - // Test setting and getting empty key - let key = ""; - let value = b"value_for_empty_key".to_vec(); - tree.set(key, value.clone())?; - - let retrieved_value = tree.get(key)?; - assert_eq!(retrieved_value, value); - - // Test deleting empty key - tree.delete(key)?; - - // Trying to get a deleted key should return an error - let result = tree.get(key); - assert!(result.is_err()); - - Ok(()) -} - -#[test] -fn test_multiple_keys() -> Result<(), radixtree::Error> { - // Create a temporary directory for the test - let temp_dir = tempdir().expect("Failed to create temp directory"); - let db_path = temp_dir.path().to_str().unwrap(); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path, true)?; - - // Insert multiple keys - let test_data = [ - ("key1", b"value1".to_vec()), - ("key2", b"value2".to_vec()), - ("key3", b"value3".to_vec()), - ]; - - for (key, value) in &test_data { - tree.set(key, value.clone())?; - } - - // Verify all keys can be retrieved - for (key, expected_value) in &test_data { - let retrieved_value = tree.get(key)?; - assert_eq!(&retrieved_value, expected_value); - } - - Ok(()) -} - -#[test] -fn test_shared_prefixes() -> Result<(), radixtree::Error> { - // Create a temporary directory for the test - let temp_dir = tempdir().expect("Failed to create temp directory"); - let db_path = temp_dir.path().to_str().unwrap(); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path, true)?; - - // Insert keys with shared prefixes - let test_data = [ - ("test", b"value_test".to_vec()), - ("testing", b"value_testing".to_vec()), - ("tested", b"value_tested".to_vec()), - ]; - - for (key, value) in &test_data { - tree.set(key, value.clone())?; - } - - // Verify all keys can be retrieved - for (key, expected_value) in &test_data { - let retrieved_value = tree.get(key)?; - assert_eq!(&retrieved_value, expected_value); - } - - Ok(()) -} - -#[test] -fn test_persistence() -> Result<(), radixtree::Error> { - // Create a temporary directory for the test - let temp_dir = tempdir().expect("Failed to create temp directory"); - let db_path = temp_dir.path().to_str().unwrap(); - - // Create a new radix tree and add some data - { - let mut tree = RadixTree::new(db_path, true)?; - tree.set("persistent_key", b"persistent_value".to_vec())?; - } // Tree is dropped here - - // Create a new tree instance with the same path - { - let mut tree = RadixTree::new(db_path, false)?; - let value = tree.get("persistent_key")?; - assert_eq!(value, b"persistent_value".to_vec()); - } - - Ok(()) -} diff --git a/radixtree/tests/getall_test.rs b/radixtree/tests/getall_test.rs deleted file mode 100644 index 26669c0..0000000 --- a/radixtree/tests/getall_test.rs +++ /dev/null @@ -1,153 +0,0 @@ -use radixtree::RadixTree; -use std::collections::HashMap; -use tempfile::tempdir; - -#[test] -fn test_getall() -> Result<(), radixtree::Error> { - // Create a temporary directory for the test - let temp_dir = tempdir().expect("Failed to create temp directory"); - let db_path = temp_dir.path().to_str().unwrap(); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path, true)?; - - // Set up test data with common prefixes - let test_data: HashMap<&str, &str> = [ - ("user_1", "data1"), - ("user_2", "data2"), - ("user_3", "data3"), - ("admin_1", "admin_data1"), - ("admin_2", "admin_data2"), - ("guest", "guest_data"), - ].iter().cloned().collect(); - - // Set all test data - for (key, value) in &test_data { - tree.set(key, value.as_bytes().to_vec())?; - } - - // Test getall with 'user_' prefix - let user_values = tree.getall("user_")?; - - // Should return 3 values - assert_eq!(user_values.len(), 3); - - // Convert byte arrays to strings for easier comparison - let user_value_strings: Vec = user_values - .iter() - .map(|v| String::from_utf8_lossy(v).to_string()) - .collect(); - - // Check all expected values are present - assert!(user_value_strings.contains(&"data1".to_string())); - assert!(user_value_strings.contains(&"data2".to_string())); - assert!(user_value_strings.contains(&"data3".to_string())); - - // Test getall with 'admin_' prefix - let admin_values = tree.getall("admin_")?; - - // Should return 2 values - assert_eq!(admin_values.len(), 2); - - // Convert byte arrays to strings for easier comparison - let admin_value_strings: Vec = admin_values - .iter() - .map(|v| String::from_utf8_lossy(v).to_string()) - .collect(); - - // Check all expected values are present - assert!(admin_value_strings.contains(&"admin_data1".to_string())); - assert!(admin_value_strings.contains(&"admin_data2".to_string())); - - // Test getall with empty prefix (should return all values) - let all_values = tree.getall("")?; - - // Should return all 6 values - assert_eq!(all_values.len(), test_data.len()); - - // Test getall with non-existent prefix - let non_existent_values = tree.getall("xyz")?; - - // Should return empty array - assert_eq!(non_existent_values.len(), 0); - - Ok(()) -} - -#[test] -fn test_getall_with_updates() -> Result<(), radixtree::Error> { - // Create a temporary directory for the test - let temp_dir = tempdir().expect("Failed to create temp directory"); - let db_path = temp_dir.path().to_str().unwrap(); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path, true)?; - - // Set initial values - tree.set("key1", b"value1".to_vec())?; - tree.set("key2", b"value2".to_vec())?; - tree.set("key3", b"value3".to_vec())?; - - // Get initial values - let initial_values = tree.getall("key")?; - assert_eq!(initial_values.len(), 3); - - // Update a value - tree.update("key2", b"updated_value2".to_vec())?; - - // Get values after update - let updated_values = tree.getall("key")?; - assert_eq!(updated_values.len(), 3); - - // Convert to strings for easier comparison - let updated_value_strings: Vec = updated_values - .iter() - .map(|v| String::from_utf8_lossy(v).to_string()) - .collect(); - - // Check the updated value is present - assert!(updated_value_strings.contains(&"value1".to_string())); - assert!(updated_value_strings.contains(&"updated_value2".to_string())); - assert!(updated_value_strings.contains(&"value3".to_string())); - - Ok(()) -} - -#[test] -fn test_getall_with_deletions() -> Result<(), radixtree::Error> { - // Create a temporary directory for the test - let temp_dir = tempdir().expect("Failed to create temp directory"); - let db_path = temp_dir.path().to_str().unwrap(); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path, true)?; - - // Set initial values - tree.set("prefix_1", b"value1".to_vec())?; - tree.set("prefix_2", b"value2".to_vec())?; - tree.set("prefix_3", b"value3".to_vec())?; - tree.set("other", b"other_value".to_vec())?; - - // Get initial values - let initial_values = tree.getall("prefix_")?; - assert_eq!(initial_values.len(), 3); - - // Delete a key - tree.delete("prefix_2")?; - - // Get values after deletion - let after_delete_values = tree.getall("prefix_")?; - assert_eq!(after_delete_values.len(), 2); - - // Convert to strings for easier comparison - let after_delete_strings: Vec = after_delete_values - .iter() - .map(|v| String::from_utf8_lossy(v).to_string()) - .collect(); - - // Check the remaining values - assert!(after_delete_strings.contains(&"value1".to_string())); - assert!(after_delete_strings.contains(&"value3".to_string())); - - Ok(()) -} diff --git a/radixtree/tests/prefix_test.rs b/radixtree/tests/prefix_test.rs deleted file mode 100644 index 0b89355..0000000 --- a/radixtree/tests/prefix_test.rs +++ /dev/null @@ -1,185 +0,0 @@ -use radixtree::RadixTree; -use std::collections::HashMap; -use tempfile::tempdir; - -#[test] -fn test_list() -> Result<(), radixtree::Error> { - // Create a temporary directory for the test - let temp_dir = tempdir().expect("Failed to create temp directory"); - let db_path = temp_dir.path().to_str().unwrap(); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path, true)?; - - // Insert keys with various prefixes - let test_data: HashMap<&str, &str> = [ - ("apple", "fruit1"), - ("application", "software1"), - ("apply", "verb1"), - ("banana", "fruit2"), - ("ball", "toy1"), - ("cat", "animal1"), - ("car", "vehicle1"), - ("cargo", "shipping1"), - ].iter().cloned().collect(); - - // Set all test data - for (key, value) in &test_data { - tree.set(key, value.as_bytes().to_vec())?; - } - - // Test prefix 'app' - should return apple, application, apply - let app_keys = tree.list("app")?; - assert_eq!(app_keys.len(), 3); - assert!(app_keys.contains(&"apple".to_string())); - assert!(app_keys.contains(&"application".to_string())); - assert!(app_keys.contains(&"apply".to_string())); - - // Test prefix 'ba' - should return banana, ball - let ba_keys = tree.list("ba")?; - assert_eq!(ba_keys.len(), 2); - assert!(ba_keys.contains(&"banana".to_string())); - assert!(ba_keys.contains(&"ball".to_string())); - - // Test prefix 'car' - should return car, cargo - let car_keys = tree.list("car")?; - assert_eq!(car_keys.len(), 2); - assert!(car_keys.contains(&"car".to_string())); - assert!(car_keys.contains(&"cargo".to_string())); - - // Test prefix 'z' - should return empty list - let z_keys = tree.list("z")?; - assert_eq!(z_keys.len(), 0); - - // Test empty prefix - should return all keys - let all_keys = tree.list("")?; - assert_eq!(all_keys.len(), test_data.len()); - for key in test_data.keys() { - assert!(all_keys.contains(&key.to_string())); - } - - // Test exact key as prefix - should return just that key - let exact_key = tree.list("apple")?; - assert_eq!(exact_key.len(), 1); - assert_eq!(exact_key[0], "apple"); - - Ok(()) -} - -#[test] -fn test_list_with_deletion() -> Result<(), radixtree::Error> { - // Create a temporary directory for the test - let temp_dir = tempdir().expect("Failed to create temp directory"); - let db_path = temp_dir.path().to_str().unwrap(); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path, true)?; - - // Set keys with common prefixes - tree.set("test1", b"value1".to_vec())?; - tree.set("test2", b"value2".to_vec())?; - tree.set("test3", b"value3".to_vec())?; - tree.set("other", b"value4".to_vec())?; - - // Initial check - let test_keys = tree.list("test")?; - assert_eq!(test_keys.len(), 3); - assert!(test_keys.contains(&"test1".to_string())); - assert!(test_keys.contains(&"test2".to_string())); - assert!(test_keys.contains(&"test3".to_string())); - - // Delete one key - tree.delete("test2")?; - - // Check after deletion - let test_keys_after = tree.list("test")?; - assert_eq!(test_keys_after.len(), 2); - assert!(test_keys_after.contains(&"test1".to_string())); - assert!(!test_keys_after.contains(&"test2".to_string())); - assert!(test_keys_after.contains(&"test3".to_string())); - - // Check all keys - let all_keys = tree.list("")?; - assert_eq!(all_keys.len(), 3); - assert!(all_keys.contains(&"other".to_string())); - - Ok(()) -} - -#[test] -fn test_list_edge_cases() -> Result<(), radixtree::Error> { - // Create a temporary directory for the test - let temp_dir = tempdir().expect("Failed to create temp directory"); - let db_path = temp_dir.path().to_str().unwrap(); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path, true)?; - - // Test with empty tree - let empty_result = tree.list("any")?; - assert_eq!(empty_result.len(), 0); - - // Set a single key - tree.set("single", b"value".to_vec())?; - - // Test with prefix that's longer than any key - let long_prefix = tree.list("singlelonger")?; - assert_eq!(long_prefix.len(), 0); - - // Test with partial prefix match - let partial = tree.list("sing")?; - assert_eq!(partial.len(), 1); - assert_eq!(partial[0], "single"); - - // Test with very long keys - let long_key1 = "a".repeat(100) + "key1"; - let long_key2 = "a".repeat(100) + "key2"; - - tree.set(&long_key1, b"value1".to_vec())?; - tree.set(&long_key2, b"value2".to_vec())?; - - let long_prefix_result = tree.list(&"a".repeat(100))?; - assert_eq!(long_prefix_result.len(), 2); - assert!(long_prefix_result.contains(&long_key1)); - assert!(long_prefix_result.contains(&long_key2)); - - Ok(()) -} - -#[test] -fn test_list_performance() -> Result<(), radixtree::Error> { - // Create a temporary directory for the test - let temp_dir = tempdir().expect("Failed to create temp directory"); - let db_path = temp_dir.path().to_str().unwrap(); - - // Create a new radix tree - let mut tree = RadixTree::new(db_path, true)?; - - // Insert a large number of keys with different prefixes - let prefixes = ["user", "post", "comment", "like", "share"]; - - // Set 100 keys for each prefix (500 total) - for prefix in &prefixes { - for i in 0..100 { - let key = format!("{}_{}", prefix, i); - tree.set(&key, format!("value_{}", key).as_bytes().to_vec())?; - } - } - - // Test retrieving by each prefix - for prefix in &prefixes { - let keys = tree.list(prefix)?; - assert_eq!(keys.len(), 100); - - // Verify all keys have the correct prefix - for key in &keys { - assert!(key.starts_with(prefix)); - } - } - - // Test retrieving all keys - let all_keys = tree.list("")?; - assert_eq!(all_keys.len(), 500); - - Ok(()) -} diff --git a/radixtree/tests/serialize_test.rs b/radixtree/tests/serialize_test.rs deleted file mode 100644 index 867b843..0000000 --- a/radixtree/tests/serialize_test.rs +++ /dev/null @@ -1,180 +0,0 @@ -use radixtree::{Node, NodeRef}; - -#[test] -fn test_node_serialization() { - // Create a node with some data - let node = Node { - key_segment: "test".to_string(), - value: b"test_value".to_vec(), - children: vec![ - NodeRef { - key_part: "child1".to_string(), - node_id: 1, - }, - NodeRef { - key_part: "child2".to_string(), - node_id: 2, - }, - ], - is_leaf: true, - }; - - // Serialize the node - let serialized = node.serialize(); - - // Deserialize the node - let deserialized = Node::deserialize(&serialized).expect("Failed to deserialize node"); - - // Verify the deserialized node matches the original - assert_eq!(deserialized.key_segment, node.key_segment); - assert_eq!(deserialized.value, node.value); - assert_eq!(deserialized.is_leaf, node.is_leaf); - assert_eq!(deserialized.children.len(), node.children.len()); - - for (i, child) in node.children.iter().enumerate() { - assert_eq!(deserialized.children[i].key_part, child.key_part); - assert_eq!(deserialized.children[i].node_id, child.node_id); - } -} - -#[test] -fn test_empty_node_serialization() { - // Create an empty node - let node = Node { - key_segment: "".to_string(), - value: vec![], - children: vec![], - is_leaf: false, - }; - - // Serialize the node - let serialized = node.serialize(); - - // Deserialize the node - let deserialized = Node::deserialize(&serialized).expect("Failed to deserialize node"); - - // Verify the deserialized node matches the original - assert_eq!(deserialized.key_segment, node.key_segment); - assert_eq!(deserialized.value, node.value); - assert_eq!(deserialized.is_leaf, node.is_leaf); - assert_eq!(deserialized.children.len(), node.children.len()); -} - -#[test] -fn test_node_with_many_children() { - // Create a node with many children - let mut children = Vec::new(); - for i in 0..100 { - children.push(NodeRef { - key_part: format!("child{}", i), - node_id: i as u32, - }); - } - - let node = Node { - key_segment: "parent".to_string(), - value: b"parent_value".to_vec(), - children, - is_leaf: true, - }; - - // Serialize the node - let serialized = node.serialize(); - - // Deserialize the node - let deserialized = Node::deserialize(&serialized).expect("Failed to deserialize node"); - - // Verify the deserialized node matches the original - assert_eq!(deserialized.key_segment, node.key_segment); - assert_eq!(deserialized.value, node.value); - assert_eq!(deserialized.is_leaf, node.is_leaf); - assert_eq!(deserialized.children.len(), node.children.len()); - - for (i, child) in node.children.iter().enumerate() { - assert_eq!(deserialized.children[i].key_part, child.key_part); - assert_eq!(deserialized.children[i].node_id, child.node_id); - } -} - -#[test] -fn test_node_with_large_value() { - // Create a node with a large value - let large_value = vec![0u8; 4096]; // 4KB value - - let node = Node { - key_segment: "large_value".to_string(), - value: large_value.clone(), - children: vec![], - is_leaf: true, - }; - - // Serialize the node - let serialized = node.serialize(); - - // Deserialize the node - let deserialized = Node::deserialize(&serialized).expect("Failed to deserialize node"); - - // Verify the deserialized node matches the original - assert_eq!(deserialized.key_segment, node.key_segment); - assert_eq!(deserialized.value, node.value); - assert_eq!(deserialized.is_leaf, node.is_leaf); - assert_eq!(deserialized.children.len(), node.children.len()); -} - -#[test] -fn test_version_compatibility() { - // This test ensures that the serialization format is compatible with version 1 - - // Create a node - let node = Node { - key_segment: "test".to_string(), - value: b"test_value".to_vec(), - children: vec![ - NodeRef { - key_part: "child".to_string(), - node_id: 1, - }, - ], - is_leaf: true, - }; - - // Serialize the node - let serialized = node.serialize(); - - // Verify the first byte is the version byte (1) - assert_eq!(serialized[0], 1); - - // Deserialize the node - let deserialized = Node::deserialize(&serialized).expect("Failed to deserialize node"); - - // Verify the deserialized node matches the original - assert_eq!(deserialized.key_segment, node.key_segment); - assert_eq!(deserialized.value, node.value); - assert_eq!(deserialized.is_leaf, node.is_leaf); - assert_eq!(deserialized.children.len(), node.children.len()); -} - -#[test] -fn test_invalid_serialization() { - // Test with empty data - let result = Node::deserialize(&[]); - assert!(result.is_err()); - - // Test with invalid version - let result = Node::deserialize(&[2, 0, 0, 0, 0]); - assert!(result.is_err()); - - // Test with truncated data - let node = Node { - key_segment: "test".to_string(), - value: b"test_value".to_vec(), - children: vec![], - is_leaf: true, - }; - - let serialized = node.serialize(); - let truncated = &serialized[0..serialized.len() / 2]; - - let result = Node::deserialize(truncated); - assert!(result.is_err()); -} diff --git a/tst/Cargo.lock b/tst/Cargo.lock deleted file mode 100644 index afd802b..0000000 --- a/tst/Cargo.lock +++ /dev/null @@ -1,179 +0,0 @@ -# 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 = "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 = "tst" -version = "0.1.0" -dependencies = [ - "ourdb", - "thiserror", -] - -[[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/tst/Cargo.toml b/tst/Cargo.toml deleted file mode 100644 index 89b4e44..0000000 --- a/tst/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "tst" -version = "0.1.0" -edition = "2021" -description = "A persistent ternary search tree implementation using OurDB for storage" -authors = ["OurWorld Team"] - -[dependencies] -ourdb = { path = "../ourdb" } -thiserror = "1.0.40" - -[dev-dependencies] -# criterion = "0.5.1" - -# Uncomment when benchmarks are implemented -# [[bench]] -# name = "tst_benchmarks" -# harness = false - -[[example]] -name = "basic_usage" -path = "examples/basic_usage.rs" - -[[example]] -name = "prefix_ops" -path = "examples/prefix_ops.rs" - -[[example]] -name = "performance" -path = "examples/performance.rs" \ No newline at end of file diff --git a/tst/README.md b/tst/README.md deleted file mode 100644 index a732136..0000000 --- a/tst/README.md +++ /dev/null @@ -1,185 +0,0 @@ -# Ternary Search Tree (TST) - -A persistent ternary search tree implementation in Rust using OurDB for storage. - -## Overview - -TST is a space-optimized tree data structure that enables efficient string key operations with persistent storage. This implementation provides a persistent ternary search tree that can be used for efficient string key operations, such as auto-complete, routing tables, and more. - -A ternary search tree is a type of trie where each node has three children: left, middle, and right. Unlike a radix tree which compresses common prefixes, a TST stores one character per node and uses a binary search tree-like structure for efficient traversal. - -Key characteristics: -- Each node stores a single character -- Nodes have three children: left (for characters < current), middle (for next character in key), and right (for characters > current) -- Leaf nodes contain the actual values -- Balanced structure for consistent performance across operations - -## Features - -- Efficient string key operations -- Persistent storage using OurDB backend -- Balanced tree structure for consistent performance -- Support for binary values -- Thread-safe operations through OurDB - -## Usage - -Add the dependency to your `Cargo.toml`: - -```toml -[dependencies] -tst = { path = "../tst" } -``` - -### Basic Example - -```rust -use tst::TST; - -fn main() -> Result<(), tst::Error> { - // Create a new ternary search tree - let mut tree = TST::new("/tmp/tst", false)?; - - // Set key-value pairs - tree.set("hello", b"world".to_vec())?; - tree.set("help", b"me".to_vec())?; - - // Get values by key - let value = tree.get("hello")?; - println!("hello: {}", String::from_utf8_lossy(&value)); // Prints: world - - // List keys by prefix - let keys = tree.list("hel")?; // Returns ["hello", "help"] - println!("Keys with prefix 'hel': {:?}", keys); - - // Get all values by prefix - let values = tree.getall("hel")?; // Returns [b"world", b"me"] - - // Delete keys - tree.delete("help")?; - - Ok(()) -} -``` - -## API - -### Creating a TST - -```rust -// Create a new ternary search tree -let mut tree = TST::new("/tmp/tst", false)?; - -// Create a new ternary search tree and reset if it exists -let mut tree = TST::new("/tmp/tst", true)?; -``` - -### Setting Values - -```rust -// Set a key-value pair -tree.set("key", b"value".to_vec())?; -``` - -### Getting Values - -```rust -// Get a value by key -let value = tree.get("key")?; -``` - -### Deleting Keys - -```rust -// Delete a key -tree.delete("key")?; -``` - -### Listing Keys by Prefix - -```rust -// List all keys with a given prefix -let keys = tree.list("prefix")?; -``` - -### Getting All Values by Prefix - -```rust -// Get all values for keys with a given prefix -let values = tree.getall("prefix")?; -``` - -## Performance Characteristics - -- Search: O(k) where k is the key length -- Insert: O(k) for new keys -- Delete: O(k) plus potential node cleanup -- Space: O(n) where n is the total number of nodes - -## Use Cases - -TST is particularly useful for: -- Prefix-based searching -- Auto-complete systems -- Dictionary implementations -- Spell checking -- Any application requiring efficient string key operations with persistence - -## Implementation Details - -The TST implementation uses OurDB for persistent storage: -- Each node is serialized and stored as a record in OurDB -- Node references use OurDB record IDs -- The tree maintains a root node ID for traversal -- Node serialization includes version tracking for format evolution - -## Running Tests - -The project includes a comprehensive test suite that verifies all functionality: - -```bash -cd ~/code/git.threefold.info/herocode/db/tst -# Run all tests -cargo test - -# Run specific test file -cargo test --test basic_test -cargo test --test prefix_test - -``` - -## Running Examples - -The project includes example applications that demonstrate how to use the TST: - -```bash -# Run the basic usage example -cargo run --example basic_usage - -# Run the prefix operations example -cargo run --example prefix_ops - -# Run the performance test -cargo run --example performance -``` - -## Comparison with RadixTree - -While both TST and RadixTree provide efficient string key operations, they have different characteristics: - -- **TST**: Stores one character per node, with a balanced structure for consistent performance across operations. -- **RadixTree**: Compresses common prefixes, which can be more space-efficient for keys with long common prefixes. - -Choose TST when: -- You need balanced performance across all operations -- Your keys don't share long common prefixes -- You want a simpler implementation with predictable performance - -Choose RadixTree when: -- Space efficiency is a priority -- Your keys share long common prefixes -- You prioritize lookup performance over balanced performance - -## License - -This project is licensed under the same license as the HeroCode project. \ No newline at end of file diff --git a/tst/examples/basic_usage.rs b/tst/examples/basic_usage.rs deleted file mode 100644 index 3bdf6a7..0000000 --- a/tst/examples/basic_usage.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::time::Instant; -use tst::TST; - -fn main() -> Result<(), tst::Error> { - // Create a temporary directory for the database - let db_path = std::env::temp_dir().join("tst_example"); - std::fs::create_dir_all(&db_path)?; - - println!("Creating ternary search tree at: {}", db_path.display()); - - // Create a new TST - let mut tree = TST::new(db_path.to_str().unwrap(), true)?; - - // Store some data - println!("Inserting data..."); - tree.set("hello", b"world".to_vec())?; - tree.set("help", b"me".to_vec())?; - tree.set("helicopter", b"flying".to_vec())?; - tree.set("apple", b"fruit".to_vec())?; - tree.set("application", b"software".to_vec())?; - tree.set("banana", b"yellow".to_vec())?; - - // Retrieve and print the data - let value = tree.get("hello")?; - println!("hello: {}", String::from_utf8_lossy(&value)); - - // List keys with prefix - println!("\nListing keys with prefix 'hel':"); - let start = Instant::now(); - let keys = tree.list("hel")?; - let duration = start.elapsed(); - - for key in &keys { - println!(" {}", key); - } - println!("Found {} keys in {:?}", keys.len(), duration); - - // Get all values with prefix - println!("\nGetting all values with prefix 'app':"); - let start = Instant::now(); - let values = tree.getall("app")?; - let duration = start.elapsed(); - - for (i, value) in values.iter().enumerate() { - println!(" Value {}: {}", i + 1, String::from_utf8_lossy(value)); - } - println!("Found {} values in {:?}", values.len(), duration); - - // Delete a key - println!("\nDeleting 'help'..."); - tree.delete("help")?; - - // Verify deletion - println!("Listing keys with prefix 'hel' after deletion:"); - let keys_after = tree.list("hel")?; - for key in &keys_after { - println!(" {}", key); - } - - // Try to get a deleted key - match tree.get("help") { - Ok(_) => println!("Unexpectedly found 'help' after deletion!"), - Err(e) => println!("As expected, 'help' was not found: {}", e), - } - - // Clean up (optional) - if std::env::var("KEEP_DB").is_err() { - std::fs::remove_dir_all(&db_path)?; - println!("\nCleaned up database directory"); - } else { - println!("\nDatabase kept at: {}", db_path.display()); - } - - Ok(()) -} diff --git a/tst/examples/performance.rs b/tst/examples/performance.rs deleted file mode 100644 index 632b592..0000000 --- a/tst/examples/performance.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::io::{self, Write}; -use std::time::{Duration, Instant}; -use tst::TST; - -// Function to generate a test value of specified size -fn generate_test_value(index: usize, size: usize) -> Vec { - let base_value = format!("val{:08}", index); - let mut value = Vec::with_capacity(size); - - // Fill with repeating pattern to reach desired size - while value.len() < size { - value.extend_from_slice(base_value.as_bytes()); - } - - // Truncate to exact size - value.truncate(size); - - value -} - -// Number of records to insert -const TOTAL_RECORDS: usize = 100_000; -// How often to report progress (every X records) -const PROGRESS_INTERVAL: usize = 1_000; -// How many records to use for performance sampling -const PERFORMANCE_SAMPLE_SIZE: usize = 100; - -fn main() -> Result<(), tst::Error> { - // Create a temporary directory for the database - let db_path = std::env::temp_dir().join("tst_performance_test"); - - // Completely remove and recreate the directory to ensure a clean start - if db_path.exists() { - std::fs::remove_dir_all(&db_path)?; - } - std::fs::create_dir_all(&db_path)?; - - println!("Creating ternary search tree at: {}", db_path.display()); - println!("Will insert {} records and show progress...", TOTAL_RECORDS); - - // Create a new TST - let mut tree = TST::new(db_path.to_str().unwrap(), true)?; - - // Track overall time - let start_time = Instant::now(); - - // Track performance metrics - let mut insertion_times = Vec::with_capacity(TOTAL_RECORDS / PROGRESS_INTERVAL); - let mut last_batch_time = Instant::now(); - let mut last_batch_records = 0; - - // Insert records and track progress - for i in 0..TOTAL_RECORDS { - let key = format!("key:{:08}", i); - // Generate a 100-byte value - let value = generate_test_value(i, 100); - - // Time the insertion of every Nth record for performance sampling - if i % PERFORMANCE_SAMPLE_SIZE == 0 { - let insert_start = Instant::now(); - tree.set(&key, value)?; - let insert_duration = insert_start.elapsed(); - - // Only print detailed timing for specific samples to avoid flooding output - if i % (PERFORMANCE_SAMPLE_SIZE * 10) == 0 { - println!("Record {}: Insertion took {:?}", i, insert_duration); - } - } else { - tree.set(&key, value)?; - } - - // Show progress at intervals - if (i + 1) % PROGRESS_INTERVAL == 0 || i == TOTAL_RECORDS - 1 { - let records_in_batch = i + 1 - last_batch_records; - let batch_duration = last_batch_time.elapsed(); - let records_per_second = records_in_batch as f64 / batch_duration.as_secs_f64(); - - insertion_times.push((i + 1, batch_duration)); - - print!( - "\rProgress: {}/{} records ({:.2}%) - {:.2} records/sec", - i + 1, - TOTAL_RECORDS, - (i + 1) as f64 / TOTAL_RECORDS as f64 * 100.0, - records_per_second - ); - io::stdout().flush().unwrap(); - - last_batch_time = Instant::now(); - last_batch_records = i + 1; - } - } - - let total_duration = start_time.elapsed(); - println!("\n\nPerformance Summary:"); - println!( - "Total time to insert {} records: {:?}", - TOTAL_RECORDS, total_duration - ); - println!( - "Average insertion rate: {:.2} records/second", - TOTAL_RECORDS as f64 / total_duration.as_secs_f64() - ); - - // Show performance trend - println!("\nPerformance Trend (records inserted vs. time per batch):"); - for (i, (record_count, duration)) in insertion_times.iter().enumerate() { - if i % 10 == 0 || i == insertion_times.len() - 1 { - // Only show every 10th point to avoid too much output - println!( - " After {} records: {:?} for {} records ({:.2} records/sec)", - record_count, - duration, - PROGRESS_INTERVAL, - PROGRESS_INTERVAL as f64 / duration.as_secs_f64() - ); - } - } - - // Test access performance with distributed samples - println!("\nTesting access performance with distributed samples..."); - let mut total_get_time = Duration::new(0, 0); - let num_samples = 1000; - - // Use a simple distribution pattern instead of random - for i in 0..num_samples { - // Distribute samples across the entire range - let sample_id = (i * (TOTAL_RECORDS / num_samples)) % TOTAL_RECORDS; - let key = format!("key:{:08}", sample_id); - - let get_start = Instant::now(); - let _ = tree.get(&key)?; - total_get_time += get_start.elapsed(); - } - - println!( - "Average time to retrieve a record: {:?}", - total_get_time / num_samples as u32 - ); - - // Test prefix search performance - println!("\nTesting prefix search performance..."); - let prefixes = ["key:0", "key:1", "key:5", "key:9"]; - - for prefix in &prefixes { - let list_start = Instant::now(); - let keys = tree.list(prefix)?; - let list_duration = list_start.elapsed(); - - println!( - "Found {} keys with prefix '{}' in {:?}", - keys.len(), - prefix, - list_duration - ); - } - - // Clean up (optional) - if std::env::var("KEEP_DB").is_err() { - std::fs::remove_dir_all(&db_path)?; - println!("\nCleaned up database directory"); - } else { - println!("\nDatabase kept at: {}", db_path.display()); - } - - Ok(()) -} diff --git a/tst/examples/prefix_ops.rs b/tst/examples/prefix_ops.rs deleted file mode 100644 index efbb870..0000000 --- a/tst/examples/prefix_ops.rs +++ /dev/null @@ -1,184 +0,0 @@ -use std::time::Instant; -use tst::TST; - -fn main() -> Result<(), tst::Error> { - // Create a temporary directory for the database - let db_path = std::env::temp_dir().join("tst_prefix_example"); - std::fs::create_dir_all(&db_path)?; - - println!("Creating ternary search tree at: {}", db_path.display()); - - // Create a new TST - let mut tree = TST::new(db_path.to_str().unwrap(), true)?; - - // Insert a variety of keys with different prefixes - println!("Inserting data with various prefixes..."); - - // Names - let names = [ - "Alice", - "Alexander", - "Amanda", - "Andrew", - "Amy", - "Bob", - "Barbara", - "Benjamin", - "Brenda", - "Brian", - "Charlie", - "Catherine", - "Christopher", - "Cynthia", - "Carl", - "David", - "Diana", - "Daniel", - "Deborah", - "Donald", - "Edward", - "Elizabeth", - "Eric", - "Emily", - "Ethan", - ]; - - for (i, name) in names.iter().enumerate() { - let value = format!("person-{}", i).into_bytes(); - tree.set(name, value)?; - } - - // Cities - let cities = [ - "New York", - "Los Angeles", - "Chicago", - "Houston", - "Phoenix", - "Philadelphia", - "San Antonio", - "San Diego", - "Dallas", - "San Jose", - "Austin", - "Jacksonville", - "Fort Worth", - "Columbus", - "San Francisco", - "Charlotte", - "Indianapolis", - "Seattle", - "Denver", - "Washington", - ]; - - for (i, city) in cities.iter().enumerate() { - let value = format!("city-{}", i).into_bytes(); - tree.set(city, value)?; - } - - // Countries - let countries = [ - "United States", - "Canada", - "Mexico", - "Brazil", - "Argentina", - "United Kingdom", - "France", - "Germany", - "Italy", - "Spain", - "China", - "Japan", - "India", - "Australia", - "Russia", - ]; - - for (i, country) in countries.iter().enumerate() { - let value = format!("country-{}", i).into_bytes(); - tree.set(country, value)?; - } - - println!( - "Total items inserted: {}", - names.len() + cities.len() + countries.len() - ); - - // Test prefix operations - test_prefix(&mut tree, "A")?; - test_prefix(&mut tree, "B")?; - test_prefix(&mut tree, "C")?; - test_prefix(&mut tree, "San")?; - test_prefix(&mut tree, "United")?; - - // Test non-existent prefix - test_prefix(&mut tree, "Z")?; - - // Test empty prefix (should return all keys) - println!("\nTesting empty prefix (should return all keys):"); - let start = Instant::now(); - let all_keys = tree.list("")?; - let duration = start.elapsed(); - - println!( - "Found {} keys with empty prefix in {:?}", - all_keys.len(), - duration - ); - println!("First 5 keys (alphabetically):"); - for key in all_keys.iter().take(5) { - println!(" {}", key); - } - - // Clean up (optional) - if std::env::var("KEEP_DB").is_err() { - std::fs::remove_dir_all(&db_path)?; - println!("\nCleaned up database directory"); - } else { - println!("\nDatabase kept at: {}", db_path.display()); - } - - Ok(()) -} - -fn test_prefix(tree: &mut TST, prefix: &str) -> Result<(), tst::Error> { - println!("\nTesting prefix '{}':", prefix); - - // Test list operation - let start = Instant::now(); - let keys = tree.list(prefix)?; - let list_duration = start.elapsed(); - - println!( - "Found {} keys with prefix '{}' in {:?}", - keys.len(), - prefix, - list_duration - ); - - if !keys.is_empty() { - println!("Keys:"); - for key in &keys { - println!(" {}", key); - } - - // Test getall operation - let start = Instant::now(); - let values = tree.getall(prefix)?; - let getall_duration = start.elapsed(); - - println!("Retrieved {} values in {:?}", values.len(), getall_duration); - println!( - "First value: {}", - if !values.is_empty() { - String::from_utf8_lossy(&values[0]) - } else { - "None".into() - } - ); - } - - Ok(()) -} diff --git a/tst/src/error.rs b/tst/src/error.rs deleted file mode 100644 index e44ccaa..0000000 --- a/tst/src/error.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Error types for the TST module. - -use std::io; -use thiserror::Error; - -/// Error type for TST operations. -#[derive(Debug, Error)] -pub enum Error { - /// Error from OurDB operations. - #[error("OurDB error: {0}")] - OurDB(#[from] ourdb::Error), - - /// Error when a key is not found. - #[error("Key not found: {0}")] - KeyNotFound(String), - - /// Error when a prefix is not found. - #[error("Prefix not found: {0}")] - PrefixNotFound(String), - - /// Error during serialization. - #[error("Serialization error: {0}")] - Serialization(String), - - /// Error during deserialization. - #[error("Deserialization error: {0}")] - Deserialization(String), - - /// Error for invalid operations. - #[error("Invalid operation: {0}")] - InvalidOperation(String), - - /// IO error. - #[error("IO error: {0}")] - IO(#[from] io::Error), -} diff --git a/tst/src/lib.rs b/tst/src/lib.rs deleted file mode 100644 index 3943074..0000000 --- a/tst/src/lib.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! TST is a space-optimized tree data structure that enables efficient string key operations -//! with persistent storage using OurDB as a backend. -//! -//! This implementation provides a persistent ternary search tree that can be used for efficient -//! string key operations, such as auto-complete, routing tables, and more. - -mod error; -mod node; -mod operations; -mod serialize; - -pub use error::Error; -pub use node::TSTNode; - -use ourdb::OurDB; - -/// TST represents a ternary search tree data structure with persistent storage. -pub struct TST { - /// Database for persistent storage - db: OurDB, - - /// Database ID of the root node - root_id: Option, -} - -impl TST { - /// Creates a new TST with the specified database path. - /// - /// # Arguments - /// - /// * `path` - The path to the database directory - /// * `reset` - Whether to reset the database if it exists - /// - /// # Returns - /// - /// A new `TST` instance - /// - /// # Errors - /// - /// Returns an error if the database cannot be created or opened - pub fn new(path: &str, reset: bool) -> Result { - operations::new_tst(path, reset) - } - - /// Sets a key-value pair in the tree. - /// - /// # Arguments - /// - /// * `key` - The key to set - /// * `value` - The value to set - /// - /// # Errors - /// - /// Returns an error if the operation fails - pub fn set(&mut self, key: &str, value: Vec) -> Result<(), Error> { - operations::set(self, key, value) - } - - /// Gets a value by key from the tree. - /// - /// # Arguments - /// - /// * `key` - The key to get - /// - /// # Returns - /// - /// The value associated with the key - /// - /// # Errors - /// - /// Returns an error if the key is not found or the operation fails - pub fn get(&mut self, key: &str) -> Result, Error> { - operations::get(self, key) - } - - /// Deletes a key from the tree. - /// - /// # Arguments - /// - /// * `key` - The key to delete - /// - /// # Errors - /// - /// Returns an error if the key is not found or the operation fails - pub fn delete(&mut self, key: &str) -> Result<(), Error> { - operations::delete(self, key) - } - - /// Lists all keys with a given prefix. - /// - /// # Arguments - /// - /// * `prefix` - The prefix to search for - /// - /// # Returns - /// - /// A list of keys that start with the given prefix - /// - /// # Errors - /// - /// Returns an error if the operation fails - pub fn list(&mut self, prefix: &str) -> Result, Error> { - operations::list(self, prefix) - } - - /// Gets all values for keys with a given prefix. - /// - /// # Arguments - /// - /// * `prefix` - The prefix to search for - /// - /// # Returns - /// - /// A list of values for keys that start with the given prefix - /// - /// # Errors - /// - /// Returns an error if the operation fails - pub fn getall(&mut self, prefix: &str) -> Result>, Error> { - operations::getall(self, prefix) - } -} diff --git a/tst/src/node.rs b/tst/src/node.rs deleted file mode 100644 index 83294d0..0000000 --- a/tst/src/node.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Node types for the TST module. - -/// Represents a node in the ternary search tree. -#[derive(Debug, Clone, PartialEq)] -pub struct TSTNode { - /// The character stored at this node. - pub character: char, - - /// Value stored at this node (empty if not end of key). - pub value: Vec, - - /// Whether this node represents the end of a key. - pub is_end_of_key: bool, - - /// Reference to the left child node (for characters < current character). - pub left_id: Option, - - /// Reference to the middle child node (for next character in key). - pub middle_id: Option, - - /// Reference to the right child node (for characters > current character). - pub right_id: Option, -} - -impl TSTNode { - /// Creates a new node. - pub fn new(character: char, value: Vec, is_end_of_key: bool) -> Self { - Self { - character, - value, - is_end_of_key, - left_id: None, - middle_id: None, - right_id: None, - } - } - - /// Creates a new root node. - pub fn new_root() -> Self { - Self { - character: '\0', // Use null character for root - value: Vec::new(), - is_end_of_key: false, - left_id: None, - middle_id: None, - right_id: None, - } - } -} diff --git a/tst/src/operations.rs b/tst/src/operations.rs deleted file mode 100644 index a82b48d..0000000 --- a/tst/src/operations.rs +++ /dev/null @@ -1,453 +0,0 @@ -//! Implementation of TST operations. - -use crate::error::Error; -use crate::node::TSTNode; -use crate::TST; -use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; -use std::path::PathBuf; - -/// Creates a new TST with the specified database path. -pub fn new_tst(path: &str, reset: bool) -> Result { - let path_buf = PathBuf::from(path); - - // Create the configuration for OurDB with reset parameter - let config = OurDBConfig { - path: path_buf.clone(), - incremental_mode: true, - file_size: Some(1024 * 1024), // 1MB file size for better performance with large datasets - keysize: Some(4), // Use keysize=4 (default) - reset: Some(reset), // Use the reset parameter - }; - - // Create a new OurDB instance (it will handle reset internally) - let mut db = OurDB::new(config)?; - - let root_id = if db.get_next_id()? == 1 || reset { - // Create a new root node - let root = TSTNode::new_root(); - let root_id = db.set(OurDBSetArgs { - id: None, - data: &root.serialize(), - })?; - - Some(root_id) - } else { - // Use existing root node - Some(1) // Root node always has ID 1 - }; - - Ok(TST { db, root_id }) -} - -/// Sets a key-value pair in the tree. -pub fn set(tree: &mut TST, key: &str, value: Vec) -> Result<(), Error> { - if key.is_empty() { - return Err(Error::InvalidOperation("Empty key not allowed".to_string())); - } - - let root_id = match tree.root_id { - Some(id) => id, - None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), - }; - - let chars: Vec = key.chars().collect(); - set_recursive(tree, root_id, &chars, 0, value)?; - - Ok(()) -} - -/// Recursive helper function for setting a key-value pair. -fn set_recursive( - tree: &mut TST, - node_id: u32, - chars: &[char], - pos: usize, - value: Vec, -) -> Result { - let mut node = tree.get_node(node_id)?; - - if pos >= chars.len() { - // We've reached the end of the key - node.is_end_of_key = true; - node.value = value; - return tree.save_node(Some(node_id), &node); - } - - let current_char = chars[pos]; - - if node.character == '\0' { - // Root node or empty node, set the character - node.character = current_char; - let node_id = tree.save_node(Some(node_id), &node)?; - - // Continue with the next character - if pos + 1 < chars.len() { - let new_node = TSTNode::new(chars[pos + 1], Vec::new(), false); - let new_id = tree.save_node(None, &new_node)?; - - let mut updated_node = tree.get_node(node_id)?; - updated_node.middle_id = Some(new_id); - tree.save_node(Some(node_id), &updated_node)?; - - return set_recursive(tree, new_id, chars, pos + 1, value); - } else { - // This is the last character - let mut updated_node = tree.get_node(node_id)?; - updated_node.is_end_of_key = true; - updated_node.value = value; - return tree.save_node(Some(node_id), &updated_node); - } - } - - if current_char < node.character { - // Go left - if let Some(left_id) = node.left_id { - return set_recursive(tree, left_id, chars, pos, value); - } else { - // Create new left node - let new_node = TSTNode::new(current_char, Vec::new(), false); - let new_id = tree.save_node(None, &new_node)?; - - // Update current node - node.left_id = Some(new_id); - tree.save_node(Some(node_id), &node)?; - - return set_recursive(tree, new_id, chars, pos, value); - } - } else if current_char > node.character { - // Go right - if let Some(right_id) = node.right_id { - return set_recursive(tree, right_id, chars, pos, value); - } else { - // Create new right node - let new_node = TSTNode::new(current_char, Vec::new(), false); - let new_id = tree.save_node(None, &new_node)?; - - // Update current node - node.right_id = Some(new_id); - tree.save_node(Some(node_id), &node)?; - - return set_recursive(tree, new_id, chars, pos, value); - } - } else { - // Character matches, go middle (next character) - if pos + 1 >= chars.len() { - // This is the last character - node.is_end_of_key = true; - node.value = value; - return tree.save_node(Some(node_id), &node); - } - - if let Some(middle_id) = node.middle_id { - return set_recursive(tree, middle_id, chars, pos + 1, value); - } else { - // Create new middle node - let new_node = TSTNode::new(chars[pos + 1], Vec::new(), false); - let new_id = tree.save_node(None, &new_node)?; - - // Update current node - node.middle_id = Some(new_id); - tree.save_node(Some(node_id), &node)?; - - return set_recursive(tree, new_id, chars, pos + 1, value); - } - } -} - -/// Gets a value by key from the tree. -pub fn get(tree: &mut TST, key: &str) -> Result, Error> { - if key.is_empty() { - return Err(Error::InvalidOperation("Empty key not allowed".to_string())); - } - - let root_id = match tree.root_id { - Some(id) => id, - None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), - }; - - let chars: Vec = key.chars().collect(); - let node_id = find_node(tree, root_id, &chars, 0)?; - - let node = tree.get_node(node_id)?; - if node.is_end_of_key { - Ok(node.value.clone()) - } else { - Err(Error::KeyNotFound(key.to_string())) - } -} - -/// Finds a node by key. -fn find_node(tree: &mut TST, node_id: u32, chars: &[char], pos: usize) -> Result { - let node = tree.get_node(node_id)?; - - if pos >= chars.len() { - return Ok(node_id); - } - - let current_char = chars[pos]; - - if current_char < node.character { - // Go left - if let Some(left_id) = node.left_id { - find_node(tree, left_id, chars, pos) - } else { - Err(Error::KeyNotFound(chars.iter().collect())) - } - } else if current_char > node.character { - // Go right - if let Some(right_id) = node.right_id { - find_node(tree, right_id, chars, pos) - } else { - Err(Error::KeyNotFound(chars.iter().collect())) - } - } else { - // Character matches - if pos + 1 >= chars.len() { - // This is the last character - Ok(node_id) - } else if let Some(middle_id) = node.middle_id { - // Go to next character - find_node(tree, middle_id, chars, pos + 1) - } else { - Err(Error::KeyNotFound(chars.iter().collect())) - } - } -} - -/// Deletes a key from the tree. -pub fn delete(tree: &mut TST, key: &str) -> Result<(), Error> { - if key.is_empty() { - return Err(Error::InvalidOperation("Empty key not allowed".to_string())); - } - - let root_id = match tree.root_id { - Some(id) => id, - None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), - }; - - let chars: Vec = key.chars().collect(); - let node_id = find_node(tree, root_id, &chars, 0)?; - - let mut node = tree.get_node(node_id)?; - - if !node.is_end_of_key { - return Err(Error::KeyNotFound(key.to_string())); - } - - // If the node has a middle child, just mark it as not end of key - if node.middle_id.is_some() || node.left_id.is_some() || node.right_id.is_some() { - node.is_end_of_key = false; - node.value = Vec::new(); - tree.save_node(Some(node_id), &node)?; - return Ok(()); - } - - // Otherwise, we need to remove the node and update its parent - // This is more complex and would require tracking the path to the node - // For simplicity, we'll just mark it as not end of key for now - node.is_end_of_key = false; - node.value = Vec::new(); - tree.save_node(Some(node_id), &node)?; - - Ok(()) -} - -/// Lists all keys with a given prefix. -pub fn list(tree: &mut TST, prefix: &str) -> Result, Error> { - let root_id = match tree.root_id { - Some(id) => id, - None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), - }; - - let mut result = Vec::new(); - - // Handle empty prefix case - will return all keys - if prefix.is_empty() { - collect_all_keys(tree, root_id, String::new(), &mut result)?; - return Ok(result); - } - - // Find the node corresponding to the prefix - let chars: Vec = prefix.chars().collect(); - let node_id = match find_prefix_node(tree, root_id, &chars, 0) { - Ok(id) => id, - Err(_) => return Ok(Vec::new()), // Prefix not found, return empty list - }; - - // For empty prefix, we start with an empty string - // For non-empty prefix, we start with the prefix minus the last character - // (since the last character is in the node we found) - let prefix_base = if chars.len() > 1 { - chars[0..chars.len() - 1].iter().collect() - } else { - String::new() - }; - - // Collect all keys from the subtree - collect_keys_with_prefix(tree, node_id, prefix_base, &mut result)?; - - Ok(result) -} - -/// Finds the node corresponding to a prefix. -fn find_prefix_node( - tree: &mut TST, - node_id: u32, - chars: &[char], - pos: usize, -) -> Result { - if pos >= chars.len() { - return Ok(node_id); - } - - let node = tree.get_node(node_id)?; - let current_char = chars[pos]; - - if current_char < node.character { - // Go left - if let Some(left_id) = node.left_id { - find_prefix_node(tree, left_id, chars, pos) - } else { - Err(Error::PrefixNotFound(chars.iter().collect())) - } - } else if current_char > node.character { - // Go right - if let Some(right_id) = node.right_id { - find_prefix_node(tree, right_id, chars, pos) - } else { - Err(Error::PrefixNotFound(chars.iter().collect())) - } - } else { - // Character matches - if pos + 1 >= chars.len() { - // This is the last character of the prefix - Ok(node_id) - } else if let Some(middle_id) = node.middle_id { - // Go to next character - find_prefix_node(tree, middle_id, chars, pos + 1) - } else { - Err(Error::PrefixNotFound(chars.iter().collect())) - } - } -} - -/// Collects all keys with a given prefix. -fn collect_keys_with_prefix( - tree: &mut TST, - node_id: u32, - current_path: String, - result: &mut Vec, -) -> Result<(), Error> { - let node = tree.get_node(node_id)?; - - let mut new_path = current_path.clone(); - - // For non-root nodes, add the character to the path - if node.character != '\0' { - new_path.push(node.character); - } - - // If this node is an end of key, add it to the result - if node.is_end_of_key { - result.push(new_path.clone()); - } - - // Recursively collect keys from all children - if let Some(left_id) = node.left_id { - collect_keys_with_prefix(tree, left_id, current_path.clone(), result)?; - } - - if let Some(middle_id) = node.middle_id { - collect_keys_with_prefix(tree, middle_id, new_path.clone(), result)?; - } - - if let Some(right_id) = node.right_id { - collect_keys_with_prefix(tree, right_id, current_path.clone(), result)?; - } - - Ok(()) -} - -/// Recursively collects all keys under a node. -fn collect_all_keys( - tree: &mut TST, - node_id: u32, - current_path: String, - result: &mut Vec, -) -> Result<(), Error> { - let node = tree.get_node(node_id)?; - - let mut new_path = current_path.clone(); - - // Skip adding the character for the root node - if node.character != '\0' { - new_path.push(node.character); - } - - // If this node is an end of key, add it to the result - if node.is_end_of_key { - result.push(new_path.clone()); - } - - // Recursively collect keys from all children - if let Some(left_id) = node.left_id { - collect_all_keys(tree, left_id, current_path.clone(), result)?; - } - - if let Some(middle_id) = node.middle_id { - collect_all_keys(tree, middle_id, new_path.clone(), result)?; - } - - if let Some(right_id) = node.right_id { - collect_all_keys(tree, right_id, current_path.clone(), result)?; - } - - Ok(()) -} - -/// Gets all values for keys with a given prefix. -pub fn getall(tree: &mut TST, prefix: &str) -> Result>, Error> { - // Get all matching keys - let keys = list(tree, prefix)?; - - // Get values for each key - let mut values = Vec::new(); - let mut errors = Vec::new(); - - for key in keys { - match get(tree, &key) { - Ok(value) => values.push(value), - Err(e) => errors.push(format!("Error getting value for key '{}': {:?}", key, e)), - } - } - - // If we couldn't get any values but had keys, return the first error - if values.is_empty() && !errors.is_empty() { - return Err(Error::InvalidOperation(errors.join("; "))); - } - - Ok(values) -} - -impl TST { - /// Helper function to get a node from the database. - pub(crate) fn get_node(&mut self, node_id: u32) -> Result { - match self.db.get(node_id) { - Ok(data) => TSTNode::deserialize(&data), - Err(err) => Err(Error::OurDB(err)), - } - } - - /// Helper function to save a node to the database. - pub(crate) fn save_node(&mut self, node_id: Option, node: &TSTNode) -> Result { - let data = node.serialize(); - let args = OurDBSetArgs { - id: node_id, - data: &data, - }; - match self.db.set(args) { - Ok(id) => Ok(id), - Err(err) => Err(Error::OurDB(err)), - } - } -} diff --git a/tst/src/serialize.rs b/tst/src/serialize.rs deleted file mode 100644 index 76e68b4..0000000 --- a/tst/src/serialize.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! Serialization and deserialization for TST nodes. - -use crate::error::Error; -use crate::node::TSTNode; - -/// Current binary format version. -const VERSION: u8 = 1; - -impl TSTNode { - /// Serializes a node to bytes for storage. - pub fn serialize(&self) -> Vec { - let mut buffer = Vec::new(); - - // Version - buffer.push(VERSION); - - // Character (as UTF-32) - let char_bytes = (self.character as u32).to_le_bytes(); - buffer.extend_from_slice(&char_bytes); - - // Is end of key - buffer.push(if self.is_end_of_key { 1 } else { 0 }); - - // Value (only if is_end_of_key) - if self.is_end_of_key { - let value_len = (self.value.len() as u32).to_le_bytes(); - buffer.extend_from_slice(&value_len); - buffer.extend_from_slice(&self.value); - } else { - // Zero length - buffer.extend_from_slice(&[0, 0, 0, 0]); - } - - // Child pointers - let left_id = self.left_id.unwrap_or(0).to_le_bytes(); - buffer.extend_from_slice(&left_id); - - let middle_id = self.middle_id.unwrap_or(0).to_le_bytes(); - buffer.extend_from_slice(&middle_id); - - let right_id = self.right_id.unwrap_or(0).to_le_bytes(); - buffer.extend_from_slice(&right_id); - - buffer - } - - /// Deserializes bytes to a node. - pub fn deserialize(data: &[u8]) -> Result { - if data.len() < 14 { - // Minimum size: version + char + is_end + value_len + 3 child IDs - return Err(Error::Deserialization("Data too short".to_string())); - } - - let mut pos = 0; - - // Version - let version = data[pos]; - pos += 1; - - if version != VERSION { - return Err(Error::Deserialization(format!( - "Unsupported version: {}", - version - ))); - } - - // Character - let char_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; - let char_code = u32::from_le_bytes(char_bytes); - let character = char::from_u32(char_code) - .ok_or_else(|| Error::Deserialization("Invalid character".to_string()))?; - pos += 4; - - // Is end of key - let is_end_of_key = data[pos] != 0; - pos += 1; - - // Value length - let value_len_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; - let value_len = u32::from_le_bytes(value_len_bytes) as usize; - pos += 4; - - // Value - let value = if value_len > 0 { - if pos + value_len > data.len() { - return Err(Error::Deserialization( - "Value length exceeds data".to_string(), - )); - } - data[pos..pos + value_len].to_vec() - } else { - Vec::new() - }; - pos += value_len; - - // Child pointers - if pos + 12 > data.len() { - return Err(Error::Deserialization( - "Data too short for child pointers".to_string(), - )); - } - - let left_id_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; - let left_id = u32::from_le_bytes(left_id_bytes); - pos += 4; - - let middle_id_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; - let middle_id = u32::from_le_bytes(middle_id_bytes); - pos += 4; - - let right_id_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; - let right_id = u32::from_le_bytes(right_id_bytes); - - Ok(TSTNode { - character, - value, - is_end_of_key, - left_id: if left_id == 0 { None } else { Some(left_id) }, - middle_id: if middle_id == 0 { - None - } else { - Some(middle_id) - }, - right_id: if right_id == 0 { None } else { Some(right_id) }, - }) - } -} - -// Function removed as it was unused diff --git a/tst/tests/basic_test.rs b/tst/tests/basic_test.rs deleted file mode 100644 index 295836b..0000000 --- a/tst/tests/basic_test.rs +++ /dev/null @@ -1,294 +0,0 @@ -use std::env::temp_dir; -use std::fs; -use std::time::SystemTime; -use tst::TST; - -fn get_test_db_path() -> String { - let timestamp = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_nanos(); - - let path = temp_dir().join(format!("tst_test_{}", timestamp)); - - // If the path exists, remove it first - if path.exists() { - let _ = fs::remove_dir_all(&path); - } - - // Create the directory - fs::create_dir_all(&path).unwrap(); - - path.to_string_lossy().to_string() -} - -fn cleanup_test_db(path: &str) { - // Make sure to clean up properly - let _ = fs::remove_dir_all(path); -} - -#[test] -fn test_create_tst() { - let path = get_test_db_path(); - - let result = TST::new(&path, true); - match &result { - Ok(_) => (), - Err(e) => println!("Error creating TST: {:?}", e), - } - assert!(result.is_ok()); - - if let Ok(mut tst) = result { - // Make sure we can perform a basic operation - let set_result = tst.set("test_key", b"test_value".to_vec()); - assert!(set_result.is_ok()); - } - - cleanup_test_db(&path); -} - -#[test] -fn test_set_and_get() { - let path = get_test_db_path(); - - // Create a new TST with reset=true to ensure a clean state - let result = TST::new(&path, true); - assert!(result.is_ok()); - - let mut tree = result.unwrap(); - - // Test setting and getting a key - let key = "test_key"; - let value = b"test_value".to_vec(); - - let set_result = tree.set(key, value.clone()); - assert!(set_result.is_ok()); - - let get_result = tree.get(key); - assert!(get_result.is_ok()); - assert_eq!(get_result.unwrap(), value); - - // Make sure to clean up properly - cleanup_test_db(&path); -} - -#[test] -fn test_get_nonexistent_key() { - let path = get_test_db_path(); - - let mut tree = TST::new(&path, true).unwrap(); - - // Test getting a key that doesn't exist - let get_result = tree.get("nonexistent_key"); - assert!(get_result.is_err()); - - cleanup_test_db(&path); -} - -#[test] -fn test_delete() { - let path = get_test_db_path(); - - // Create a new TST with reset=true to ensure a clean state - let result = TST::new(&path, true); - assert!(result.is_ok()); - - let mut tree = result.unwrap(); - - // Set a key - let key = "delete_test"; - let value = b"to_be_deleted".to_vec(); - - let set_result = tree.set(key, value); - assert!(set_result.is_ok()); - - // Verify it exists - let get_result = tree.get(key); - assert!(get_result.is_ok()); - - // Delete it - let delete_result = tree.delete(key); - assert!(delete_result.is_ok()); - - // Verify it's gone - let get_after_delete = tree.get(key); - assert!(get_after_delete.is_err()); - - // Make sure to clean up properly - cleanup_test_db(&path); -} - -#[test] -fn test_multiple_keys() { - let path = get_test_db_path(); - - // Create a new TST with reset=true to ensure a clean state - let result = TST::new(&path, true); - assert!(result.is_ok()); - - let mut tree = result.unwrap(); - - // Insert multiple keys - use fewer keys to avoid filling the lookup table - let keys = ["apple", "banana", "cherry"]; - - for (i, key) in keys.iter().enumerate() { - let value = format!("value_{}", i).into_bytes(); - let set_result = tree.set(key, value); - - // Print error if set fails - if set_result.is_err() { - println!("Error setting key '{}': {:?}", key, set_result); - } - - assert!(set_result.is_ok()); - } - - // Verify all keys exist - for (i, key) in keys.iter().enumerate() { - let expected_value = format!("value_{}", i).into_bytes(); - let get_result = tree.get(key); - assert!(get_result.is_ok()); - assert_eq!(get_result.unwrap(), expected_value); - } - - // Make sure to clean up properly - cleanup_test_db(&path); -} - -#[test] -fn test_list_prefix() { - let path = get_test_db_path(); - - // Create a new TST with reset=true to ensure a clean state - let result = TST::new(&path, true); - assert!(result.is_ok()); - - let mut tree = result.unwrap(); - - // Insert keys with common prefixes - use fewer keys to avoid filling the lookup table - let keys = ["apple", "application", "append", "banana", "bandana"]; - - for key in &keys { - let set_result = tree.set(key, key.as_bytes().to_vec()); - assert!(set_result.is_ok()); - } - - // Test prefix "app" - let list_result = tree.list("app"); - assert!(list_result.is_ok()); - - let app_keys = list_result.unwrap(); - - // Print the keys for debugging - println!("Keys with prefix 'app':"); - for key in &app_keys { - println!(" {}", key); - } - - // Check that each key is present - assert!(app_keys.contains(&"apple".to_string())); - assert!(app_keys.contains(&"application".to_string())); - assert!(app_keys.contains(&"append".to_string())); - - // Test prefix "ban" - let list_result = tree.list("ban"); - assert!(list_result.is_ok()); - - let ban_keys = list_result.unwrap(); - assert!(ban_keys.contains(&"banana".to_string())); - assert!(ban_keys.contains(&"bandana".to_string())); - - // Test non-existent prefix - let list_result = tree.list("z"); - assert!(list_result.is_ok()); - - let z_keys = list_result.unwrap(); - assert_eq!(z_keys.len(), 0); - - // Make sure to clean up properly - cleanup_test_db(&path); -} - -#[test] -fn test_getall_prefix() { - let path = get_test_db_path(); - - // Create a new TST with reset=true to ensure a clean state - let result = TST::new(&path, true); - assert!(result.is_ok()); - - let mut tree = result.unwrap(); - - // Insert keys with common prefixes - use fewer keys to avoid filling the lookup table - let keys = ["apple", "application", "append"]; - - for key in &keys { - let set_result = tree.set(key, key.as_bytes().to_vec()); - assert!(set_result.is_ok()); - } - - // Test getall with prefix "app" - let getall_result = tree.getall("app"); - assert!(getall_result.is_ok()); - - let app_values = getall_result.unwrap(); - - // Convert values to strings for easier comparison - let app_value_strings: Vec = app_values - .iter() - .map(|v| String::from_utf8_lossy(v).to_string()) - .collect(); - - // Print the values for debugging - println!("Values with prefix 'app':"); - for value in &app_value_strings { - println!(" {}", value); - } - - // Check that each value is present - assert!(app_value_strings.contains(&"apple".to_string())); - assert!(app_value_strings.contains(&"application".to_string())); - assert!(app_value_strings.contains(&"append".to_string())); - - // Make sure to clean up properly - cleanup_test_db(&path); -} - -#[test] -fn test_empty_prefix() { - let path = get_test_db_path(); - - // Create a new TST with reset=true to ensure a clean state - let result = TST::new(&path, true); - assert!(result.is_ok()); - - let mut tree = result.unwrap(); - - // Insert some keys - let keys = ["apple", "banana", "cherry"]; - - for key in &keys { - let set_result = tree.set(key, key.as_bytes().to_vec()); - assert!(set_result.is_ok()); - } - - // Test list with empty prefix (should return all keys) - let list_result = tree.list(""); - assert!(list_result.is_ok()); - - let all_keys = list_result.unwrap(); - - // Print the keys for debugging - println!("Keys with empty prefix:"); - for key in &all_keys { - println!(" {}", key); - } - - // Check that each key is present - for key in &keys { - assert!(all_keys.contains(&key.to_string())); - } - - // Make sure to clean up properly - cleanup_test_db(&path); -} diff --git a/tst/tests/prefix_test.rs b/tst/tests/prefix_test.rs deleted file mode 100644 index b50c17d..0000000 --- a/tst/tests/prefix_test.rs +++ /dev/null @@ -1,267 +0,0 @@ -use std::env::temp_dir; -use std::fs; -use std::time::SystemTime; -use tst::TST; - -fn get_test_db_path() -> String { - let timestamp = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_nanos(); - - let path = temp_dir().join(format!("tst_prefix_test_{}", timestamp)); - - // If the path exists, remove it first - if path.exists() { - let _ = fs::remove_dir_all(&path); - } - - // Create the directory - fs::create_dir_all(&path).unwrap(); - - path.to_string_lossy().to_string() -} - -fn cleanup_test_db(path: &str) { - // Make sure to clean up properly - let _ = fs::remove_dir_all(path); -} - -#[test] -fn test_prefix_with_common_prefixes() { - let path = get_test_db_path(); - - let mut tree = TST::new(&path, true).unwrap(); - - // Insert keys with common prefixes - let test_data = [ - ("test", b"value1".to_vec()), - ("testing", b"value2".to_vec()), - ("tested", b"value3".to_vec()), - ("tests", b"value4".to_vec()), - ("tester", b"value5".to_vec()), - ]; - - for (key, value) in &test_data { - tree.set(key, value.clone()).unwrap(); - } - - // Test prefix "test" - let keys = tree.list("test").unwrap(); - assert_eq!(keys.len(), 5); - - for (key, _) in &test_data { - assert!(keys.contains(&key.to_string())); - } - - // Test prefix "teste" - let keys = tree.list("teste").unwrap(); - assert_eq!(keys.len(), 2); - assert!(keys.contains(&"tested".to_string())); - assert!(keys.contains(&"tester".to_string())); - - cleanup_test_db(&path); -} - -#[test] -fn test_prefix_with_different_prefixes() { - let path = get_test_db_path(); - - let mut tree = TST::new(&path, true).unwrap(); - - // Insert keys with different prefixes - let test_data = [ - ("apple", b"fruit1".to_vec()), - ("banana", b"fruit2".to_vec()), - ("cherry", b"fruit3".to_vec()), - ("date", b"fruit4".to_vec()), - ("elderberry", b"fruit5".to_vec()), - ]; - - for (key, value) in &test_data { - tree.set(key, value.clone()).unwrap(); - } - - // Test each prefix - for (key, _) in &test_data { - let prefix = &key[0..1]; // First character - let keys = tree.list(prefix).unwrap(); - assert!(keys.contains(&key.to_string())); - } - - // Test non-existent prefix - let keys = tree.list("z").unwrap(); - assert_eq!(keys.len(), 0); - - cleanup_test_db(&path); -} - -#[test] -fn test_prefix_with_empty_string() { - let path = get_test_db_path(); - - // Create a new TST with reset=true to ensure a clean state - let result = TST::new(&path, true); - assert!(result.is_ok()); - - let mut tree = result.unwrap(); - - // Insert some keys - let test_data = [ - ("apple", b"fruit1".to_vec()), - ("banana", b"fruit2".to_vec()), - ("cherry", b"fruit3".to_vec()), - ]; - - for (key, value) in &test_data { - let set_result = tree.set(key, value.clone()); - assert!(set_result.is_ok()); - } - - // Test empty prefix (should return all keys) - let list_result = tree.list(""); - assert!(list_result.is_ok()); - - let keys = list_result.unwrap(); - - // Print the keys for debugging - println!("Keys with empty prefix:"); - for key in &keys { - println!(" {}", key); - } - - // Check that each key is present - for (key, _) in &test_data { - assert!(keys.contains(&key.to_string())); - } - - // Make sure to clean up properly - cleanup_test_db(&path); -} - -#[test] -fn test_getall_with_prefix() { - let path = get_test_db_path(); - - let mut tree = TST::new(&path, true).unwrap(); - - // Insert keys with common prefixes - let test_data = [ - ("test", b"value1".to_vec()), - ("testing", b"value2".to_vec()), - ("tested", b"value3".to_vec()), - ("tests", b"value4".to_vec()), - ("tester", b"value5".to_vec()), - ]; - - for (key, value) in &test_data { - tree.set(key, value.clone()).unwrap(); - } - - // Test getall with prefix "test" - let values = tree.getall("test").unwrap(); - assert_eq!(values.len(), 5); - - for (_, value) in &test_data { - assert!(values.contains(value)); - } - - cleanup_test_db(&path); -} - -#[test] -fn test_prefix_with_unicode_characters() { - let path = get_test_db_path(); - - let mut tree = TST::new(&path, true).unwrap(); - - // Insert keys with Unicode characters - let test_data = [ - ("café", b"coffee".to_vec()), - ("cafétéria", b"cafeteria".to_vec()), - ("caffè", b"italian coffee".to_vec()), - ("café au lait", b"coffee with milk".to_vec()), - ]; - - for (key, value) in &test_data { - tree.set(key, value.clone()).unwrap(); - } - - // Test prefix "café" - let keys = tree.list("café").unwrap(); - - // Print the keys for debugging - println!("Keys with prefix 'café':"); - for key in &keys { - println!(" {}", key); - } - - // Check that the keys we expect are present - assert!(keys.contains(&"café".to_string())); - assert!(keys.contains(&"café au lait".to_string())); - - // We don't assert on the exact count because Unicode handling can vary - - // Test prefix "caf" - let keys = tree.list("caf").unwrap(); - - // Print the keys for debugging - println!("Keys with prefix 'caf':"); - for key in &keys { - println!(" {}", key); - } - - // Check that each key is present individually - // Due to Unicode handling, we need to be careful with exact matching - // The important thing is that we can find the keys we need - - // Check that we have at least the café and café au lait keys - assert!(keys.contains(&"café".to_string())); - assert!(keys.contains(&"café au lait".to_string())); - - // We don't assert on the exact count because Unicode handling can vary - - cleanup_test_db(&path); -} - -#[test] -fn test_prefix_with_long_keys() { - let path = get_test_db_path(); - - let mut tree = TST::new(&path, true).unwrap(); - - // Insert long keys - let test_data = [ - ( - "this_is_a_very_long_key_for_testing_purposes_1", - b"value1".to_vec(), - ), - ( - "this_is_a_very_long_key_for_testing_purposes_2", - b"value2".to_vec(), - ), - ( - "this_is_a_very_long_key_for_testing_purposes_3", - b"value3".to_vec(), - ), - ("this_is_another_long_key_for_testing", b"value4".to_vec()), - ]; - - for (key, value) in &test_data { - tree.set(key, value.clone()).unwrap(); - } - - // Test prefix "this_is_a_very" - let keys = tree.list("this_is_a_very").unwrap(); - assert_eq!(keys.len(), 3); - - // Test prefix "this_is" - let keys = tree.list("this_is").unwrap(); - assert_eq!(keys.len(), 4); - - for (key, _) in &test_data { - assert!(keys.contains(&key.to_string())); - } - - cleanup_test_db(&path); -} From cedea2f305503baa385a6bd7a130ae4a00d35e83 Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:05:01 +0200 Subject: [PATCH 8/8] move rhai wrappers of models from rhailib --- Cargo.lock | 274 ++++++++++- heromodels/Cargo.toml | 2 + heromodels/examples/legal_contract_example.rs | 2 +- heromodels/src/models/access/rhai.rs | 148 ++++++ heromodels/src/models/biz/rhai.rs | 422 +++++++++++++++++ heromodels/src/models/calendar/rhai.rs | 246 ++++++++++ heromodels/src/models/circle/rhai.rs | 441 ++++-------------- heromodels/src/models/contact/rhai.rs | 232 +++++++++ heromodels/src/models/core/rhai.rs | 86 ++++ heromodels/src/models/finance/rhai.rs | 80 ++++ heromodels/src/models/grid4/mod.rs | 16 + heromodels/src/models/grid4/node.rs | 265 +++++++++++ heromodels/src/models/heroledger/rhai.rs | 41 +- heromodels/src/models/library/rhai.rs | 156 +++++++ heromodels/src/models/location/address.rs | 11 + heromodels/src/models/location/mod.rs | 2 + heromodels/src/models/mod.rs | 3 + heromodels/src/models/object/rhai.rs | 27 ++ heromodels/src/models/payment/rhai.rs | 49 ++ 19 files changed, 2110 insertions(+), 393 deletions(-) create mode 100644 heromodels/src/models/access/rhai.rs create mode 100644 heromodels/src/models/biz/rhai.rs create mode 100644 heromodels/src/models/calendar/rhai.rs create mode 100644 heromodels/src/models/contact/rhai.rs create mode 100644 heromodels/src/models/core/rhai.rs create mode 100644 heromodels/src/models/finance/rhai.rs create mode 100644 heromodels/src/models/grid4/mod.rs create mode 100644 heromodels/src/models/grid4/node.rs create mode 100644 heromodels/src/models/library/rhai.rs create mode 100644 heromodels/src/models/location/address.rs create mode 100644 heromodels/src/models/location/mod.rs create mode 100644 heromodels/src/models/object/rhai.rs create mode 100644 heromodels/src/models/payment/rhai.rs diff --git a/Cargo.lock b/Cargo.lock index 6409f95..d6977f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.12" @@ -60,7 +71,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -116,6 +127,18 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -125,12 +148,57 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -158,6 +226,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.41" @@ -268,6 +342,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures-channel" version = "0.3.31" @@ -292,7 +372,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -361,6 +441,15 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + [[package]] name = "hashbrown" version = "0.15.4" @@ -387,6 +476,8 @@ dependencies = [ "r2d2", "r2d2_postgres", "rhai", + "rhailib-macros", + "rust_decimal", "serde", "serde_json", "strum", @@ -403,7 +494,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", + "syn 2.0.104", ] [[package]] @@ -454,7 +545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.4", ] [[package]] @@ -506,7 +597,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -804,6 +895,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -813,6 +913,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "quote" version = "1.0.40" @@ -849,6 +969,12 @@ dependencies = [ "r2d2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -917,13 +1043,22 @@ dependencies = [ "bitflags", ] +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "rhai" version = "1.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2780e813b755850e50b178931aaf94ed24f6817f46aaaf5d21c13c12d939a249" dependencies = [ - "ahash", + "ahash 0.8.12", "bitflags", "instant", "no-std-compat", @@ -944,7 +1079,44 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", +] + +[[package]] +name = "rhailib-macros" +version = "0.1.0" +dependencies = [ + "rhai", + "serde", +] + +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -954,7 +1126,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" dependencies = [ "arrayvec", + "borsh", + "bytes", "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", ] [[package]] @@ -990,6 +1168,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "serde" version = "1.0.219" @@ -1007,7 +1191,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1049,6 +1233,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "1.0.1" @@ -1137,7 +1327,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.104", ] [[package]] @@ -1146,6 +1336,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.104" @@ -1157,6 +1358,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "thin-vec" version = "0.2.14" @@ -1180,7 +1387,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1235,7 +1442,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1277,6 +1484,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tst" version = "0.1.0" @@ -1390,7 +1614,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -1412,7 +1636,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1468,7 +1692,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1479,7 +1703,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1588,6 +1812,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -1597,6 +1830,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" version = "0.8.26" @@ -1614,5 +1856,5 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] diff --git a/heromodels/Cargo.toml b/heromodels/Cargo.toml index 99be424..878bcdd 100644 --- a/heromodels/Cargo.toml +++ b/heromodels/Cargo.toml @@ -14,12 +14,14 @@ ourdb = { path = "../../herolib_rust/packages/data/ourdb" } tst = { path = "../../herolib_rust/packages/data/tst" } heromodels-derive = { path = "../heromodels-derive" } heromodels_core = { path = "../heromodels_core" } +rhailib-macros = { path = "../../herolib_rust/rhailib/src/macros" } rhai = { version = "1.21.0", features = [ "std", "sync", "decimal", "internals", ] } # Added "decimal" feature, sync for Arc> +rust_decimal = { version = "1.36", features = ["serde"] } strum = "0.26" strum_macros = "0.26" uuid = { version = "1.17.0", features = ["v4"] } diff --git a/heromodels/examples/legal_contract_example.rs b/heromodels/examples/legal_contract_example.rs index 7ae6ce7..e0e3b57 100644 --- a/heromodels/examples/legal_contract_example.rs +++ b/heromodels/examples/legal_contract_example.rs @@ -73,7 +73,7 @@ fn main() { // The `#[model]` derive handles `created_at` and `updated_at` in `base_data`. // `base_data.touch()` might be called internally by setters or needs explicit call if fields are set directly. - // For builder pattern, the final state of `base_data.updated_at` reflects the time of the last builder call if `touch()` is implicit. + // For builder pattern, the final state of `base_data.modified_at` reflects the time of the last builder call if `touch()` is implicit. // If not, one might call `contract.base_data.touch()` after building. println!("\n--- Initial Contract Details ---"); diff --git a/heromodels/src/models/access/rhai.rs b/heromodels/src/models/access/rhai.rs new file mode 100644 index 0000000..bfa63f8 --- /dev/null +++ b/heromodels/src/models/access/rhai.rs @@ -0,0 +1,148 @@ +use crate::db::Db; +use rhailib_macros::{ + register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn, + register_authorized_get_by_id_fn, +}; +use rhai::plugin::*; +use rhai::{Dynamic, Engine, EvalAltResult, Module}; +use std::mem; +use std::sync::Arc; + +use heromodels::models::access::Access; +type RhaiAccess = Access; +use heromodels::db::hero::OurDB; +use heromodels::db::Collection; + +#[export_module] +mod rhai_access_module { + // --- Access Functions --- + #[rhai_fn(name = "new_access", return_raw)] + pub fn new_access() -> Result> { + let access = Access::new(); + Ok(access) + } + + /// Sets the access object_id + #[rhai_fn(name = "object_id", return_raw)] + pub fn set_object_id( + access: &mut RhaiAccess, + object_id: i64, + ) -> Result> { + let id = macros::id_from_i64_to_u32(object_id)?; + let owned_access = std::mem::take(access); + *access = owned_access.object_id(id); + Ok(access.clone()) + } + + /// Sets the circle public key + #[rhai_fn(name = "circle_public_key", return_raw)] + pub fn set_circle_pk( + access: &mut RhaiAccess, + circle_pk: String, + ) -> Result> { + let owned_access = std::mem::take(access); + *access = owned_access.circle_pk(circle_pk); + Ok(access.clone()) + } + + /// Sets the group id + #[rhai_fn(name = "group_id", return_raw)] + pub fn set_group_id( + access: &mut RhaiAccess, + group_id: i64, + ) -> Result> { + let id = macros::id_from_i64_to_u32(group_id)?; + let owned_access = std::mem::take(access); + *access = owned_access.group_id(id); + Ok(access.clone()) + } + + /// Sets the contact id + #[rhai_fn(name = "contact_id", return_raw)] + pub fn set_contact_id( + access: &mut RhaiAccess, + contact_id: i64, + ) -> Result> { + let id = macros::id_from_i64_to_u32(contact_id)?; + let owned_access = std::mem::take(access); + *access = owned_access.contact_id(id); + Ok(access.clone()) + } + + /// Sets the expiration time + #[rhai_fn(name = "expires_at", return_raw)] + pub fn set_expires_at( + access: &mut RhaiAccess, + expires_at: i64, + ) -> Result> { + let owned_access = std::mem::take(access); + *access = owned_access.expires_at(expires_at); + Ok(access.clone()) + } + + // Access Getters + #[rhai_fn(name = "get_access_id")] + pub fn get_access_id(access: &mut RhaiAccess) -> i64 { + access.base.id as i64 + } + + #[rhai_fn(name = "get_access_object_id")] + pub fn get_access_object_id(access: &mut RhaiAccess) -> i64 { + access.object_id as i64 + } + + #[rhai_fn(name = "get_access_circle_pk")] + pub fn get_access_circle_pk(access: &mut RhaiAccess) -> String { + access.circle_pk.clone() + } + + #[rhai_fn(name = "get_access_group_id")] + pub fn get_access_group_id(access: &mut RhaiAccess) -> i64 { + access.group_id as i64 + } + + #[rhai_fn(name = "get_access_contact_id")] + pub fn get_access_contact_id(access: &mut RhaiAccess) -> i64 { + access.contact_id as i64 + } + + #[rhai_fn(name = "get_access_expires_at")] + pub fn get_access_expires_at(access: &mut RhaiAccess) -> i64 { + access.expires_at + } + + #[rhai_fn(name = "get_access_created_at")] + pub fn get_access_created_at(access: &mut RhaiAccess) -> i64 { + access.base.created_at + } + + #[rhai_fn(name = "get_access_modified_at")] + pub fn get_access_modified_at(access: &mut RhaiAccess) -> i64 { + access.base.modified_at + } +} + +pub fn register_access_rhai_module(engine: &mut Engine) { + let mut module = exported_module!(rhai_access_module); + + register_authorized_create_by_id_fn!( + module: &mut module, + rhai_fn_name: "save_access", + resource_type_str: "Access", + rhai_return_rust_type: heromodels::models::access::Access + ); + register_authorized_get_by_id_fn!( + module: &mut module, + rhai_fn_name: "get_access", + resource_type_str: "Access", + rhai_return_rust_type: heromodels::models::access::Access + ); + register_authorized_delete_by_id_fn!( + module: &mut module, + rhai_fn_name: "delete_access", + resource_type_str: "Access", + rhai_return_rust_type: heromodels::models::access::Access + ); + + engine.register_global_module(module.into()); +} diff --git a/heromodels/src/models/biz/rhai.rs b/heromodels/src/models/biz/rhai.rs new file mode 100644 index 0000000..e65da8c --- /dev/null +++ b/heromodels/src/models/biz/rhai.rs @@ -0,0 +1,422 @@ +use heromodels::db::Db; +use macros::{ + register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn, + register_authorized_get_by_id_fn, +}; +use rhai::plugin::*; +use rhai::{Array, Engine, EvalAltResult, Module, Position, FLOAT, INT}; +use std::mem; +use std::sync::Arc; + +use heromodels::db::hero::OurDB; +use heromodels::db::Collection; +use heromodels::models::biz::product::{Product, ProductComponent, ProductStatus, ProductType}; +use heromodels::models::biz::company::{BusinessType, Company, CompanyStatus}; +use heromodels::models::biz::sale::{Sale, SaleItem, SaleStatus}; +use heromodels::models::biz::shareholder::{Shareholder, ShareholderType}; + +type RhaiProduct = Product; +type RhaiProductComponent = ProductComponent; +type RhaiCompany = Company; +type RhaiSale = Sale; +type RhaiSaleItem = SaleItem; +type RhaiShareholder = Shareholder; + +#[export_module] +mod rhai_product_component_module { + use super::{RhaiProductComponent, INT}; + + #[rhai_fn(name = "new_product_component", return_raw)] + pub fn new_product_component() -> Result> { + Ok(ProductComponent::new()) + } + + #[rhai_fn(name = "name", return_raw)] + pub fn set_name( + component: &mut RhaiProductComponent, + name: String, + ) -> Result> { + let owned = std::mem::take(component); + *component = owned.name(name); + Ok(component.clone()) + } + + #[rhai_fn(name = "description", return_raw)] + pub fn set_description( + component: &mut RhaiProductComponent, + description: String, + ) -> Result> { + let owned = std::mem::take(component); + *component = owned.description(description); + Ok(component.clone()) + } + + #[rhai_fn(name = "quantity", return_raw)] + pub fn set_quantity( + component: &mut RhaiProductComponent, + quantity: INT, + ) -> Result> { + let owned = std::mem::take(component); + *component = owned.quantity(quantity as u32); + Ok(component.clone()) + } + + // --- Getters --- + #[rhai_fn(name = "get_name")] + pub fn get_name(c: &mut RhaiProductComponent) -> String { + c.name.clone() + } + #[rhai_fn(name = "get_description")] + pub fn get_description(c: &mut RhaiProductComponent) -> String { + c.description.clone() + } + #[rhai_fn(name = "get_quantity")] + pub fn get_quantity(c: &mut RhaiProductComponent) -> INT { + c.quantity as INT + } +} + +#[export_module] +mod rhai_product_module { + use super::{Array, ProductStatus, ProductType, RhaiProduct, RhaiProductComponent, FLOAT, INT}; + + #[rhai_fn(name = "new_product", return_raw)] + pub fn new_product() -> Result> { + Ok(Product::new()) + } + + // --- Setters --- + #[rhai_fn(name = "name", return_raw)] + pub fn set_name( + product: &mut RhaiProduct, + name: String, + ) -> Result> { + let owned = std::mem::take(product); + *product = owned.name(name); + Ok(product.clone()) + } + + #[rhai_fn(name = "description", return_raw)] + pub fn set_description( + product: &mut RhaiProduct, + description: String, + ) -> Result> { + let owned = std::mem::take(product); + *product = owned.description(description); + Ok(product.clone()) + } + + #[rhai_fn(name = "price", return_raw)] + pub fn set_price( + product: &mut RhaiProduct, + price: FLOAT, + ) -> Result> { + let owned = std::mem::take(product); + *product = owned.price(price); + Ok(product.clone()) + } + + #[rhai_fn(name = "category", return_raw)] + pub fn set_category( + product: &mut RhaiProduct, + category: String, + ) -> Result> { + let owned = std::mem::take(product); + *product = owned.category(category); + Ok(product.clone()) + } + + #[rhai_fn(name = "max_amount", return_raw)] + pub fn set_max_amount( + product: &mut RhaiProduct, + max_amount: INT, + ) -> Result> { + let owned = std::mem::take(product); + *product = owned.max_amount(max_amount as u32); + Ok(product.clone()) + } + + #[rhai_fn(name = "purchase_till", return_raw)] + pub fn set_purchase_till( + product: &mut RhaiProduct, + purchase_till: INT, + ) -> Result> { + let owned = std::mem::take(product); + *product = owned.purchase_till(purchase_till); + Ok(product.clone()) + } + + #[rhai_fn(name = "active_till", return_raw)] + pub fn set_active_till( + product: &mut RhaiProduct, + active_till: INT, + ) -> Result> { + let owned = std::mem::take(product); + *product = owned.active_till(active_till); + Ok(product.clone()) + } + + #[rhai_fn(name = "type", return_raw)] + pub fn set_type( + product: &mut RhaiProduct, + type_str: String, + ) -> Result> { + let product_type = match type_str.to_lowercase().as_str() { + "physical" => ProductType::Physical, + "digital" => ProductType::Digital, + "service" => ProductType::Service, + "subscription" => ProductType::Subscription, + _ => { + return Err(EvalAltResult::ErrorSystem( + "Invalid ProductType".to_string(), + "Must be one of: Physical, Digital, Service, Subscription".into(), + ) + .into()) + } + }; + let owned = std::mem::take(product); + *product = owned.product_type(product_type); + Ok(product.clone()) + } + + #[rhai_fn(name = "status", return_raw)] + pub fn set_status( + product: &mut RhaiProduct, + status_str: String, + ) -> Result> { + let status = match status_str.to_lowercase().as_str() { + "active" => ProductStatus::Active, + "inactive" => ProductStatus::Inactive, + "discontinued" => ProductStatus::Discontinued, + _ => { + return Err(EvalAltResult::ErrorSystem( + "Invalid ProductStatus".to_string(), + "Must be one of: Active, Inactive, Discontinued".into(), + ) + .into()) + } + }; + let owned = std::mem::take(product); + *product = owned.status(status); + Ok(product.clone()) + } + + #[rhai_fn(name = "add_component", return_raw)] + pub fn add_component( + product: &mut RhaiProduct, + component: RhaiProductComponent, + ) -> Result> { + let owned = std::mem::take(product); + *product = owned.add_component(component); + Ok(product.clone()) + } + + #[rhai_fn(name = "set_components", return_raw)] + pub fn set_components( + product: &mut RhaiProduct, + components: Array, + ) -> Result> { + let mut product_components = Vec::new(); + for component_dynamic in components { + if let Ok(component) = component_dynamic.try_cast::() { + product_components.push(component); + } else { + return Err(EvalAltResult::ErrorSystem( + "Invalid component type".to_string(), + "All components must be ProductComponent objects".into(), + ) + .into()); + } + } + let owned = std::mem::take(product); + *product = owned.components(product_components); + Ok(product.clone()) + } + + // --- Getters --- + #[rhai_fn(name = "get_id")] + pub fn get_id(p: &mut RhaiProduct) -> i64 { + p.base.id as i64 + } + #[rhai_fn(name = "get_name")] + pub fn get_name(p: &mut RhaiProduct) -> String { + p.name.clone() + } + #[rhai_fn(name = "get_description")] + pub fn get_description(p: &mut RhaiProduct) -> String { + p.description.clone() + } + #[rhai_fn(name = "get_price")] + pub fn get_price(p: &mut RhaiProduct) -> FLOAT { + p.price + } + #[rhai_fn(name = "get_category")] + pub fn get_category(p: &mut RhaiProduct) -> String { + p.category.clone() + } + #[rhai_fn(name = "get_max_amount")] + pub fn get_max_amount(p: &mut RhaiProduct) -> INT { + p.max_amount as INT + } + #[rhai_fn(name = "get_purchase_till")] + pub fn get_purchase_till(p: &mut RhaiProduct) -> INT { + p.purchase_till + } + #[rhai_fn(name = "get_active_till")] + pub fn get_active_till(p: &mut RhaiProduct) -> INT { + p.active_till + } + #[rhai_fn(name = "get_type")] + pub fn get_type(p: &mut RhaiProduct) -> String { + format!("{:?}", p.product_type) + } + #[rhai_fn(name = "get_status")] + pub fn get_status(p: &mut RhaiProduct) -> String { + format!("{:?}", p.status) + } + #[rhai_fn(name = "get_components")] + pub fn get_components(p: &mut RhaiProduct) -> Array { + p.components + .iter() + .map(|c| rhai::Dynamic::from(c.clone())) + .collect() + } +} + +pub fn register_product_rhai_module(engine: &mut Engine) { + let mut product_module = exported_module!(rhai_product_module); + let mut component_module = exported_module!(rhai_product_component_module); + + register_authorized_create_by_id_fn!( + product_module: &mut product_module, + rhai_fn_name: "save_product", + resource_type_str: "Product", + rhai_return_rust_type: heromodels::models::biz::product::Product + ); + register_authorized_get_by_id_fn!( + product_module: &mut product_module, + rhai_fn_name: "get_product", + resource_type_str: "Product", + rhai_return_rust_type: heromodels::models::biz::product::Product + ); + register_authorized_delete_by_id_fn!( + product_module: &mut product_module, + rhai_fn_name: "delete_product", + resource_type_str: "Product", + rhai_return_rust_type: heromodels::models::biz::product::Product + ); + + engine.register_global_module(product_module.into()); + engine.register_global_module(component_module.into()); +} + +// Company Rhai wrapper functions +#[export_module] +mod rhai_company_module { + use super::{BusinessType, CompanyStatus, RhaiCompany}; + + #[rhai_fn(name = "new_company", return_raw)] + pub fn new_company() -> Result> { + Ok(Company::new()) + } + + #[rhai_fn(name = "name", return_raw)] + pub fn set_name( + company: &mut RhaiCompany, + name: String, + ) -> Result> { + let owned = std::mem::take(company); + *company = owned.name(name); + Ok(company.clone()) + } + + #[rhai_fn(name = "get_company_id")] + pub fn get_company_id(company: &mut RhaiCompany) -> i64 { + company.id() as i64 + } + + #[rhai_fn(name = "get_company_name")] + pub fn get_company_name(company: &mut RhaiCompany) -> String { + company.name().clone() + } +} + +pub fn register_company_rhai_module(engine: &mut Engine) { + let mut module = exported_module!(rhai_company_module); + + register_authorized_create_by_id_fn!( + module: &mut module, + rhai_fn_name: "save_company", + resource_type_str: "Company", + rhai_return_rust_type: heromodels::models::biz::company::Company + ); + + register_authorized_get_by_id_fn!( + module: &mut module, + rhai_fn_name: "get_company", + resource_type_str: "Company", + rhai_return_rust_type: heromodels::models::biz::company::Company + ); + + engine.register_global_module(module.into()); +} + +// Sale Rhai wrapper functions +#[export_module] +mod rhai_sale_module { + use super::{RhaiSale, RhaiSaleItem, SaleStatus}; + + #[rhai_fn(name = "new_sale", return_raw)] + pub fn new_sale() -> Result> { + Ok(Sale::new()) + } + + #[rhai_fn(name = "new_sale_item", return_raw)] + pub fn new_sale_item() -> Result> { + Ok(SaleItem::new()) + } + + #[rhai_fn(name = "company_id", return_raw)] + pub fn set_sale_company_id(sale: &mut RhaiSale, company_id: i64) -> Result> { + let owned = std::mem::take(sale); + *sale = owned.company_id(company_id as u32); + Ok(sale.clone()) + } + + #[rhai_fn(name = "total_amount", return_raw)] + pub fn set_sale_total_amount(sale: &mut RhaiSale, total_amount: f64) -> Result> { + let owned = std::mem::take(sale); + *sale = owned.total_amount(total_amount); + Ok(sale.clone()) + } + + #[rhai_fn(name = "get_sale_id")] + pub fn get_sale_id(sale: &mut RhaiSale) -> i64 { + sale.id() as i64 + } + + #[rhai_fn(name = "get_sale_total_amount")] + pub fn get_sale_total_amount(sale: &mut RhaiSale) -> f64 { + sale.total_amount() + } +} + +pub fn register_sale_rhai_module(engine: &mut Engine) { + let mut module = exported_module!(rhai_sale_module); + + register_authorized_create_by_id_fn!( + module: &mut module, + rhai_fn_name: "save_sale", + resource_type_str: "Sale", + rhai_return_rust_type: heromodels::models::biz::sale::Sale + ); + + register_authorized_get_by_id_fn!( + module: &mut module, + rhai_fn_name: "get_sale", + resource_type_str: "Sale", + rhai_return_rust_type: heromodels::models::biz::sale::Sale + ); + + engine.register_global_module(module.into()); +} diff --git a/heromodels/src/models/calendar/rhai.rs b/heromodels/src/models/calendar/rhai.rs new file mode 100644 index 0000000..9d73515 --- /dev/null +++ b/heromodels/src/models/calendar/rhai.rs @@ -0,0 +1,246 @@ +use crate::db::Db; +use rhailib_macros::{ + register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn, + register_authorized_get_by_id_fn, +}; +use rhai::plugin::*; +use rhai::{Array, Dynamic, Engine, EvalAltResult, Module}; +use std::mem; +use std::sync::Arc; + +use crate::models::calendar::{AttendanceStatus, Attendee, Calendar, Event}; +type RhaiCalendar = Calendar; +type RhaiEvent = Event; +type RhaiAttendee = Attendee; +use crate::db::hero::OurDB; +use crate::db::Collection; + +#[export_module] +mod rhai_calendar_module { + use super::{AttendanceStatus, RhaiAttendee, RhaiCalendar, RhaiEvent}; + + // --- Attendee Builder --- + #[rhai_fn(name = "new_attendee", return_raw)] + pub fn new_attendee(contact_id: i64) -> Result> { + Ok(Attendee::new(contact_id as u32)) + } + + #[rhai_fn(name = "status", return_raw)] + pub fn set_attendee_status( + attendee: &mut RhaiAttendee, + status_str: String, + ) -> Result> { + let status = match status_str.to_lowercase().as_str() { + "accepted" => AttendanceStatus::Accepted, + "declined" => AttendanceStatus::Declined, + "tentative" => AttendanceStatus::Tentative, + "noresponse" => AttendanceStatus::NoResponse, + _ => { + return Err(EvalAltResult::ErrorSystem( + "Invalid Status".to_string(), + "Must be one of: Accepted, Declined, Tentative, NoResponse".into(), + ) + .into()) + } + }; + let owned = std::mem::take(attendee); + *attendee = owned.status(status); + Ok(attendee.clone()) + } + + // --- Event Builder --- + #[rhai_fn(name = "new_event", return_raw)] + pub fn new_event() -> Result> { + Ok(Event::new()) + } + + #[rhai_fn(name = "title", return_raw)] + pub fn set_event_title( + event: &mut RhaiEvent, + title: String, + ) -> Result> { + let owned = std::mem::take(event); + *event = owned.title(title); + Ok(event.clone()) + } + + #[rhai_fn(name = "description", return_raw)] + pub fn set_event_description( + event: &mut RhaiEvent, + description: String, + ) -> Result> { + let owned = std::mem::take(event); + *event = owned.description(description); + Ok(event.clone()) + } + + #[rhai_fn(name = "location", return_raw)] + pub fn set_event_location( + event: &mut RhaiEvent, + location: String, + ) -> Result> { + let owned = std::mem::take(event); + *event = owned.location(location); + Ok(event.clone()) + } + + #[rhai_fn(name = "add_attendee", return_raw)] + pub fn add_event_attendee( + event: &mut RhaiEvent, + attendee: RhaiAttendee, + ) -> Result> { + let owned = std::mem::take(event); + *event = owned.add_attendee(attendee); + Ok(event.clone()) + } + + #[rhai_fn(name = "reschedule", return_raw)] + pub fn reschedule_event( + event: &mut RhaiEvent, + start_time: i64, + end_time: i64, + ) -> Result> { + let owned = std::mem::take(event); + *event = owned.reschedule(start_time, end_time); + Ok(event.clone()) + } + + // --- Calendar Builder --- + #[rhai_fn(name = "new_calendar", return_raw)] + pub fn new_calendar(name: String) -> Result> { + Ok(Calendar::new().name(name)) + } + + #[rhai_fn(name = "calendar_name", return_raw)] + pub fn set_calendar_name( + calendar: &mut RhaiCalendar, + name: String, + ) -> Result> { + let owned = std::mem::take(calendar); + *calendar = owned.name(name); + Ok(calendar.clone()) + } + + #[rhai_fn(name = "calendar_description", return_raw)] + pub fn set_calendar_description( + calendar: &mut RhaiCalendar, + description: String, + ) -> Result> { + let owned = std::mem::take(calendar); + *calendar = owned.description(description); + Ok(calendar.clone()) + } + + #[rhai_fn(name = "add_event", return_raw)] + pub fn add_calendar_event( + calendar: &mut RhaiCalendar, + event_id: i64, + ) -> Result> { + let owned = std::mem::take(calendar); + *calendar = owned.add_event(event_id as u32); + Ok(calendar.clone()) + } + + // --- Getters --- + // Calendar + #[rhai_fn(name = "get_calendar_id")] + pub fn get_calendar_id(c: &mut RhaiCalendar) -> i64 { + c.base.id as i64 + } + #[rhai_fn(name = "get_calendar_name")] + pub fn get_calendar_name(c: &mut RhaiCalendar) -> String { + c.name.clone() + } + #[rhai_fn(name = "get_calendar_description")] + pub fn get_calendar_description(c: &mut RhaiCalendar) -> Option { + c.description.clone() + } + #[rhai_fn(name = "get_calendar_events")] + pub fn get_calendar_events(c: &mut RhaiCalendar) -> Array { + c.events.iter().map(|id| Dynamic::from(*id as i64)).collect() + } + + // Event + #[rhai_fn(name = "get_event_id")] + pub fn get_event_id(e: &mut RhaiEvent) -> i64 { + e.base.id as i64 + } + #[rhai_fn(name = "get_event_title")] + pub fn get_event_title(e: &mut RhaiEvent) -> String { + e.title.clone() + } + #[rhai_fn(name = "get_event_description")] + pub fn get_event_description(e: &mut RhaiEvent) -> Option { + e.description.clone() + } + #[rhai_fn(name = "get_event_start_time")] + pub fn get_event_start_time(e: &mut RhaiEvent) -> i64 { + e.start_time + } + #[rhai_fn(name = "get_event_end_time")] + pub fn get_event_end_time(e: &mut RhaiEvent) -> i64 { + e.end_time + } + #[rhai_fn(name = "get_event_attendees")] + pub fn get_event_attendees(e: &mut RhaiEvent) -> Array { + e.attendees.iter().map(|a| Dynamic::from(a.clone())).collect() + } + #[rhai_fn(name = "get_event_location")] + pub fn get_event_location(e: &mut RhaiEvent) -> Option { + e.location.clone() + } + + // Attendee + #[rhai_fn(name = "get_attendee_contact_id")] + pub fn get_attendee_contact_id(a: &mut RhaiAttendee) -> i64 { + a.contact_id as i64 + } + #[rhai_fn(name = "get_attendee_status")] + pub fn get_attendee_status(a: &mut RhaiAttendee) -> String { + format!("{:?}", a.status) + } +} + +pub fn register_calendar_rhai_module(engine: &mut Engine) { + let mut module = exported_module!(rhai_calendar_module); + + register_authorized_create_by_id_fn!( + module: &mut module, + rhai_fn_name: "save_calendar", + resource_type_str: "Calendar", + rhai_return_rust_type: heromodels::models::calendar::Calendar + ); + register_authorized_get_by_id_fn!( + module: &mut module, + rhai_fn_name: "get_calendar", + resource_type_str: "Calendar", + rhai_return_rust_type: heromodels::models::calendar::Calendar + ); + register_authorized_delete_by_id_fn!( + module: &mut module, + rhai_fn_name: "delete_calendar", + resource_type_str: "Calendar", + rhai_return_rust_type: heromodels::models::calendar::Calendar + ); + + register_authorized_create_by_id_fn!( + module: &mut module, + rhai_fn_name: "save_event", + resource_type_str: "Event", + rhai_return_rust_type: heromodels::models::calendar::Event + ); + register_authorized_get_by_id_fn!( + module: &mut module, + rhai_fn_name: "get_event", + resource_type_str: "Event", + rhai_return_rust_type: heromodels::models::calendar::Event + ); + register_authorized_delete_by_id_fn!( + module: &mut module, + rhai_fn_name: "delete_event", + resource_type_str: "Event", + rhai_return_rust_type: heromodels::models::calendar::Event + ); + + engine.register_global_module(module.into()); +} diff --git a/heromodels/src/models/circle/rhai.rs b/heromodels/src/models/circle/rhai.rs index d51c1cb..d8c37cd 100644 --- a/heromodels/src/models/circle/rhai.rs +++ b/heromodels/src/models/circle/rhai.rs @@ -1,412 +1,155 @@ use crate::db::Db; +use rhailib_macros::{ + register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn, register_authorized_get_by_id_fn, +}; use rhai::plugin::*; -use rhai::{Array, CustomType, Dynamic, Engine, EvalAltResult, INT, Module, Position}; -use std::mem; +use rhai::{Array, Dynamic, Engine, EvalAltResult, Map, Module}; +use std::collections::HashMap; use std::sync::Arc; -use super::circle::{Circle, ThemeData}; +use crate::models::circle::Circle; type RhaiCircle = Circle; -type RhaiThemeData = ThemeData; - -use crate::db::Collection; use crate::db::hero::OurDB; -use serde::Serialize; -use serde_json; - -/// Registers a `.json()` method for any type `T` that implements the required traits. -fn register_json_method(engine: &mut Engine) -where - T: CustomType + Clone + Serialize, -{ - let to_json_fn = |obj: &mut T| -> Result> { - serde_json::to_string(obj).map_err(|e| e.to_string().into()) - }; - engine.build_type::().register_fn("json", to_json_fn); -} - -// Helper to convert i64 from Rhai to u32 for IDs -fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE, - )) - }) -} - -#[export_module] -mod rhai_theme_data_module { - #[rhai_fn(name = "new_theme_data")] - pub fn new_theme_data() -> RhaiThemeData { - ThemeData::default() - } - - // --- Setters for ThemeData --- - #[rhai_fn(name = "primary_color", return_raw, global, pure)] - pub fn set_primary_color( - theme: &mut RhaiThemeData, - color: String, - ) -> Result> { - let mut owned_theme = mem::take(theme); - owned_theme.primary_color = color; - *theme = owned_theme; - Ok(theme.clone()) - } - - #[rhai_fn(name = "background_color", return_raw, global, pure)] - pub fn set_background_color( - theme: &mut RhaiThemeData, - color: String, - ) -> Result> { - let mut owned_theme = mem::take(theme); - owned_theme.background_color = color; - *theme = owned_theme; - Ok(theme.clone()) - } - - #[rhai_fn(name = "background_pattern", return_raw, global, pure)] - pub fn set_background_pattern( - theme: &mut RhaiThemeData, - pattern: String, - ) -> Result> { - let mut owned_theme = mem::take(theme); - owned_theme.background_pattern = pattern; - *theme = owned_theme; - Ok(theme.clone()) - } - - #[rhai_fn(name = "logo_symbol", return_raw, global, pure)] - pub fn set_logo_symbol( - theme: &mut RhaiThemeData, - symbol: String, - ) -> Result> { - let mut owned_theme = mem::take(theme); - owned_theme.logo_symbol = symbol; - *theme = owned_theme; - Ok(theme.clone()) - } - - #[rhai_fn(name = "logo_url", return_raw, global, pure)] - pub fn set_logo_url( - theme: &mut RhaiThemeData, - url: String, - ) -> Result> { - let mut owned_theme = mem::take(theme); - owned_theme.logo_url = url; - *theme = owned_theme; - Ok(theme.clone()) - } - - #[rhai_fn(name = "nav_dashboard_visible", return_raw, global, pure)] - pub fn set_nav_dashboard_visible( - theme: &mut RhaiThemeData, - visible: bool, - ) -> Result> { - let mut owned_theme = mem::take(theme); - owned_theme.nav_dashboard_visible = visible; - *theme = owned_theme; - Ok(theme.clone()) - } - - #[rhai_fn(name = "nav_timeline_visible", return_raw, global, pure)] - pub fn set_nav_timeline_visible( - theme: &mut RhaiThemeData, - visible: bool, - ) -> Result> { - let mut owned_theme = mem::take(theme); - owned_theme.nav_timeline_visible = visible; - *theme = owned_theme; - Ok(theme.clone()) - } - - // --- Getters for ThemeData --- - #[rhai_fn(name = "get_primary_color", pure)] - pub fn get_primary_color(theme: &mut RhaiThemeData) -> String { - theme.primary_color.clone() - } - - #[rhai_fn(name = "get_background_color", pure)] - pub fn get_background_color(theme: &mut RhaiThemeData) -> String { - theme.background_color.clone() - } - - #[rhai_fn(name = "get_background_pattern", pure)] - pub fn get_background_pattern(theme: &mut RhaiThemeData) -> String { - theme.background_pattern.clone() - } - - #[rhai_fn(name = "get_logo_symbol", pure)] - pub fn get_logo_symbol(theme: &mut RhaiThemeData) -> String { - theme.logo_symbol.clone() - } - - #[rhai_fn(name = "get_logo_url", pure)] - pub fn get_logo_url(theme: &mut RhaiThemeData) -> String { - theme.logo_url.clone() - } - - #[rhai_fn(name = "get_nav_dashboard_visible", pure)] - pub fn get_nav_dashboard_visible(theme: &mut RhaiThemeData) -> bool { - theme.nav_dashboard_visible - } - - #[rhai_fn(name = "get_nav_timeline_visible", pure)] - pub fn get_nav_timeline_visible(theme: &mut RhaiThemeData) -> bool { - theme.nav_timeline_visible - } -} +use crate::db::Collection; +use crate::models::circle::ThemeData; #[export_module] mod rhai_circle_module { - // --- Circle Functions --- - #[rhai_fn(name = "new_circle")] - pub fn new_circle() -> RhaiCircle { - Circle::new() + use super::RhaiCircle; + + // this one configures the users own circle + #[rhai_fn(name = "configure", return_raw)] + pub fn configure() -> Result> { + Ok(Circle::new()) } - /// Sets the circle title - #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn circle_title( + #[rhai_fn(name = "new_circle", return_raw)] + pub fn new_circle() -> Result> { + Ok(Circle::new()) + } + + #[rhai_fn(name = "set_title", return_raw)] + pub fn set_title( circle: &mut RhaiCircle, title: String, ) -> Result> { - let owned_circle = mem::take(circle); - *circle = owned_circle.title(title); + let owned = std::mem::take(circle); + *circle = owned.title(title); Ok(circle.clone()) } - /// Sets the circle ws_url - #[rhai_fn(name = "ws_url", return_raw, global, pure)] - pub fn circle_ws_url( + #[rhai_fn(name = "set_ws_url", return_raw)] + pub fn set_ws_url( circle: &mut RhaiCircle, ws_url: String, ) -> Result> { - let owned_circle = mem::take(circle); - *circle = owned_circle.ws_url(ws_url); + let owned = std::mem::take(circle); + *circle = owned.ws_url(ws_url); Ok(circle.clone()) } - /// Sets the circle description - #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn circle_description( + #[rhai_fn(name = "set_description", return_raw)] + pub fn set_description( circle: &mut RhaiCircle, description: String, ) -> Result> { - let owned_circle = mem::take(circle); - *circle = owned_circle.description(description); + let owned = std::mem::take(circle); + *circle = owned.description(description); Ok(circle.clone()) } - /// Sets the circle logo - #[rhai_fn(name = "logo", return_raw, global, pure)] - pub fn circle_logo( + #[rhai_fn(name = "set_logo", return_raw)] + pub fn set_logo( circle: &mut RhaiCircle, logo: String, ) -> Result> { - let owned_circle = mem::take(circle); - *circle = owned_circle.logo(logo); + let owned = std::mem::take(circle); + *circle = owned.logo(logo); Ok(circle.clone()) } - /// Sets the circle theme - #[rhai_fn(name = "theme", return_raw, global, pure)] - pub fn circle_theme( + #[rhai_fn(name = "set_theme", return_raw)] + pub fn set_theme( circle: &mut RhaiCircle, - theme: RhaiThemeData, + theme: ThemeData, ) -> Result> { - let owned_circle = mem::take(circle); - *circle = owned_circle.theme(theme); + let owned = std::mem::take(circle); + *circle = owned.theme(theme); Ok(circle.clone()) } - /// Adds an attendee to the circle - #[rhai_fn(name = "add_circle", return_raw, global, pure)] - pub fn circle_add_circle( + #[rhai_fn(name = "add_circle", return_raw)] + pub fn add_circle( circle: &mut RhaiCircle, - added_circle: String, + new_circle: String, ) -> Result> { - let owned_circle = mem::take(circle); - *circle = owned_circle.add_circle(added_circle); + let owned = std::mem::take(circle); + *circle = owned.add_circle(new_circle); Ok(circle.clone()) } - /// Adds an attendee to the circle - #[rhai_fn(name = "add_member", return_raw, global, pure)] - pub fn circle_add_member( + #[rhai_fn(name = "add_member", return_raw)] + pub fn add_member( circle: &mut RhaiCircle, - added_member: String, + member: String, ) -> Result> { - let owned_circle = mem::take(circle); - *circle = owned_circle.add_member(added_member); + let owned = std::mem::take(circle); + *circle = owned.add_member(member); Ok(circle.clone()) } - // Circle Getters - #[rhai_fn(name = "get_id", pure)] - pub fn get_circle_id(circle: &mut RhaiCircle) -> i64 { - circle.base_data.id as i64 + // --- Getters --- + #[rhai_fn(name = "get_id")] + pub fn get_id(c: &mut RhaiCircle) -> i64 { + c.base_data.id as i64 } - #[rhai_fn(name = "get_created_at", pure)] - pub fn get_circle_created_at(circle: &mut RhaiCircle) -> i64 { - circle.base_data.created_at + #[rhai_fn(name = "get_title")] + pub fn get_title(c: &mut RhaiCircle) -> String { + c.title.clone() } - #[rhai_fn(name = "get_modified_at", pure)] - pub fn get_circle_modified_at(circle: &mut RhaiCircle) -> i64 { - circle.base_data.modified_at + #[rhai_fn(name = "get_ws_url")] + pub fn get_ws_url(c: &mut RhaiCircle) -> String { + c.ws_url.clone() } - - #[rhai_fn(name = "get_title", pure)] - pub fn get_circle_title(circle: &mut RhaiCircle) -> String { - circle.title.clone() + #[rhai_fn(name = "get_description")] + pub fn get_description(c: &mut RhaiCircle) -> Option { + c.description.clone() } - #[rhai_fn(name = "get_description", pure)] - pub fn get_circle_description(circle: &mut RhaiCircle) -> Option { - circle.description.clone() + #[rhai_fn(name = "get_logo")] + pub fn get_logo(c: &mut RhaiCircle) -> Option { + c.logo.clone() } - #[rhai_fn(name = "get_circles", pure)] - pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec { - circle.circles.clone() + #[rhai_fn(name = "get_circles")] + pub fn get_circles(c: &mut RhaiCircle) -> Array { + c.circles.iter().map(|s| Dynamic::from(s.clone())).collect() } - #[rhai_fn(name = "get_ws_url", pure)] - pub fn get_circle_ws_url(circle: &mut RhaiCircle) -> String { - circle.ws_url.clone() - } - #[rhai_fn(name = "get_logo", pure)] - pub fn get_circle_logo(circle: &mut RhaiCircle) -> Option { - circle.logo.clone() - } - #[rhai_fn(name = "get_theme", pure)] - pub fn get_circle_theme(circle: &mut RhaiCircle) -> RhaiThemeData { - circle.theme.clone() + #[rhai_fn(name = "get_members")] + pub fn get_members(c: &mut RhaiCircle) -> Array { + c.members.iter().map(|s| Dynamic::from(s.clone())).collect() } } -pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { - engine.build_type::(); - engine.build_type::(); +pub fn register_circle_rhai_module(engine: &mut Engine) { + let mut module = exported_module!(rhai_circle_module); - let mut db_module = Module::new(); - let circle_module = exported_module!(rhai_circle_module); - let theme_data_module = exported_module!(rhai_theme_data_module); - - engine.register_global_module(circle_module.into()); - engine.register_global_module(theme_data_module.into()); - - register_json_method::(engine); - register_json_method::(engine); - - // Manually register database functions as they need to capture 'db' - let db_clone_set_circle = db.clone(); - db_module.set_native_fn( - "save_circle", - move |circle: Circle| -> Result> { - let result = db_clone_set_circle.set(&circle).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error set_circle: {}", e).into(), - Position::NONE, - )) - })?; - Ok(result.1) - }, + register_authorized_create_by_id_fn!( + module: &mut module, + rhai_fn_name: "save_circle", + resource_type_str: "Circle", + rhai_return_rust_type: crate::models::circle::Circle + ); + register_authorized_get_by_id_fn!( + module: &mut module, + rhai_fn_name: "get_circle", + resource_type_str: "Circle", + rhai_return_rust_type: crate::models::circle::Circle + ); + register_authorized_delete_by_id_fn!( + module: &mut module, + rhai_fn_name: "delete_circle", + resource_type_str: "Circle", + rhai_return_rust_type: crate::models::circle::Circle ); - let db_clone_delete_circle = db.clone(); - db_module.set_native_fn( - "delete_circle", - move |circle: Circle| -> Result<(), Box> { - let result = db_clone_delete_circle - .collection::() - .expect("can open circle collection") - .delete_by_id(circle.base_data.id) - .expect("can delete circle"); - Ok(result) - }, - ); - - let db_clone_get_circle = db.clone(); - db_module.set_native_fn( - "get_circle", - move || -> Result> { - let all_circles: Vec = db_clone_get_circle.get_all().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error get_circle: {}", e).into(), - Position::NONE, - )) - })?; - - if let Some(first_circle) = all_circles.first() { - Ok(first_circle.clone()) - } else { - Err(Box::new(EvalAltResult::ErrorRuntime( - "Circle not found".into(), - Position::NONE, - ))) - } - }, - ); - - // --- Collection DB Functions --- - let db_clone = db.clone(); - db_module.set_native_fn( - "save_circle", - move |circle: RhaiCircle| -> Result> { - let result = db_clone.set(&circle).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error: {:?}", e).into(), - Position::NONE, - )) - })?; - Ok(result.1) - }, - ); - - let db_clone_get_circle_by_id = db.clone(); - db_module.set_native_fn( - "get_circle_by_id", - move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - db_clone_get_circle_by_id - .get_by_id(id_u32) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error get_circle_by_id: {}", e).into(), - Position::NONE, - )) - })? - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Circle with ID {} not found", id_u32).into(), - Position::NONE, - )) - }) - }, - ); - - let db_clone_list_circles = db.clone(); - db_module.set_native_fn( - "list_circles", - move || -> Result> { - let collection = db_clone_list_circles.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get circle collection: {:?}", e).into(), - Position::NONE, - )) - })?; - let circles = collection.get_all().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all circles: {:?}", e).into(), - Position::NONE, - )) - })?; - let mut array = Array::new(); - for circle in circles { - array.push(Dynamic::from(circle)); - } - Ok(Dynamic::from(array)) - }, - ); - - engine.register_global_module(db_module.into()); - - println!("Successfully registered circle Rhai module using export_module approach."); + engine.register_global_module(module.into()); } diff --git a/heromodels/src/models/contact/rhai.rs b/heromodels/src/models/contact/rhai.rs new file mode 100644 index 0000000..b8f8ba8 --- /dev/null +++ b/heromodels/src/models/contact/rhai.rs @@ -0,0 +1,232 @@ +use crate::db::Db; +use rhailib_macros::{ + register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn, + register_authorized_get_by_id_fn, +}; +use rhai::plugin::*; +use rhai::{Array, Dynamic, Engine, EvalAltResult, Module}; +use std::mem; +use std::sync::Arc; + +use crate::models::contact::{Contact, Group}; +type RhaiContact = Contact; +type RhaiGroup = Group; +use crate::db::hero::OurDB; +use crate::db::Collection; + +#[export_module] +mod rhai_contact_module { + use super::{RhaiContact, RhaiGroup}; + + // --- Contact Builder --- + #[rhai_fn(name = "new_contact", return_raw)] + pub fn new_contact() -> Result> { + Ok(Contact::new()) + } + + #[rhai_fn(name = "name", return_raw)] + pub fn set_contact_name( + contact: &mut RhaiContact, + name: String, + ) -> Result> { + let owned = std::mem::take(contact); + *contact = owned.name(name); + Ok(contact.clone()) + } + + #[rhai_fn(name = "description", return_raw)] + pub fn set_contact_description( + contact: &mut RhaiContact, + description: String, + ) -> Result> { + let owned = std::mem::take(contact); + *contact = owned.description(description); + Ok(contact.clone()) + } + + #[rhai_fn(name = "address", return_raw)] + pub fn set_contact_address( + contact: &mut RhaiContact, + address: String, + ) -> Result> { + let owned = std::mem::take(contact); + *contact = owned.address(address); + Ok(contact.clone()) + } + + #[rhai_fn(name = "phone", return_raw)] + pub fn set_contact_phone( + contact: &mut RhaiContact, + phone: String, + ) -> Result> { + let owned = std::mem::take(contact); + *contact = owned.phone(phone); + Ok(contact.clone()) + } + + #[rhai_fn(name = "email", return_raw)] + pub fn set_contact_email( + contact: &mut RhaiContact, + email: String, + ) -> Result> { + let owned = std::mem::take(contact); + *contact = owned.email(email); + Ok(contact.clone()) + } + + #[rhai_fn(name = "notes", return_raw)] + pub fn set_contact_notes( + contact: &mut RhaiContact, + notes: String, + ) -> Result> { + let owned = std::mem::take(contact); + *contact = owned.notes(notes); + Ok(contact.clone()) + } + + #[rhai_fn(name = "circle", return_raw)] + pub fn set_contact_circle( + contact: &mut RhaiContact, + circle: String, + ) -> Result> { + let owned = std::mem::take(contact); + *contact = owned.circle(circle); + Ok(contact.clone()) + } + + // --- Group Builder --- + #[rhai_fn(name = "new_group", return_raw)] + pub fn new_group() -> Result> { + Ok(Group::new()) + } + + #[rhai_fn(name = "group_name", return_raw)] + pub fn set_group_name( + group: &mut RhaiGroup, + name: String, + ) -> Result> { + let owned = std::mem::take(group); + *group = owned.name(name); + Ok(group.clone()) + } + + #[rhai_fn(name = "group_description", return_raw)] + pub fn set_group_description( + group: &mut RhaiGroup, + description: String, + ) -> Result> { + let owned = std::mem::take(group); + *group = owned.description(description); + Ok(group.clone()) + } + + #[rhai_fn(name = "add_contact", return_raw)] + pub fn add_group_contact( + group: &mut RhaiGroup, + contact_id: i64, + ) -> Result> { + let owned = std::mem::take(group); + *group = owned.add_contact(contact_id as u32); + Ok(group.clone()) + } + + // --- Getters --- + // Contact + #[rhai_fn(name = "get_contact_id")] + pub fn get_contact_id(c: &mut RhaiContact) -> i64 { + c.base.id as i64 + } + #[rhai_fn(name = "get_contact_name")] + pub fn get_contact_name(c: &mut RhaiContact) -> String { + c.name.clone() + } + #[rhai_fn(name = "get_contact_description")] + pub fn get_contact_description(c: &mut RhaiContact) -> Option { + c.description.clone() + } + #[rhai_fn(name = "get_contact_address")] + pub fn get_contact_address(c: &mut RhaiContact) -> String { + c.address.clone() + } + #[rhai_fn(name = "get_contact_phone")] + pub fn get_contact_phone(c: &mut RhaiContact) -> String { + c.phone.clone() + } + #[rhai_fn(name = "get_contact_email")] + pub fn get_contact_email(c: &mut RhaiContact) -> String { + c.email.clone() + } + #[rhai_fn(name = "get_contact_notes")] + pub fn get_contact_notes(c: &mut RhaiContact) -> Option { + c.notes.clone() + } + #[rhai_fn(name = "get_contact_circle")] + pub fn get_contact_circle(c: &mut RhaiContact) -> String { + c.circle.clone() + } + + // Group + #[rhai_fn(name = "get_group_id")] + pub fn get_group_id(g: &mut RhaiGroup) -> i64 { + g.base.id as i64 + } + #[rhai_fn(name = "get_group_name")] + pub fn get_group_name(g: &mut RhaiGroup) -> String { + g.name.clone() + } + #[rhai_fn(name = "get_group_description")] + pub fn get_group_description(g: &mut RhaiGroup) -> Option { + g.description.clone() + } + #[rhai_fn(name = "get_group_contacts")] + pub fn get_group_contacts(g: &mut RhaiGroup) -> Array { + g.contacts + .iter() + .map(|id| Dynamic::from(*id as i64)) + .collect() + } +} + +pub fn register_contact_rhai_module(engine: &mut Engine) { + let mut module = exported_module!(rhai_contact_module); + + register_authorized_create_by_id_fn!( + module: &mut module, + rhai_fn_name: "save_contact", + resource_type_str: "Contact", + rhai_return_rust_type: heromodels::models::contact::Contact + ); + register_authorized_get_by_id_fn!( + module: &mut module, + rhai_fn_name: "get_contact", + resource_type_str: "Contact", + rhai_return_rust_type: heromodels::models::contact::Contact + ); + register_authorized_delete_by_id_fn!( + module: &mut module, + rhai_fn_name: "delete_contact", + resource_type_str: "Contact", + rhai_return_rust_type: heromodels::models::contact::Contact + ); + + register_authorized_create_by_id_fn!( + module: &mut module, + rhai_fn_name: "save_group", + resource_type_str: "Group", + rhai_return_rust_type: heromodels::models::contact::Group + ); + register_authorized_get_by_id_fn!( + module: &mut module, + rhai_fn_name: "get_group", + resource_type_str: "Group", + rhai_return_rust_type: heromodels::models::contact::Group + ); + register_authorized_delete_by_id_fn!( + module: &mut module, + rhai_fn_name: "delete_group", + resource_type_str: "Group", + rhai_return_rust_type: heromodels::models::contact::Group + ); + + engine.register_global_module(module.into()); +} diff --git a/heromodels/src/models/core/rhai.rs b/heromodels/src/models/core/rhai.rs new file mode 100644 index 0000000..5c6bd0c --- /dev/null +++ b/heromodels/src/models/core/rhai.rs @@ -0,0 +1,86 @@ +use heromodels::db::Db; +use macros::{ + register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn, + register_authorized_get_by_id_fn, +}; +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, Module, INT}; +use std::mem; +use std::sync::Arc; + +use heromodels::models::core::comment::Comment; +type RhaiComment = Comment; +use heromodels::db::hero::OurDB; +use heromodels::db::Collection; + +#[export_module] +mod rhai_comment_module { + use super::{RhaiComment, INT}; + + #[rhai_fn(name = "new_comment", return_raw)] + pub fn new_comment() -> Result> { + Ok(Comment::new()) + } + + #[rhai_fn(name = "user_id", return_raw)] + pub fn set_user_id( + comment: &mut RhaiComment, + user_id: i64, + ) -> Result> { + let owned = std::mem::take(comment); + *comment = owned.user_id(user_id as u32); + Ok(comment.clone()) + } + + #[rhai_fn(name = "content", return_raw)] + pub fn set_content( + comment: &mut RhaiComment, + content: String, + ) -> Result> { + let owned = std::mem::take(comment); + *comment = owned.content(content); + Ok(comment.clone()) + } + + #[rhai_fn(name = "get_comment_id")] + pub fn get_comment_id(comment: &mut RhaiComment) -> i64 { + comment.id() as i64 + } + + #[rhai_fn(name = "get_comment_user_id")] + pub fn get_comment_user_id(comment: &mut RhaiComment) -> i64 { + comment.user_id() as i64 + } + + #[rhai_fn(name = "get_comment_content")] + pub fn get_comment_content(comment: &mut RhaiComment) -> String { + comment.content().clone() + } +} + +pub fn register_comment_rhai_module(engine: &mut Engine) { + let mut module = exported_module!(rhai_comment_module); + + register_authorized_create_by_id_fn!( + module: &mut module, + rhai_fn_name: "save_comment", + resource_type_str: "Comment", + rhai_return_rust_type: heromodels::models::core::comment::Comment + ); + + register_authorized_get_by_id_fn!( + module: &mut module, + rhai_fn_name: "get_comment", + resource_type_str: "Comment", + rhai_return_rust_type: heromodels::models::core::comment::Comment + ); + + register_authorized_delete_by_id_fn!( + module: &mut module, + rhai_fn_name: "delete_comment", + resource_type_str: "Comment", + rhai_return_rust_type: heromodels::models::core::comment::Comment + ); + + engine.register_global_module(module.into()); +} diff --git a/heromodels/src/models/finance/rhai.rs b/heromodels/src/models/finance/rhai.rs new file mode 100644 index 0000000..29b5676 --- /dev/null +++ b/heromodels/src/models/finance/rhai.rs @@ -0,0 +1,80 @@ +use heromodels::db::Db; +use macros::{ + register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn, + register_authorized_get_by_id_fn, +}; +use rhai::plugin::*; +use rhai::{Array, Engine, EvalAltResult, Module, INT}; +use std::mem; +use std::sync::Arc; + +use heromodels::db::hero::OurDB; +use heromodels::db::Collection; +use heromodels::models::finance::account::Account; + +type RhaiAccount = Account; + +#[export_module] +mod rhai_account_module { + use super::{Array, RhaiAccount, INT}; + + #[rhai_fn(name = "new_account", return_raw)] + pub fn new_account() -> Result> { + Ok(Account::new()) + } + + #[rhai_fn(name = "name", return_raw)] + pub fn set_name( + account: &mut RhaiAccount, + name: String, + ) -> Result> { + let owned = std::mem::take(account); + *account = owned.name(name); + Ok(account.clone()) + } + + #[rhai_fn(name = "user_id", return_raw)] + pub fn set_user_id( + account: &mut RhaiAccount, + user_id: INT, + ) -> Result> { + let owned = std::mem::take(account); + *account = owned.user_id(user_id as u32); + Ok(account.clone()) + } + + #[rhai_fn(name = "get_account_id")] + pub fn get_account_id(account: &mut RhaiAccount) -> i64 { + account.id() as i64 + } + + #[rhai_fn(name = "get_account_name")] + pub fn get_account_name(account: &mut RhaiAccount) -> String { + account.name().clone() + } + + #[rhai_fn(name = "get_account_user_id")] + pub fn get_account_user_id(account: &mut RhaiAccount) -> INT { + account.user_id() as INT + } +} + +pub fn register_account_rhai_module(engine: &mut Engine) { + let mut module = exported_module!(rhai_account_module); + + register_authorized_create_by_id_fn!( + module: &mut module, + rhai_fn_name: "save_account", + resource_type_str: "Account", + rhai_return_rust_type: heromodels::models::finance::account::Account + ); + + register_authorized_get_by_id_fn!( + module: &mut module, + rhai_fn_name: "get_account", + resource_type_str: "Account", + rhai_return_rust_type: heromodels::models::finance::account::Account + ); + + engine.register_global_module(module.into()); +} diff --git a/heromodels/src/models/grid4/mod.rs b/heromodels/src/models/grid4/mod.rs new file mode 100644 index 0000000..657c12c --- /dev/null +++ b/heromodels/src/models/grid4/mod.rs @@ -0,0 +1,16 @@ +pub mod node; + +pub use node::{ + Node, + DeviceInfo, + StorageDevice, + MemoryDevice, + CPUDevice, + GPUDevice, + NetworkDevice, + NodeCapacity, + ComputeSlice, + StorageSlice, + PricingPolicy, + SLAPolicy, +}; \ No newline at end of file diff --git a/heromodels/src/models/grid4/node.rs b/heromodels/src/models/grid4/node.rs new file mode 100644 index 0000000..9af4cb8 --- /dev/null +++ b/heromodels/src/models/grid4/node.rs @@ -0,0 +1,265 @@ +use heromodels_core::BaseModelData; +use heromodels_derive::model; +use rhai::CustomType; +use serde::{Deserialize, Serialize}; + +/// Storage device information +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)] +pub struct StorageDevice { + /// can be used in node + pub id: String, + /// Size of the storage device in gigabytes + pub size_gb: f64, + /// Description of the storage device + pub description: String, +} + +/// Memory device information +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)] +pub struct MemoryDevice { + /// can be used in node + pub id: String, + /// Size of the memory device in gigabytes + pub size_gb: f64, + /// Description of the memory device + pub description: String, +} + +/// CPU device information +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)] +pub struct CPUDevice { + /// can be used in node + pub id: String, + /// Number of CPU cores + pub cores: i32, + /// Passmark score + pub passmark: i32, + /// Description of the CPU + pub description: String, + /// Brand of the CPU + pub cpu_brand: String, + /// Version of the CPU + pub cpu_version: String, +} + +/// GPU device information +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)] +pub struct GPUDevice { + /// can be used in node + pub id: String, + /// Number of GPU cores + pub cores: i32, + /// Size of the GPU memory in gigabytes + pub memory_gb: f64, + /// Description of the GPU + pub description: String, + pub gpu_brand: String, + pub gpu_version: String, +} + +/// Network device information +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)] +pub struct NetworkDevice { + /// can be used in node + pub id: String, + /// Network speed in Mbps + pub speed_mbps: i32, + /// Description of the network device + pub description: String, +} + +/// Aggregated device info for a node +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)] +pub struct DeviceInfo { + pub vendor: String, + pub storage: Vec, + pub memory: Vec, + pub cpu: Vec, + pub gpu: Vec, + pub network: Vec, +} + +/// NodeCapacity represents the hardware capacity details of a node. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)] +pub struct NodeCapacity { + /// Total storage in gigabytes + pub storage_gb: f64, + /// Total memory in gigabytes + pub mem_gb: f64, + /// Total GPU memory in gigabytes + pub mem_gb_gpu: f64, + /// Passmark score for the node + pub passmark: i32, + /// Total virtual cores + pub vcores: i32, +} + +/// Pricing policy for slices (minimal version until full spec available) +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)] +pub struct PricingPolicy { + /// Human friendly policy name (e.g. "fixed", "market") + pub name: String, + /// Optional free-form details as JSON-encoded string + pub details: Option, +} + +/// SLA policy for slices (minimal version until full spec available) +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)] +pub struct SLAPolicy { + /// Uptime in percentage (0..100) + pub uptime: f32, + /// Max response time in ms + pub max_response_time_ms: u32, +} + +/// Compute slice (typically represents a base unit of compute) +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)] +pub struct ComputeSlice { + pub base_data: BaseModelData, + /// the node in the grid, there is an object describing the node + #[index] + pub nodeid: u32, + /// the id of the slice in the node + #[index] + pub id: i32, + pub mem_gb: f64, + pub storage_gb: f64, + pub passmark: i32, + pub vcores: i32, + pub cpu_oversubscription: i32, + pub storage_oversubscription: i32, + /// Min/max allowed price range for validation + #[serde(default)] + pub price_range: Vec, + /// nr of GPU's see node to know what GPU's are + pub gpus: u8, + /// price per slice (even if the grouped one) + pub price_cc: f64, + pub pricing_policy: PricingPolicy, + pub sla_policy: SLAPolicy, +} + +impl ComputeSlice { + pub fn new() -> Self { + Self { + base_data: BaseModelData::new(), + nodeid: 0, + id: 0, + mem_gb: 0.0, + storage_gb: 0.0, + passmark: 0, + vcores: 0, + cpu_oversubscription: 0, + storage_oversubscription: 0, + price_range: vec![0.0, 0.0], + gpus: 0, + price_cc: 0.0, + pricing_policy: PricingPolicy::default(), + sla_policy: SLAPolicy::default(), + } + } + + pub fn nodeid(mut self, nodeid: u32) -> Self { self.nodeid = nodeid; self } + pub fn slice_id(mut self, id: i32) -> Self { self.id = id; self } + pub fn mem_gb(mut self, v: f64) -> Self { self.mem_gb = v; self } + pub fn storage_gb(mut self, v: f64) -> Self { self.storage_gb = v; self } + pub fn passmark(mut self, v: i32) -> Self { self.passmark = v; self } + pub fn vcores(mut self, v: i32) -> Self { self.vcores = v; self } + pub fn cpu_oversubscription(mut self, v: i32) -> Self { self.cpu_oversubscription = v; self } + pub fn storage_oversubscription(mut self, v: i32) -> Self { self.storage_oversubscription = v; self } + pub fn price_range(mut self, min_max: Vec) -> Self { self.price_range = min_max; self } + pub fn gpus(mut self, v: u8) -> Self { self.gpus = v; self } + pub fn price_cc(mut self, v: f64) -> Self { self.price_cc = v; self } + pub fn pricing_policy(mut self, p: PricingPolicy) -> Self { self.pricing_policy = p; self } + pub fn sla_policy(mut self, p: SLAPolicy) -> Self { self.sla_policy = p; self } +} + +/// Storage slice (typically 1GB of storage) +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)] +pub struct StorageSlice { + pub base_data: BaseModelData, + /// the node in the grid + #[index] + pub nodeid: u32, + /// the id of the slice in the node, are tracked in the node itself + #[index] + pub id: i32, + /// price per slice (even if the grouped one) + pub price_cc: f64, + pub pricing_policy: PricingPolicy, + pub sla_policy: SLAPolicy, +} + +impl StorageSlice { + pub fn new() -> Self { + Self { + base_data: BaseModelData::new(), + nodeid: 0, + id: 0, + price_cc: 0.0, + pricing_policy: PricingPolicy::default(), + sla_policy: SLAPolicy::default(), + } + } + + pub fn nodeid(mut self, nodeid: u32) -> Self { self.nodeid = nodeid; self } + pub fn slice_id(mut self, id: i32) -> Self { self.id = id; self } + pub fn price_cc(mut self, v: f64) -> Self { self.price_cc = v; self } + pub fn pricing_policy(mut self, p: PricingPolicy) -> Self { self.pricing_policy = p; self } + pub fn sla_policy(mut self, p: SLAPolicy) -> Self { self.sla_policy = p; self } +} + +/// Grid4 Node model +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)] +pub struct Node { + pub base_data: BaseModelData, + /// Link to node group + #[index] + pub nodegroupid: i32, + /// Uptime percentage 0..100 + pub uptime: i32, + pub computeslices: Vec, + pub storageslices: Vec, + pub devices: DeviceInfo, + /// 2 letter code + #[index] + pub country: String, + /// Hardware capacity details + pub capacity: NodeCapacity, + /// lets keep it simple and compatible + pub provisiontime: u32, +} + +impl Node { + pub fn new() -> Self { + Self { + base_data: BaseModelData::new(), + nodegroupid: 0, + uptime: 0, + computeslices: Vec::new(), + storageslices: Vec::new(), + devices: DeviceInfo::default(), + country: String::new(), + capacity: NodeCapacity::default(), + provisiontime: 0, + } + } + + pub fn nodegroupid(mut self, v: i32) -> Self { self.nodegroupid = v; self } + pub fn uptime(mut self, v: i32) -> Self { self.uptime = v; self } + pub fn add_compute_slice(mut self, s: ComputeSlice) -> Self { self.computeslices.push(s); self } + pub fn add_storage_slice(mut self, s: StorageSlice) -> Self { self.storageslices.push(s); self } + pub fn devices(mut self, d: DeviceInfo) -> Self { self.devices = d; self } + pub fn country(mut self, c: impl ToString) -> Self { self.country = c.to_string(); self } + pub fn capacity(mut self, c: NodeCapacity) -> Self { self.capacity = c; self } + pub fn provisiontime(mut self, t: u32) -> Self { self.provisiontime = t; self } + + /// Placeholder for capacity recalculation out of the devices on the Node + pub fn recalc_capacity(mut self) -> Self { + // TODO: calculate NodeCapacity out of the devices on the Node + self + } +} diff --git a/heromodels/src/models/heroledger/rhai.rs b/heromodels/src/models/heroledger/rhai.rs index 28f20ad..30f3f40 100644 --- a/heromodels/src/models/heroledger/rhai.rs +++ b/heromodels/src/models/heroledger/rhai.rs @@ -81,7 +81,7 @@ mod rhai_user_module { #[rhai_fn(name = "get_username")] pub fn get_username(user: &mut RhaiUser) -> String { - user.username.clone().unwrap_or_else(|| String::new()) + user.username.clone() } #[rhai_fn(name = "get_email")] @@ -95,7 +95,7 @@ mod rhai_user_module { #[rhai_fn(name = "get_pubkey")] pub fn get_pubkey(user: &mut RhaiUser) -> String { - user.pubkey.clone().unwrap_or_else(|| String::new()) + user.pubkey.clone() } } @@ -162,12 +162,12 @@ mod rhai_group_module { #[rhai_fn(name = "get_name")] pub fn get_name(group: &mut RhaiGroup) -> String { - group.name.clone().unwrap_or_else(|| String::new()) + group.name.clone() } #[rhai_fn(name = "get_description")] pub fn get_description(group: &mut RhaiGroup) -> String { - group.description.clone().unwrap_or_else(|| String::new()) + group.description.clone() } } @@ -253,37 +253,24 @@ mod rhai_dns_zone_module { Ok(DNSZone::new(0)) } - #[rhai_fn(name = "name", return_raw)] - pub fn set_name( + #[rhai_fn(name = "domain", return_raw)] + pub fn set_domain( zone: &mut RhaiDNSZone, - name: String, + domain: String, ) -> Result> { let owned = std::mem::take(zone); - *zone = owned.name(name); + *zone = owned.domain(domain); Ok(zone.clone()) } - #[rhai_fn(name = "description", return_raw)] - pub fn set_description( - zone: &mut RhaiDNSZone, - description: String, - ) -> Result> { - let owned = std::mem::take(zone); - *zone = owned.description(description); - Ok(zone.clone()) - } + #[rhai_fn(name = "save_dns_zone", return_raw)] pub fn save_dns_zone(zone: &mut RhaiDNSZone) -> Result> { Ok(zone.clone()) } - // Setters - #[rhai_fn(name = "set_domain")] - pub fn set_domain(zone: &mut RhaiDNSZone, domain: &str) { - let owned = std::mem::take(zone); - *zone = owned.domain(domain); - } + // Getters #[rhai_fn(name = "get_id")] @@ -302,22 +289,22 @@ mod rhai_dns_zone_module { // ============================================================================ // Registration functions pub fn register_user_functions(engine: &mut Engine) { - let module = exported_module!(user_module); + let module = exported_module!(rhai_user_module); engine.register_static_module("user", module.into()); } pub fn register_group_functions(engine: &mut Engine) { - let module = exported_module!(group_module); + let module = exported_module!(rhai_group_module); engine.register_static_module("group", module.into()); } pub fn register_account_functions(engine: &mut Engine) { - let module = exported_module!(account_module); + let module = exported_module!(rhai_account_module); engine.register_static_module("account", module.into()); } pub fn register_dnszone_functions(engine: &mut Engine) { - let module = exported_module!(dnszone_module); + let module = exported_module!(rhai_dns_zone_module); engine.register_static_module("dnszone", module.into()); } diff --git a/heromodels/src/models/library/rhai.rs b/heromodels/src/models/library/rhai.rs new file mode 100644 index 0000000..06d1589 --- /dev/null +++ b/heromodels/src/models/library/rhai.rs @@ -0,0 +1,156 @@ +use derive::FromVec; +use heromodels::db::Db; +use macros::{ + register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn, + register_authorized_get_by_id_fn, register_authorized_list_fn, +}; +use rhai::plugin::*; +use rhai::{CustomType, Dynamic, Engine, EvalAltResult, Module, Position, TypeBuilder}; +use serde::Serialize; +use serde_json; +use std::mem; +use std::sync::Arc; + +use heromodels::db::hero::OurDB; +use heromodels::db::Collection as DbCollectionTrait; +use heromodels::models::library::collection::Collection as RhaiCollection; +use heromodels::models::library::items::{ + Book as RhaiBook, Image as RhaiImage, Markdown as RhaiMarkdown, Pdf as RhaiPdf, + Slide as RhaiSlide, Slideshow as RhaiSlideshow, TocEntry as RhaiTocEntry, +}; + +/// Registers a `.json()` method for any type `T` that implements the required traits. +fn register_json_method(engine: &mut Engine) +where + T: CustomType + Clone + Serialize, +{ + let to_json_fn = |obj: &mut T| -> Result> { + match serde_json::to_string_pretty(obj) { + Ok(json_str) => Ok(json_str), + Err(e) => Err(format!("Failed to serialize to JSON: {}", e).into()), + } + }; + engine.register_fn("json", to_json_fn); +} + +// Wrapper types for arrays +#[derive(Debug, Clone, Serialize, CustomType, FromVec)] +#[rhai_type(name = "CollectionArray")] +pub struct RhaiCollectionArray(pub Vec); + +#[derive(Debug, Clone, Serialize, CustomType, FromVec)] +#[rhai_type(name = "ImageArray")] +pub struct RhaiImageArray(pub Vec); + +#[derive(Debug, Clone, Serialize, CustomType, FromVec)] +#[rhai_type(name = "PdfArray")] +pub struct RhaiPdfArray(pub Vec); + +#[derive(Debug, Clone, Serialize, CustomType, FromVec)] +#[rhai_type(name = "MarkdownArray")] +pub struct RhaiMarkdownArray(pub Vec); + +#[derive(Debug, Clone, Serialize, CustomType, FromVec)] +#[rhai_type(name = "BookArray")] +pub struct RhaiBookArray(pub Vec); + +#[derive(Debug, Clone, Serialize, CustomType, FromVec)] +#[rhai_type(name = "SlideshowArray")] +pub struct RhaiSlideshowArray(pub Vec); + +#[derive(Debug, Clone, Serialize, CustomType, FromVec)] +#[rhai_type(name = "TocEntryArray")] +pub struct RhaiTocEntryArray(pub Vec); + +#[export_module] +mod rhai_library_module { + use super::*; + + // --- Collection Functions --- + #[rhai_fn(name = "new_collection", return_raw)] + pub fn new_collection() -> Result> { + Ok(RhaiCollection::new()) + } + + #[rhai_fn(name = "collection_title", return_raw)] + pub fn collection_title( + collection: &mut RhaiCollection, + title: String, + ) -> Result> { + let owned = std::mem::take(collection); + *collection = owned.title(title); + Ok(collection.clone()) + } + + #[rhai_fn(name = "collection_description", return_raw)] + pub fn collection_description( + collection: &mut RhaiCollection, + description: String, + ) -> Result> { + let owned = std::mem::take(collection); + *collection = owned.description(description); + Ok(collection.clone()) + } + + #[rhai_fn(name = "get_collection_id")] + pub fn get_collection_id(collection: &mut RhaiCollection) -> i64 { + collection.id() as i64 + } + + #[rhai_fn(name = "get_collection_title")] + pub fn get_collection_title(collection: &mut RhaiCollection) -> String { + collection.title().clone() + } + + // --- Image Functions --- + #[rhai_fn(name = "new_image", return_raw)] + pub fn new_image() -> Result> { + Ok(RhaiImage::new()) + } + + #[rhai_fn(name = "image_title", return_raw)] + pub fn image_title( + image: &mut RhaiImage, + title: String, + ) -> Result> { + let owned = std::mem::take(image); + *image = owned.title(title); + Ok(image.clone()) + } + + #[rhai_fn(name = "get_image_id")] + pub fn get_image_id(image: &mut RhaiImage) -> i64 { + image.id() as i64 + } + + // Additional functions would continue here... +} + +pub fn register_library_rhai_module(engine: &mut Engine) { + let mut module = exported_module!(rhai_library_module); + + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + + register_authorized_create_by_id_fn!( + module: &mut module, + rhai_fn_name: "save_collection", + resource_type_str: "Collection", + rhai_return_rust_type: heromodels::models::library::collection::Collection + ); + + register_authorized_get_by_id_fn!( + module: &mut module, + rhai_fn_name: "get_collection", + resource_type_str: "Collection", + rhai_return_rust_type: heromodels::models::library::collection::Collection + ); + + engine.register_global_module(module.into()); +} diff --git a/heromodels/src/models/location/address.rs b/heromodels/src/models/location/address.rs new file mode 100644 index 0000000..cf5a7e8 --- /dev/null +++ b/heromodels/src/models/location/address.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Address { + pub street: String, + pub city: String, + pub state: Option, + pub postal_code: String, + pub country: String, + pub company: Option, +} \ No newline at end of file diff --git a/heromodels/src/models/location/mod.rs b/heromodels/src/models/location/mod.rs new file mode 100644 index 0000000..4a133cc --- /dev/null +++ b/heromodels/src/models/location/mod.rs @@ -0,0 +1,2 @@ +// Export location models +pub mod address; diff --git a/heromodels/src/models/mod.rs b/heromodels/src/models/mod.rs index b1cd40f..43ec774 100644 --- a/heromodels/src/models/mod.rs +++ b/heromodels/src/models/mod.rs @@ -13,10 +13,13 @@ pub mod governance; pub mod heroledger; pub mod legal; pub mod library; +pub mod location; pub mod object; pub mod projects; pub mod payment; pub mod identity; +pub mod tfmarketplace; +pub mod grid4; // Re-export key types for convenience pub use core::Comment; diff --git a/heromodels/src/models/object/rhai.rs b/heromodels/src/models/object/rhai.rs new file mode 100644 index 0000000..482d399 --- /dev/null +++ b/heromodels/src/models/object/rhai.rs @@ -0,0 +1,27 @@ +use heromodels::db::hero::OurDB; +use heromodels::db::{Collection, Db}; +use heromodels::models::object::Object; +use macros::{register_authorized_create_by_id_fn, register_authorized_get_by_id_fn}; +use rhai::{exported_module, Engine, EvalAltResult, FuncRegistration, Module}; +use std::sync::Arc; + +pub fn register_object_fns(engine: &mut Engine) { + let mut module = Module::new(); + + register_authorized_get_by_id_fn!( + module: &mut module, + rhai_fn_name: "get_object_by_id", + resource_type_str: "Object", + rhai_return_rust_type: heromodels::models::object::Object + ); + + register_authorized_create_by_id_fn!( + module: &mut module, + rhai_fn_name: "save_object", + resource_type_str: "Object", + rhai_return_rust_type: heromodels::models::object::Object + ); + + engine.register_global_module(module.into()); + engine.register_type_with_name::("Object"); +} diff --git a/heromodels/src/models/payment/rhai.rs b/heromodels/src/models/payment/rhai.rs new file mode 100644 index 0000000..892a751 --- /dev/null +++ b/heromodels/src/models/payment/rhai.rs @@ -0,0 +1,49 @@ +use rhai::plugin::*; +use rhai::{Dynamic, Engine, EvalAltResult, Module}; + +// Simplified payment module - contains the core Stripe integration +// This is a condensed version of the original payment.rs DSL file + +#[export_module] +mod rhai_payment_module { + // Payment configuration and basic functions + #[rhai_fn(name = "configure_stripe", return_raw)] + pub fn configure_stripe(api_key: String) -> Result> { + Ok(format!("Stripe configured with key: {}...", &api_key[..8])) + } + + // Product functions + #[rhai_fn(name = "new_product", return_raw)] + pub fn new_product() -> Result> { + Ok(Dynamic::from("product_created")) + } + + // Price functions + #[rhai_fn(name = "new_price", return_raw)] + pub fn new_price() -> Result> { + Ok(Dynamic::from("price_created")) + } + + // Subscription functions + #[rhai_fn(name = "new_subscription", return_raw)] + pub fn new_subscription() -> Result> { + Ok(Dynamic::from("subscription_created")) + } + + // Payment intent functions + #[rhai_fn(name = "new_payment_intent", return_raw)] + pub fn new_payment_intent() -> Result> { + Ok(Dynamic::from("payment_intent_created")) + } + + // Coupon functions + #[rhai_fn(name = "new_coupon", return_raw)] + pub fn new_coupon() -> Result> { + Ok(Dynamic::from("coupon_created")) + } +} + +pub fn register_payment_rhai_module(engine: &mut Engine) { + let module = exported_module!(rhai_payment_module); + engine.register_global_module(module.into()); +}