From 1708e6dd063c08b4af875ed2eb5b23bbd9396a13 Mon Sep 17 00:00:00 2001 From: despiegk Date: Tue, 22 Apr 2025 12:53:17 +0400 Subject: [PATCH] ... --- {herodb => herodb_old}/.gitignore | 0 {herodb => herodb_old}/Cargo.lock | 0 {herodb => herodb_old}/Cargo.toml | 0 {herodb => herodb_old}/README.md | 0 .../aiprompts/builderparadigm.md | 0 .../aiprompts/moduledocu.md | 0 .../aiprompts/rhaiwrapping.md | 0 .../aiprompts/rhaiwrapping_advanced.md | 0 .../aiprompts/rhaiwrapping_best_practices.md | 0 .../examples/business_models_demo.rs | 0 .../examples/circle_basic_demo.rs | 0 .../examples/circle_models_demo.rs | 0 .../examples/circle_standalone.rs | 0 .../examples/ourdb_example.rs | 0 .../examples/tst_index_example.rs | 0 .../src/cmd/dbexample_biz/README.md | 0 .../src/cmd/dbexample_biz/main.rs | 0 .../src/cmd/dbexample_biz/mod.rs | 0 .../src/cmd/dbexample_gov/main.rs | 0 .../src/cmd/dbexample_mcc/main.rs | 0 .../src/cmd/dbexample_prod/main.rs | 0 {herodb => herodb_old}/src/cmd/mod.rs | 0 {herodb => herodb_old}/src/db/db.rs | 0 {herodb => herodb_old}/src/db/error.rs | 0 .../src/db/generic_store.rs | 0 {herodb => herodb_old}/src/db/macros.rs | 0 {herodb => herodb_old}/src/db/mod.rs | 0 {herodb => herodb_old}/src/db/model.rs | 0 .../src/db/model_methods.rs | 0 {herodb => herodb_old}/src/db/store.rs | 0 {herodb => herodb_old}/src/db/tests.rs | 0 {herodb => herodb_old}/src/db/tst_index.rs | 0 {herodb => herodb_old}/src/error.rs | 0 {herodb => herodb_old}/src/instructions.md | 0 {herodb => herodb_old}/src/lib.rs | 0 {herodb => herodb_old}/src/mod.rs | 0 .../src/models/biz/README.md | 0 .../src/models/biz/contract.rs | 0 .../src/models/biz/currency.rs | 0 .../src/models/biz/customer.rs | 0 .../src/models/biz/exchange_rate.rs | 0 .../src/models/biz/invoice.rs | 0 {herodb => herodb_old}/src/models/biz/lib.rs | 0 {herodb => herodb_old}/src/models/biz/mod.rs | 0 .../src/models/biz/product.rs | 0 .../src/models/biz/rhai/Cargo.lock | 0 .../src/models/biz/rhai/Cargo.toml | 0 .../src/models/biz/rhai/examples/example.rhai | 0 .../src/models/biz/rhai/examples/example.rs | 0 .../src/models/biz/rhai/src/engine.rs | 0 .../models/biz/rhai/src/generic_wrapper.rs | 0 .../src/models/biz/rhai/src/lib.rs | 0 .../src/models/biz/rhai/src/wrapper.rs | 0 {herodb => herodb_old}/src/models/biz/sale.rs | 0 .../src/models/biz/service.rs | 0 .../src/models/circle/README.md | 0 .../src/models/circle/circle.rs | 0 .../src/models/circle/lib.rs | 0 .../src/models/circle/member.rs | 0 .../src/models/circle/mod.rs | 0 .../src/models/circle/name.rs | 0 .../src/models/circle/wallet.rs | 0 .../src/models/gov/README.md | 0 .../src/models/gov/committee.rs | 0 .../src/models/gov/company.rs | 0 .../src/models/gov/meeting.rs | 0 {herodb => herodb_old}/src/models/gov/mod.rs | 0 .../src/models/gov/resolution.rs | 0 .../src/models/gov/shareholder.rs | 0 {herodb => herodb_old}/src/models/gov/user.rs | 0 {herodb => herodb_old}/src/models/gov/vote.rs | 0 .../src/models/instructions.md | 0 .../src/models/mcc/README.md | 0 .../src/models/mcc/calendar.rs | 0 .../src/models/mcc/contacts.rs | 0 .../src/models/mcc/event.rs | 0 {herodb => herodb_old}/src/models/mcc/lib.rs | 0 {herodb => herodb_old}/src/models/mcc/mail.rs | 0 .../src/models/mcc/message.rs | 0 {herodb => herodb_old}/src/models/mcc/mod.rs | 0 {herodb => herodb_old}/src/models/mod.rs | 0 .../src/models/py/README.md | 0 .../src/models/py/__init__.py | 0 .../models/py/__pycache__/api.cpython-312.pyc | Bin .../py/__pycache__/models.cpython-312.pyc | Bin {herodb => herodb_old}/src/models/py/api.py | 0 .../src/models/py/business.db | Bin .../src/models/py/example.py | 0 .../src/models/py/install_and_run.sh | 0 .../src/models/py/models.py | 0 .../src/models/py/server.sh | 0 .../src/rhaiengine/engine.rs | 0 {herodb => herodb_old}/src/rhaiengine/mod.rs | 0 heromodels/examples/basic_user_example.rs | 2 +- heromodels/src/core/lib.rs | 10 - heromodels/src/core/mod.rs | 7 - heromodels/src/herodb/db.rs | 646 ++++++++++++++++++ heromodels/src/herodb/error.rs | 30 + heromodels/src/herodb/generic_store.rs | 140 ++++ heromodels/src/herodb/macros.rs | 38 ++ heromodels/src/herodb/mod.rs | 33 + heromodels/src/herodb/model.rs | 96 +++ heromodels/src/herodb/model_methods.rs | 78 +++ heromodels/src/herodb/store.rs | 156 +++++ heromodels/src/herodb/tests.rs | 98 +++ heromodels/src/herodb/tst_index.rs | 261 +++++++ heromodels/src/lib.rs | 14 +- heromodels/src/{ => models}/core/comment.rs | 13 +- heromodels/src/models/core/mod.rs | 7 + heromodels/src/{ => models}/core/model.rs | 54 +- heromodels/src/models/lib.rs | 11 + heromodels/src/models/mod.rs | 8 + .../src/{ => models}/userexample/mod.rs | 0 .../src/{ => models}/userexample/user.rs | 15 +- heromodels/src/userexample/lib.rs | 6 - model_trait_unification_plan.md | 277 ++++++++ 116 files changed, 1919 insertions(+), 81 deletions(-) rename {herodb => herodb_old}/.gitignore (100%) rename {herodb => herodb_old}/Cargo.lock (100%) rename {herodb => herodb_old}/Cargo.toml (100%) rename {herodb => herodb_old}/README.md (100%) rename {herodb => herodb_old}/aiprompts/builderparadigm.md (100%) rename {herodb => herodb_old}/aiprompts/moduledocu.md (100%) rename {herodb => herodb_old}/aiprompts/rhaiwrapping.md (100%) rename {herodb => herodb_old}/aiprompts/rhaiwrapping_advanced.md (100%) rename {herodb => herodb_old}/aiprompts/rhaiwrapping_best_practices.md (100%) rename {herodb => herodb_old}/examples/business_models_demo.rs (100%) rename {herodb => herodb_old}/examples/circle_basic_demo.rs (100%) rename {herodb => herodb_old}/examples/circle_models_demo.rs (100%) rename {herodb => herodb_old}/examples/circle_standalone.rs (100%) rename {herodb => herodb_old}/examples/ourdb_example.rs (100%) rename {herodb => herodb_old}/examples/tst_index_example.rs (100%) rename {herodb => herodb_old}/src/cmd/dbexample_biz/README.md (100%) rename {herodb => herodb_old}/src/cmd/dbexample_biz/main.rs (100%) rename {herodb => herodb_old}/src/cmd/dbexample_biz/mod.rs (100%) rename {herodb => herodb_old}/src/cmd/dbexample_gov/main.rs (100%) rename {herodb => herodb_old}/src/cmd/dbexample_mcc/main.rs (100%) rename {herodb => herodb_old}/src/cmd/dbexample_prod/main.rs (100%) rename {herodb => herodb_old}/src/cmd/mod.rs (100%) rename {herodb => herodb_old}/src/db/db.rs (100%) rename {herodb => herodb_old}/src/db/error.rs (100%) rename {herodb => herodb_old}/src/db/generic_store.rs (100%) rename {herodb => herodb_old}/src/db/macros.rs (100%) rename {herodb => herodb_old}/src/db/mod.rs (100%) rename {herodb => herodb_old}/src/db/model.rs (100%) rename {herodb => herodb_old}/src/db/model_methods.rs (100%) rename {herodb => herodb_old}/src/db/store.rs (100%) rename {herodb => herodb_old}/src/db/tests.rs (100%) rename {herodb => herodb_old}/src/db/tst_index.rs (100%) rename {herodb => herodb_old}/src/error.rs (100%) rename {herodb => herodb_old}/src/instructions.md (100%) rename {herodb => herodb_old}/src/lib.rs (100%) rename {herodb => herodb_old}/src/mod.rs (100%) rename {herodb => herodb_old}/src/models/biz/README.md (100%) rename {herodb => herodb_old}/src/models/biz/contract.rs (100%) rename {herodb => herodb_old}/src/models/biz/currency.rs (100%) rename {herodb => herodb_old}/src/models/biz/customer.rs (100%) rename {herodb => herodb_old}/src/models/biz/exchange_rate.rs (100%) rename {herodb => herodb_old}/src/models/biz/invoice.rs (100%) rename {herodb => herodb_old}/src/models/biz/lib.rs (100%) rename {herodb => herodb_old}/src/models/biz/mod.rs (100%) rename {herodb => herodb_old}/src/models/biz/product.rs (100%) rename {herodb => herodb_old}/src/models/biz/rhai/Cargo.lock (100%) rename {herodb => herodb_old}/src/models/biz/rhai/Cargo.toml (100%) rename {herodb => herodb_old}/src/models/biz/rhai/examples/example.rhai (100%) rename {herodb => herodb_old}/src/models/biz/rhai/examples/example.rs (100%) rename {herodb => herodb_old}/src/models/biz/rhai/src/engine.rs (100%) rename {herodb => herodb_old}/src/models/biz/rhai/src/generic_wrapper.rs (100%) rename {herodb => herodb_old}/src/models/biz/rhai/src/lib.rs (100%) rename {herodb => herodb_old}/src/models/biz/rhai/src/wrapper.rs (100%) rename {herodb => herodb_old}/src/models/biz/sale.rs (100%) rename {herodb => herodb_old}/src/models/biz/service.rs (100%) rename {herodb => herodb_old}/src/models/circle/README.md (100%) rename {herodb => herodb_old}/src/models/circle/circle.rs (100%) rename {herodb => herodb_old}/src/models/circle/lib.rs (100%) rename {herodb => herodb_old}/src/models/circle/member.rs (100%) rename {herodb => herodb_old}/src/models/circle/mod.rs (100%) rename {herodb => herodb_old}/src/models/circle/name.rs (100%) rename {herodb => herodb_old}/src/models/circle/wallet.rs (100%) rename {herodb => herodb_old}/src/models/gov/README.md (100%) rename {herodb => herodb_old}/src/models/gov/committee.rs (100%) rename {herodb => herodb_old}/src/models/gov/company.rs (100%) rename {herodb => herodb_old}/src/models/gov/meeting.rs (100%) rename {herodb => herodb_old}/src/models/gov/mod.rs (100%) rename {herodb => herodb_old}/src/models/gov/resolution.rs (100%) rename {herodb => herodb_old}/src/models/gov/shareholder.rs (100%) rename {herodb => herodb_old}/src/models/gov/user.rs (100%) rename {herodb => herodb_old}/src/models/gov/vote.rs (100%) rename {herodb => herodb_old}/src/models/instructions.md (100%) rename {herodb => herodb_old}/src/models/mcc/README.md (100%) rename {herodb => herodb_old}/src/models/mcc/calendar.rs (100%) rename {herodb => herodb_old}/src/models/mcc/contacts.rs (100%) rename {herodb => herodb_old}/src/models/mcc/event.rs (100%) rename {herodb => herodb_old}/src/models/mcc/lib.rs (100%) rename {herodb => herodb_old}/src/models/mcc/mail.rs (100%) rename {herodb => herodb_old}/src/models/mcc/message.rs (100%) rename {herodb => herodb_old}/src/models/mcc/mod.rs (100%) rename {herodb => herodb_old}/src/models/mod.rs (100%) rename {herodb => herodb_old}/src/models/py/README.md (100%) rename {herodb => herodb_old}/src/models/py/__init__.py (100%) rename {herodb => herodb_old}/src/models/py/__pycache__/api.cpython-312.pyc (100%) rename {herodb => herodb_old}/src/models/py/__pycache__/models.cpython-312.pyc (100%) rename {herodb => herodb_old}/src/models/py/api.py (100%) rename {herodb => herodb_old}/src/models/py/business.db (100%) rename {herodb => herodb_old}/src/models/py/example.py (100%) rename {herodb => herodb_old}/src/models/py/install_and_run.sh (100%) rename {herodb => herodb_old}/src/models/py/models.py (100%) rename {herodb => herodb_old}/src/models/py/server.sh (100%) rename {herodb => herodb_old}/src/rhaiengine/engine.rs (100%) rename {herodb => herodb_old}/src/rhaiengine/mod.rs (100%) delete mode 100644 heromodels/src/core/lib.rs delete mode 100644 heromodels/src/core/mod.rs create mode 100644 heromodels/src/herodb/db.rs create mode 100644 heromodels/src/herodb/error.rs create mode 100644 heromodels/src/herodb/generic_store.rs create mode 100644 heromodels/src/herodb/macros.rs create mode 100644 heromodels/src/herodb/mod.rs create mode 100644 heromodels/src/herodb/model.rs create mode 100644 heromodels/src/herodb/model_methods.rs create mode 100644 heromodels/src/herodb/store.rs create mode 100644 heromodels/src/herodb/tests.rs create mode 100644 heromodels/src/herodb/tst_index.rs rename heromodels/src/{ => models}/core/comment.rs (82%) create mode 100644 heromodels/src/models/core/mod.rs rename heromodels/src/{ => models}/core/model.rs (87%) create mode 100644 heromodels/src/models/lib.rs create mode 100644 heromodels/src/models/mod.rs rename heromodels/src/{ => models}/userexample/mod.rs (100%) rename heromodels/src/{ => models}/userexample/user.rs (90%) delete mode 100644 heromodels/src/userexample/lib.rs create mode 100644 model_trait_unification_plan.md diff --git a/herodb/.gitignore b/herodb_old/.gitignore similarity index 100% rename from herodb/.gitignore rename to herodb_old/.gitignore diff --git a/herodb/Cargo.lock b/herodb_old/Cargo.lock similarity index 100% rename from herodb/Cargo.lock rename to herodb_old/Cargo.lock diff --git a/herodb/Cargo.toml b/herodb_old/Cargo.toml similarity index 100% rename from herodb/Cargo.toml rename to herodb_old/Cargo.toml diff --git a/herodb/README.md b/herodb_old/README.md similarity index 100% rename from herodb/README.md rename to herodb_old/README.md diff --git a/herodb/aiprompts/builderparadigm.md b/herodb_old/aiprompts/builderparadigm.md similarity index 100% rename from herodb/aiprompts/builderparadigm.md rename to herodb_old/aiprompts/builderparadigm.md diff --git a/herodb/aiprompts/moduledocu.md b/herodb_old/aiprompts/moduledocu.md similarity index 100% rename from herodb/aiprompts/moduledocu.md rename to herodb_old/aiprompts/moduledocu.md diff --git a/herodb/aiprompts/rhaiwrapping.md b/herodb_old/aiprompts/rhaiwrapping.md similarity index 100% rename from herodb/aiprompts/rhaiwrapping.md rename to herodb_old/aiprompts/rhaiwrapping.md diff --git a/herodb/aiprompts/rhaiwrapping_advanced.md b/herodb_old/aiprompts/rhaiwrapping_advanced.md similarity index 100% rename from herodb/aiprompts/rhaiwrapping_advanced.md rename to herodb_old/aiprompts/rhaiwrapping_advanced.md diff --git a/herodb/aiprompts/rhaiwrapping_best_practices.md b/herodb_old/aiprompts/rhaiwrapping_best_practices.md similarity index 100% rename from herodb/aiprompts/rhaiwrapping_best_practices.md rename to herodb_old/aiprompts/rhaiwrapping_best_practices.md diff --git a/herodb/examples/business_models_demo.rs b/herodb_old/examples/business_models_demo.rs similarity index 100% rename from herodb/examples/business_models_demo.rs rename to herodb_old/examples/business_models_demo.rs diff --git a/herodb/examples/circle_basic_demo.rs b/herodb_old/examples/circle_basic_demo.rs similarity index 100% rename from herodb/examples/circle_basic_demo.rs rename to herodb_old/examples/circle_basic_demo.rs diff --git a/herodb/examples/circle_models_demo.rs b/herodb_old/examples/circle_models_demo.rs similarity index 100% rename from herodb/examples/circle_models_demo.rs rename to herodb_old/examples/circle_models_demo.rs diff --git a/herodb/examples/circle_standalone.rs b/herodb_old/examples/circle_standalone.rs similarity index 100% rename from herodb/examples/circle_standalone.rs rename to herodb_old/examples/circle_standalone.rs diff --git a/herodb/examples/ourdb_example.rs b/herodb_old/examples/ourdb_example.rs similarity index 100% rename from herodb/examples/ourdb_example.rs rename to herodb_old/examples/ourdb_example.rs diff --git a/herodb/examples/tst_index_example.rs b/herodb_old/examples/tst_index_example.rs similarity index 100% rename from herodb/examples/tst_index_example.rs rename to herodb_old/examples/tst_index_example.rs diff --git a/herodb/src/cmd/dbexample_biz/README.md b/herodb_old/src/cmd/dbexample_biz/README.md similarity index 100% rename from herodb/src/cmd/dbexample_biz/README.md rename to herodb_old/src/cmd/dbexample_biz/README.md diff --git a/herodb/src/cmd/dbexample_biz/main.rs b/herodb_old/src/cmd/dbexample_biz/main.rs similarity index 100% rename from herodb/src/cmd/dbexample_biz/main.rs rename to herodb_old/src/cmd/dbexample_biz/main.rs diff --git a/herodb/src/cmd/dbexample_biz/mod.rs b/herodb_old/src/cmd/dbexample_biz/mod.rs similarity index 100% rename from herodb/src/cmd/dbexample_biz/mod.rs rename to herodb_old/src/cmd/dbexample_biz/mod.rs diff --git a/herodb/src/cmd/dbexample_gov/main.rs b/herodb_old/src/cmd/dbexample_gov/main.rs similarity index 100% rename from herodb/src/cmd/dbexample_gov/main.rs rename to herodb_old/src/cmd/dbexample_gov/main.rs diff --git a/herodb/src/cmd/dbexample_mcc/main.rs b/herodb_old/src/cmd/dbexample_mcc/main.rs similarity index 100% rename from herodb/src/cmd/dbexample_mcc/main.rs rename to herodb_old/src/cmd/dbexample_mcc/main.rs diff --git a/herodb/src/cmd/dbexample_prod/main.rs b/herodb_old/src/cmd/dbexample_prod/main.rs similarity index 100% rename from herodb/src/cmd/dbexample_prod/main.rs rename to herodb_old/src/cmd/dbexample_prod/main.rs diff --git a/herodb/src/cmd/mod.rs b/herodb_old/src/cmd/mod.rs similarity index 100% rename from herodb/src/cmd/mod.rs rename to herodb_old/src/cmd/mod.rs diff --git a/herodb/src/db/db.rs b/herodb_old/src/db/db.rs similarity index 100% rename from herodb/src/db/db.rs rename to herodb_old/src/db/db.rs diff --git a/herodb/src/db/error.rs b/herodb_old/src/db/error.rs similarity index 100% rename from herodb/src/db/error.rs rename to herodb_old/src/db/error.rs diff --git a/herodb/src/db/generic_store.rs b/herodb_old/src/db/generic_store.rs similarity index 100% rename from herodb/src/db/generic_store.rs rename to herodb_old/src/db/generic_store.rs diff --git a/herodb/src/db/macros.rs b/herodb_old/src/db/macros.rs similarity index 100% rename from herodb/src/db/macros.rs rename to herodb_old/src/db/macros.rs diff --git a/herodb/src/db/mod.rs b/herodb_old/src/db/mod.rs similarity index 100% rename from herodb/src/db/mod.rs rename to herodb_old/src/db/mod.rs diff --git a/herodb/src/db/model.rs b/herodb_old/src/db/model.rs similarity index 100% rename from herodb/src/db/model.rs rename to herodb_old/src/db/model.rs diff --git a/herodb/src/db/model_methods.rs b/herodb_old/src/db/model_methods.rs similarity index 100% rename from herodb/src/db/model_methods.rs rename to herodb_old/src/db/model_methods.rs diff --git a/herodb/src/db/store.rs b/herodb_old/src/db/store.rs similarity index 100% rename from herodb/src/db/store.rs rename to herodb_old/src/db/store.rs diff --git a/herodb/src/db/tests.rs b/herodb_old/src/db/tests.rs similarity index 100% rename from herodb/src/db/tests.rs rename to herodb_old/src/db/tests.rs diff --git a/herodb/src/db/tst_index.rs b/herodb_old/src/db/tst_index.rs similarity index 100% rename from herodb/src/db/tst_index.rs rename to herodb_old/src/db/tst_index.rs diff --git a/herodb/src/error.rs b/herodb_old/src/error.rs similarity index 100% rename from herodb/src/error.rs rename to herodb_old/src/error.rs diff --git a/herodb/src/instructions.md b/herodb_old/src/instructions.md similarity index 100% rename from herodb/src/instructions.md rename to herodb_old/src/instructions.md diff --git a/herodb/src/lib.rs b/herodb_old/src/lib.rs similarity index 100% rename from herodb/src/lib.rs rename to herodb_old/src/lib.rs diff --git a/herodb/src/mod.rs b/herodb_old/src/mod.rs similarity index 100% rename from herodb/src/mod.rs rename to herodb_old/src/mod.rs diff --git a/herodb/src/models/biz/README.md b/herodb_old/src/models/biz/README.md similarity index 100% rename from herodb/src/models/biz/README.md rename to herodb_old/src/models/biz/README.md diff --git a/herodb/src/models/biz/contract.rs b/herodb_old/src/models/biz/contract.rs similarity index 100% rename from herodb/src/models/biz/contract.rs rename to herodb_old/src/models/biz/contract.rs diff --git a/herodb/src/models/biz/currency.rs b/herodb_old/src/models/biz/currency.rs similarity index 100% rename from herodb/src/models/biz/currency.rs rename to herodb_old/src/models/biz/currency.rs diff --git a/herodb/src/models/biz/customer.rs b/herodb_old/src/models/biz/customer.rs similarity index 100% rename from herodb/src/models/biz/customer.rs rename to herodb_old/src/models/biz/customer.rs diff --git a/herodb/src/models/biz/exchange_rate.rs b/herodb_old/src/models/biz/exchange_rate.rs similarity index 100% rename from herodb/src/models/biz/exchange_rate.rs rename to herodb_old/src/models/biz/exchange_rate.rs diff --git a/herodb/src/models/biz/invoice.rs b/herodb_old/src/models/biz/invoice.rs similarity index 100% rename from herodb/src/models/biz/invoice.rs rename to herodb_old/src/models/biz/invoice.rs diff --git a/herodb/src/models/biz/lib.rs b/herodb_old/src/models/biz/lib.rs similarity index 100% rename from herodb/src/models/biz/lib.rs rename to herodb_old/src/models/biz/lib.rs diff --git a/herodb/src/models/biz/mod.rs b/herodb_old/src/models/biz/mod.rs similarity index 100% rename from herodb/src/models/biz/mod.rs rename to herodb_old/src/models/biz/mod.rs diff --git a/herodb/src/models/biz/product.rs b/herodb_old/src/models/biz/product.rs similarity index 100% rename from herodb/src/models/biz/product.rs rename to herodb_old/src/models/biz/product.rs diff --git a/herodb/src/models/biz/rhai/Cargo.lock b/herodb_old/src/models/biz/rhai/Cargo.lock similarity index 100% rename from herodb/src/models/biz/rhai/Cargo.lock rename to herodb_old/src/models/biz/rhai/Cargo.lock diff --git a/herodb/src/models/biz/rhai/Cargo.toml b/herodb_old/src/models/biz/rhai/Cargo.toml similarity index 100% rename from herodb/src/models/biz/rhai/Cargo.toml rename to herodb_old/src/models/biz/rhai/Cargo.toml diff --git a/herodb/src/models/biz/rhai/examples/example.rhai b/herodb_old/src/models/biz/rhai/examples/example.rhai similarity index 100% rename from herodb/src/models/biz/rhai/examples/example.rhai rename to herodb_old/src/models/biz/rhai/examples/example.rhai diff --git a/herodb/src/models/biz/rhai/examples/example.rs b/herodb_old/src/models/biz/rhai/examples/example.rs similarity index 100% rename from herodb/src/models/biz/rhai/examples/example.rs rename to herodb_old/src/models/biz/rhai/examples/example.rs diff --git a/herodb/src/models/biz/rhai/src/engine.rs b/herodb_old/src/models/biz/rhai/src/engine.rs similarity index 100% rename from herodb/src/models/biz/rhai/src/engine.rs rename to herodb_old/src/models/biz/rhai/src/engine.rs diff --git a/herodb/src/models/biz/rhai/src/generic_wrapper.rs b/herodb_old/src/models/biz/rhai/src/generic_wrapper.rs similarity index 100% rename from herodb/src/models/biz/rhai/src/generic_wrapper.rs rename to herodb_old/src/models/biz/rhai/src/generic_wrapper.rs diff --git a/herodb/src/models/biz/rhai/src/lib.rs b/herodb_old/src/models/biz/rhai/src/lib.rs similarity index 100% rename from herodb/src/models/biz/rhai/src/lib.rs rename to herodb_old/src/models/biz/rhai/src/lib.rs diff --git a/herodb/src/models/biz/rhai/src/wrapper.rs b/herodb_old/src/models/biz/rhai/src/wrapper.rs similarity index 100% rename from herodb/src/models/biz/rhai/src/wrapper.rs rename to herodb_old/src/models/biz/rhai/src/wrapper.rs diff --git a/herodb/src/models/biz/sale.rs b/herodb_old/src/models/biz/sale.rs similarity index 100% rename from herodb/src/models/biz/sale.rs rename to herodb_old/src/models/biz/sale.rs diff --git a/herodb/src/models/biz/service.rs b/herodb_old/src/models/biz/service.rs similarity index 100% rename from herodb/src/models/biz/service.rs rename to herodb_old/src/models/biz/service.rs diff --git a/herodb/src/models/circle/README.md b/herodb_old/src/models/circle/README.md similarity index 100% rename from herodb/src/models/circle/README.md rename to herodb_old/src/models/circle/README.md diff --git a/herodb/src/models/circle/circle.rs b/herodb_old/src/models/circle/circle.rs similarity index 100% rename from herodb/src/models/circle/circle.rs rename to herodb_old/src/models/circle/circle.rs diff --git a/herodb/src/models/circle/lib.rs b/herodb_old/src/models/circle/lib.rs similarity index 100% rename from herodb/src/models/circle/lib.rs rename to herodb_old/src/models/circle/lib.rs diff --git a/herodb/src/models/circle/member.rs b/herodb_old/src/models/circle/member.rs similarity index 100% rename from herodb/src/models/circle/member.rs rename to herodb_old/src/models/circle/member.rs diff --git a/herodb/src/models/circle/mod.rs b/herodb_old/src/models/circle/mod.rs similarity index 100% rename from herodb/src/models/circle/mod.rs rename to herodb_old/src/models/circle/mod.rs diff --git a/herodb/src/models/circle/name.rs b/herodb_old/src/models/circle/name.rs similarity index 100% rename from herodb/src/models/circle/name.rs rename to herodb_old/src/models/circle/name.rs diff --git a/herodb/src/models/circle/wallet.rs b/herodb_old/src/models/circle/wallet.rs similarity index 100% rename from herodb/src/models/circle/wallet.rs rename to herodb_old/src/models/circle/wallet.rs diff --git a/herodb/src/models/gov/README.md b/herodb_old/src/models/gov/README.md similarity index 100% rename from herodb/src/models/gov/README.md rename to herodb_old/src/models/gov/README.md diff --git a/herodb/src/models/gov/committee.rs b/herodb_old/src/models/gov/committee.rs similarity index 100% rename from herodb/src/models/gov/committee.rs rename to herodb_old/src/models/gov/committee.rs diff --git a/herodb/src/models/gov/company.rs b/herodb_old/src/models/gov/company.rs similarity index 100% rename from herodb/src/models/gov/company.rs rename to herodb_old/src/models/gov/company.rs diff --git a/herodb/src/models/gov/meeting.rs b/herodb_old/src/models/gov/meeting.rs similarity index 100% rename from herodb/src/models/gov/meeting.rs rename to herodb_old/src/models/gov/meeting.rs diff --git a/herodb/src/models/gov/mod.rs b/herodb_old/src/models/gov/mod.rs similarity index 100% rename from herodb/src/models/gov/mod.rs rename to herodb_old/src/models/gov/mod.rs diff --git a/herodb/src/models/gov/resolution.rs b/herodb_old/src/models/gov/resolution.rs similarity index 100% rename from herodb/src/models/gov/resolution.rs rename to herodb_old/src/models/gov/resolution.rs diff --git a/herodb/src/models/gov/shareholder.rs b/herodb_old/src/models/gov/shareholder.rs similarity index 100% rename from herodb/src/models/gov/shareholder.rs rename to herodb_old/src/models/gov/shareholder.rs diff --git a/herodb/src/models/gov/user.rs b/herodb_old/src/models/gov/user.rs similarity index 100% rename from herodb/src/models/gov/user.rs rename to herodb_old/src/models/gov/user.rs diff --git a/herodb/src/models/gov/vote.rs b/herodb_old/src/models/gov/vote.rs similarity index 100% rename from herodb/src/models/gov/vote.rs rename to herodb_old/src/models/gov/vote.rs diff --git a/herodb/src/models/instructions.md b/herodb_old/src/models/instructions.md similarity index 100% rename from herodb/src/models/instructions.md rename to herodb_old/src/models/instructions.md diff --git a/herodb/src/models/mcc/README.md b/herodb_old/src/models/mcc/README.md similarity index 100% rename from herodb/src/models/mcc/README.md rename to herodb_old/src/models/mcc/README.md diff --git a/herodb/src/models/mcc/calendar.rs b/herodb_old/src/models/mcc/calendar.rs similarity index 100% rename from herodb/src/models/mcc/calendar.rs rename to herodb_old/src/models/mcc/calendar.rs diff --git a/herodb/src/models/mcc/contacts.rs b/herodb_old/src/models/mcc/contacts.rs similarity index 100% rename from herodb/src/models/mcc/contacts.rs rename to herodb_old/src/models/mcc/contacts.rs diff --git a/herodb/src/models/mcc/event.rs b/herodb_old/src/models/mcc/event.rs similarity index 100% rename from herodb/src/models/mcc/event.rs rename to herodb_old/src/models/mcc/event.rs diff --git a/herodb/src/models/mcc/lib.rs b/herodb_old/src/models/mcc/lib.rs similarity index 100% rename from herodb/src/models/mcc/lib.rs rename to herodb_old/src/models/mcc/lib.rs diff --git a/herodb/src/models/mcc/mail.rs b/herodb_old/src/models/mcc/mail.rs similarity index 100% rename from herodb/src/models/mcc/mail.rs rename to herodb_old/src/models/mcc/mail.rs diff --git a/herodb/src/models/mcc/message.rs b/herodb_old/src/models/mcc/message.rs similarity index 100% rename from herodb/src/models/mcc/message.rs rename to herodb_old/src/models/mcc/message.rs diff --git a/herodb/src/models/mcc/mod.rs b/herodb_old/src/models/mcc/mod.rs similarity index 100% rename from herodb/src/models/mcc/mod.rs rename to herodb_old/src/models/mcc/mod.rs diff --git a/herodb/src/models/mod.rs b/herodb_old/src/models/mod.rs similarity index 100% rename from herodb/src/models/mod.rs rename to herodb_old/src/models/mod.rs diff --git a/herodb/src/models/py/README.md b/herodb_old/src/models/py/README.md similarity index 100% rename from herodb/src/models/py/README.md rename to herodb_old/src/models/py/README.md diff --git a/herodb/src/models/py/__init__.py b/herodb_old/src/models/py/__init__.py similarity index 100% rename from herodb/src/models/py/__init__.py rename to herodb_old/src/models/py/__init__.py diff --git a/herodb/src/models/py/__pycache__/api.cpython-312.pyc b/herodb_old/src/models/py/__pycache__/api.cpython-312.pyc similarity index 100% rename from herodb/src/models/py/__pycache__/api.cpython-312.pyc rename to herodb_old/src/models/py/__pycache__/api.cpython-312.pyc diff --git a/herodb/src/models/py/__pycache__/models.cpython-312.pyc b/herodb_old/src/models/py/__pycache__/models.cpython-312.pyc similarity index 100% rename from herodb/src/models/py/__pycache__/models.cpython-312.pyc rename to herodb_old/src/models/py/__pycache__/models.cpython-312.pyc diff --git a/herodb/src/models/py/api.py b/herodb_old/src/models/py/api.py similarity index 100% rename from herodb/src/models/py/api.py rename to herodb_old/src/models/py/api.py diff --git a/herodb/src/models/py/business.db b/herodb_old/src/models/py/business.db similarity index 100% rename from herodb/src/models/py/business.db rename to herodb_old/src/models/py/business.db diff --git a/herodb/src/models/py/example.py b/herodb_old/src/models/py/example.py similarity index 100% rename from herodb/src/models/py/example.py rename to herodb_old/src/models/py/example.py diff --git a/herodb/src/models/py/install_and_run.sh b/herodb_old/src/models/py/install_and_run.sh similarity index 100% rename from herodb/src/models/py/install_and_run.sh rename to herodb_old/src/models/py/install_and_run.sh diff --git a/herodb/src/models/py/models.py b/herodb_old/src/models/py/models.py similarity index 100% rename from herodb/src/models/py/models.py rename to herodb_old/src/models/py/models.py diff --git a/herodb/src/models/py/server.sh b/herodb_old/src/models/py/server.sh similarity index 100% rename from herodb/src/models/py/server.sh rename to herodb_old/src/models/py/server.sh diff --git a/herodb/src/rhaiengine/engine.rs b/herodb_old/src/rhaiengine/engine.rs similarity index 100% rename from herodb/src/rhaiengine/engine.rs rename to herodb_old/src/rhaiengine/engine.rs diff --git a/herodb/src/rhaiengine/mod.rs b/herodb_old/src/rhaiengine/mod.rs similarity index 100% rename from herodb/src/rhaiengine/mod.rs rename to herodb_old/src/rhaiengine/mod.rs diff --git a/heromodels/examples/basic_user_example.rs b/heromodels/examples/basic_user_example.rs index 4b8f8e4..03fd28a 100644 --- a/heromodels/examples/basic_user_example.rs +++ b/heromodels/examples/basic_user_example.rs @@ -1,4 +1,4 @@ -use heromodels::{BaseModel, Comment, User, ModelBuilder}; +use heromodels::models::{Model, Comment, User}; fn main() { println!("Hero Models - Basic Usage Example"); diff --git a/heromodels/src/core/lib.rs b/heromodels/src/core/lib.rs deleted file mode 100644 index fcafca8..0000000 --- a/heromodels/src/core/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ - -pub mod model; -pub mod comment; - - - -// Re-export key types for convenience -pub use model::{BaseModel, BaseModelData, IndexKey, IndexKeyBuilder, ModelBuilder}; -pub use comment::Comment; -pub use crate::impl_model_builder; diff --git a/heromodels/src/core/mod.rs b/heromodels/src/core/mod.rs deleted file mode 100644 index 180b3d4..0000000 --- a/heromodels/src/core/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Export submodules -pub mod model; -pub mod comment; - -// Re-export key types for convenience -pub use model::{BaseModel, BaseModelData, IndexKey, IndexKeyBuilder, ModelBuilder}; -pub use comment::Comment; \ No newline at end of file diff --git a/heromodels/src/herodb/db.rs b/heromodels/src/herodb/db.rs new file mode 100644 index 0000000..c7247d6 --- /dev/null +++ b/heromodels/src/herodb/db.rs @@ -0,0 +1,646 @@ +use crate::db::error::{DbError, DbResult}; +use crate::db::model::{Model, IndexKey}; +use crate::db::store::{DbOperations, OurDbStore}; +use crate::db::generic_store::{GenericStore, GetId}; +use crate::db::tst_index::TSTIndexManager; +use std::any::TypeId; +use std::collections::HashMap; +use std::fmt::Debug; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, RwLock}; +use rhai::{CustomType, EvalAltResult, TypeBuilder}; +use serde::{Serialize, de::DeserializeOwned}; + +/// Represents a single database operation in a transaction +#[derive(Debug, Clone)] +enum DbOperation { + Set { + model_type: TypeId, + serialized: Vec, + model_prefix: String, // Add model prefix + model_id: u32, // Add model ID + }, + Delete { + model_type: TypeId, + id: u32, + model_prefix: String, // Add model prefix + }, +} + +/// Transaction state for DB operations +#[derive(Clone)] +pub struct TransactionState { + operations: Vec, + active: bool, +} + +impl TransactionState { + /// Create a new transaction state + pub fn new() -> Self { + Self { + operations: Vec::new(), + active: true, + } + } +} + +/// Main DB manager that automatically handles all models +#[derive(Clone, CustomType)] +pub struct DB { + db_path: PathBuf, + + // Type map for generic operations + type_map: HashMap>>, + + // TST index manager + tst_index: Arc>, + + // Transaction state + transaction: Arc>>, +} + +/// Builder for DB that allows registering models +#[derive(Clone, CustomType)] +pub struct DBBuilder { + base_path: PathBuf, + model_registrations: Vec>, +} + +/// Trait for model registration +pub trait ModelRegistration: Send + Sync { + fn register(&self, path: &Path) -> DbResult<(TypeId, Arc>)>; +} + +/// Implementation of ModelRegistration for any Model type +pub struct ModelRegistrar { + phantom: std::marker::PhantomData, +} + +impl ModelRegistrar { + pub fn new() -> Self { + Self { + phantom: std::marker::PhantomData, + } + } +} + +/// Implementation of ModelRegistration for any serializable type that implements GetId +pub struct TypeRegistrar { + prefix: &'static str, + phantom: std::marker::PhantomData, +} + +impl TypeRegistrar { + pub fn new(prefix: &'static str) -> Self { + Self { + prefix, + phantom: std::marker::PhantomData, + } + } +} + +impl ModelRegistration for ModelRegistrar { + fn register(&self, path: &Path) -> DbResult<(TypeId, Arc>)> { + let store = OurDbStore::::open(path.join(T::db_prefix()))?; + Ok((TypeId::of::(), Arc::new(RwLock::new(store)) as Arc>)) + } +} + +impl ModelRegistration for TypeRegistrar { + fn register(&self, path: &Path) -> DbResult<(TypeId, Arc>)> { + let store = GenericStore::::open(path, self.prefix)?; + Ok((TypeId::of::(), Arc::new(RwLock::new(store)) as Arc>)) + } +} + +impl DBBuilder { + /// Create a new DB builder + pub fn new>(base_path: P) -> Self { + Self { + base_path: base_path.into(), + model_registrations: Vec::new(), + } + } + + pub fn with_path>(base_path: P) -> Self { + Self { + base_path: base_path.into(), + model_registrations: Vec::new(), + } + } + + /// Register a model type with the DB + pub fn register_model(mut self) -> Self { + self.model_registrations + .push(Arc::new(ModelRegistrar::::new())); + self + } + + /// Register any serializable type with the DB + pub fn register_type( + mut self, + prefix: &'static str + ) -> Self { + self.model_registrations + .push(Arc::new(TypeRegistrar::::new(prefix))); + self + } + + /// Build the DB with the registered models + pub fn build(self) -> Result> { + let base_path = self.base_path; + + // Ensure base directory exists + if !base_path.exists() { + std::fs::create_dir_all(&base_path).map_err(|e| { + EvalAltResult::ErrorSystem("Could not create base dir".to_string(), Box::new(e)) + })?; + } + + // Register all models + let mut type_map: HashMap>> = HashMap::new(); + + for registration in self.model_registrations { + let (type_id, store) = registration.register(&base_path).map_err(|e| { + EvalAltResult::ErrorSystem("Could not register type".to_string(), Box::new(e)) + })?; + type_map.insert(type_id, store); + } + + // Create the TST index manager + let tst_index = TSTIndexManager::new(&base_path).map_err(|e| { + EvalAltResult::ErrorSystem("Could not create TST index manager".to_string(), Box::new(e)) + })?; + + let transaction = Arc::new(RwLock::new(None)); + + Ok(DB { + db_path: base_path, + type_map, + tst_index: Arc::new(RwLock::new(tst_index)), + transaction, + }) + } +} + +impl DB { + /// Create a new empty DB instance without any models + pub fn new>(base_path: P) -> DbResult { + let base_path = base_path.into(); + + // Ensure base directory exists + if !base_path.exists() { + std::fs::create_dir_all(&base_path)?; + } + + // Create the TST index manager + let tst_index = TSTIndexManager::new(&base_path)?; + + let transaction = Arc::new(RwLock::new(None)); + + Ok(Self { + db_path: base_path, + type_map: HashMap::new(), + tst_index: Arc::new(RwLock::new(tst_index)), + transaction, + }) + } + + // Transaction-related methods + + /// Begin a new transaction + pub fn begin_transaction(&self) -> DbResult<()> { + let mut tx = self.transaction.write().unwrap(); + if tx.is_some() { + return Err(DbError::TransactionError( + "Transaction already in progress".into(), + )); + } + *tx = Some(TransactionState::new()); + Ok(()) + } + + /// Check if a transaction is active + pub fn has_active_transaction(&self) -> bool { + let tx = self.transaction.read().unwrap(); + tx.is_some() && tx.as_ref().unwrap().active + } + + /// Apply a set operation with the serialized data - bypass transaction check + fn apply_set_operation(&self, model_type: TypeId, serialized: &[u8]) -> DbResult<()> { + // Get the database operations for this model type + if let Some(db_ops) = self.type_map.get(&model_type) { + // Just pass the raw serialized data to a special raw insert method + let mut db_ops_guard = db_ops.write().unwrap(); + return db_ops_guard.insert_raw(serialized); + } + + Err(DbError::GeneralError(format!( + "No DB registered for type ID {:?}", + model_type + ))) + } + + /// Commit the current transaction, applying all operations + pub fn commit_transaction(&self) -> DbResult<()> { + let mut tx_guard = self.transaction.write().unwrap(); + + if let Some(tx_state) = tx_guard.take() { + if !tx_state.active { + return Err(DbError::TransactionError("Transaction not active".into())); + } + + // Create a backup of the transaction state in case we need to rollback + let backup = tx_state.clone(); + + // Try to execute all operations + let result = (|| { + for op in tx_state.operations { + match op { + DbOperation::Set { + model_type, + serialized, + model_prefix, + model_id, + } => { + // Apply to OurDB + self.apply_set_operation(model_type, &serialized)?; + + // Apply to TST index (primary key only) + // We can't easily get the index keys in the transaction commit + // because we don't have the model type information at runtime + let mut tst_index = self.tst_index.write().unwrap(); + tst_index.set(&model_prefix, model_id, serialized.clone())?; + } + DbOperation::Delete { + model_type, + id, + model_prefix, + } => { + // For delete operations, we can't get the index keys from the model + // because it's already deleted. We'll just delete the primary key. + + // Apply to OurDB + let db_ops = self + .type_map + .get(&model_type) + .ok_or_else(|| DbError::TypeError)?; + let mut db_ops_guard = db_ops.write().unwrap(); + db_ops_guard.delete(id)?; + + // Apply to TST index (primary key only) + let mut tst_index = self.tst_index.write().unwrap(); + tst_index.delete(&model_prefix, id)?; + } + } + } + Ok(()) + })(); + + // If any operation failed, restore the transaction state + if result.is_err() { + *tx_guard = Some(backup); + return result; + } + + Ok(()) + } else { + Err(DbError::TransactionError("No active transaction".into())) + } + } + + /// Rollback the current transaction, discarding all operations + pub fn rollback_transaction(&self) -> DbResult<()> { + let mut tx = self.transaction.write().unwrap(); + if tx.is_none() { + return Err(DbError::TransactionError("No active transaction".into())); + } + *tx = None; + Ok(()) + } + + /// Get the path to the database + pub fn path(&self) -> &PathBuf { + &self.db_path + } + + // Generic methods that work with any supported model type + + /// Insert a model instance into its appropriate database based on type + pub fn set(&self, model: &T) -> DbResult<()> { + // Try to acquire a write lock on the transaction + let mut tx_guard = self.transaction.write().unwrap(); + + // Check if there's an active transaction + if let Some(tx_state) = tx_guard.as_mut() { + if tx_state.active { + // Serialize the model for later use + let serialized = model.to_bytes()?; + + // Get the index keys for this model + let index_keys = model.db_keys(); + + // Record a Set operation in the transaction with prefix and ID + tx_state.operations.push(DbOperation::Set { + model_type: TypeId::of::(), + serialized, + model_prefix: T::db_prefix().to_string(), + model_id: model.get_id(), + }); + + return Ok(()); + } + } + + // If we got here, either there's no transaction or it's not active + // Drop the write lock before doing a direct database operation + drop(tx_guard); + + // Execute directly + match self.type_map.get(&TypeId::of::()) { + Some(db_ops) => { + let mut db_ops_guard = db_ops.write().unwrap(); + db_ops_guard.insert(model)?; + + // Also update the TST index with all index keys + let mut tst_index = self.tst_index.write().unwrap(); + let prefix = T::db_prefix(); + let id = model.get_id(); + let data = model.to_bytes()?; + let index_keys = model.db_keys(); + tst_index.set_with_indexes(prefix, id, data, &index_keys)?; + + Ok(()) + }, + None => Err(DbError::TypeError), + } + } + + /// Insert any serializable struct that implements GetId + pub fn set_any( + &self, + item: &T, + prefix: &str + ) -> DbResult<()> { + // Try to acquire a write lock on the transaction + let mut tx_guard = self.transaction.write().unwrap(); + + // Check if there's an active transaction + if let Some(tx_state) = tx_guard.as_mut() { + if tx_state.active { + // Serialize the item for later use + let serialized = bincode::serialize(item).map_err(DbError::SerializationError)?; + + // Record a Set operation in the transaction with prefix and ID + tx_state.operations.push(DbOperation::Set { + model_type: TypeId::of::(), + serialized, + model_prefix: prefix.to_string(), + model_id: item.get_id(), + }); + + return Ok(()); + } + } + + // If we got here, either there's no transaction or it's not active + // Drop the write lock before doing a direct database operation + drop(tx_guard); + + // Execute directly + match self.type_map.get(&TypeId::of::()) { + Some(db_ops) => { + // Serialize the item + let data = bincode::serialize(item).map_err(DbError::SerializationError)?; + + // Insert the raw data + let mut db_ops_guard = db_ops.write().unwrap(); + db_ops_guard.insert_raw(&data)?; + + // Also update the TST index (primary key only) + let mut tst_index = self.tst_index.write().unwrap(); + tst_index.set(prefix, item.get_id(), data)?; + + Ok(()) + }, + None => Err(DbError::TypeError), + } + } + + /// Check the transaction state for the given type and id + fn check_transaction(&self, id: u32) -> Option, DbError>> { + // Try to acquire a read lock on the transaction + let tx_guard = self.transaction.read().unwrap(); + + if let Some(tx_state) = tx_guard.as_ref() { + if !tx_state.active { + return None; + } + + let type_id = TypeId::of::(); + + // Process operations in reverse order (last operation wins) + for op in tx_state.operations.iter().rev() { + match op { + // First check if this ID has been deleted in the transaction + DbOperation::Delete { + model_type, + id: op_id, + model_prefix: _, + } => { + if *model_type == type_id && *op_id == id { + // Return NotFound error for deleted records + return Some(Err(DbError::NotFound(id))); + } + } + // Then check if it has been set in the transaction + DbOperation::Set { + model_type, + serialized, + model_prefix: _, + model_id, + } => { + if *model_type == type_id && *model_id == id { + // Try to deserialize + match T::from_bytes(serialized) { + Ok(model) => { + return Some(Ok(Some(model))); + } + Err(_) => continue, // Skip if deserialization fails + } + } + } + } + } + } + + // Not found in transaction (continue to database) + None + } + + /// Get a model instance by its ID and type + pub fn get(&self, id: u32) -> DbResult { + // First check if there's a pending value in the current transaction + if let Some(tx_result) = self.check_transaction::(id) { + match tx_result { + Ok(Some(model)) => return Ok(model), + Ok(None) => return Err(DbError::NotFound(id)), + Err(e) => return Err(e), + } + } + + // If not found in transaction, get from database + match self.type_map.get(&TypeId::of::()) { + Some(db_ops) => { + let mut db_ops_guard = db_ops.write().unwrap(); + let any_result = db_ops_guard.get(id)?; + + // Try to downcast to T + match any_result.downcast::() { + Ok(boxed_t) => Ok(*boxed_t), + Err(_) => Err(DbError::TypeError), + } + } + None => Err(DbError::TypeError), + } + } + + /// Get any serializable struct by its ID and type + pub fn get_any( + &self, + id: u32 + ) -> DbResult { + // If not found in transaction, get from database + match self.type_map.get(&TypeId::of::()) { + Some(db_ops) => { + let mut db_ops_guard = db_ops.write().unwrap(); + let any_result = db_ops_guard.get(id)?; + + // Try to downcast to T + match any_result.downcast::() { + Ok(boxed_t) => Ok(*boxed_t), + Err(_) => Err(DbError::TypeError), + } + } + None => Err(DbError::TypeError), + } + } + + /// Delete a model instance by its ID and type + pub fn delete(&self, id: u32) -> DbResult<()> { + // Try to acquire a write lock on the transaction + let mut tx_guard = self.transaction.write().unwrap(); + + // Check if there's an active transaction + if let Some(tx_state) = tx_guard.as_mut() { + if tx_state.active { + // Record a Delete operation in the transaction + tx_state.operations.push(DbOperation::Delete { + model_type: TypeId::of::(), + id, + model_prefix: T::db_prefix().to_string(), + }); + + return Ok(()); + } + } + + // If we got here, either there's no transaction or it's not active + // Drop the write lock before doing a direct database operation + drop(tx_guard); + + // Execute directly + match self.type_map.get(&TypeId::of::()) { + Some(db_ops) => { + let mut db_ops_guard = db_ops.write().unwrap(); + db_ops_guard.delete(id)?; + + // Also delete from the TST index + let mut tst_index = self.tst_index.write().unwrap(); + tst_index.delete(T::db_prefix(), id)?; + + Ok(()) + } + None => Err(DbError::TypeError), + } + } + + /// Delete any serializable struct by its ID and type + pub fn delete_any( + &self, + id: u32, + prefix: &str + ) -> DbResult<()> { + // Execute directly + match self.type_map.get(&TypeId::of::()) { + Some(db_ops) => { + let mut db_ops_guard = db_ops.write().unwrap(); + db_ops_guard.delete(id)?; + + // Also delete from the TST index + let mut tst_index = self.tst_index.write().unwrap(); + tst_index.delete(prefix, id)?; + + Ok(()) + } + None => Err(DbError::TypeError), + } + } + + /// List all model instances of a given type + pub fn list(&self) -> DbResult> { + match self.type_map.get(&TypeId::of::()) { + Some(db_ops) => { + let db_ops_guard = db_ops.read().unwrap(); + let any_result = db_ops_guard.list()?; + + // Try to downcast to Vec + match any_result.downcast::>() { + Ok(boxed_vec) => Ok(*boxed_vec), + Err(_) => Err(DbError::TypeError), + } + } + None => Err(DbError::TypeError), + } + } + + /// List all instances of any serializable type + pub fn list_any( + &self + ) -> DbResult> { + match self.type_map.get(&TypeId::of::()) { + Some(db_ops) => { + let db_ops_guard = db_ops.read().unwrap(); + let any_result = db_ops_guard.list()?; + + // Try to downcast to Vec + match any_result.downcast::>() { + Ok(boxed_vec) => Ok(*boxed_vec), + Err(_) => Err(DbError::TypeError), + } + } + None => Err(DbError::TypeError), + } + } + + /// Get the history of a model instance + pub fn get_history(&self, id: u32, depth: u8) -> DbResult> { + match self.type_map.get(&TypeId::of::()) { + Some(db_ops) => { + let mut db_ops_guard = db_ops.write().unwrap(); + let any_results = db_ops_guard.get_history(id, depth)?; + + let mut results = Vec::with_capacity(any_results.len()); + for any_result in any_results { + match any_result.downcast::() { + Ok(boxed_t) => results.push(*boxed_t), + Err(_) => return Err(DbError::TypeError), + } + } + + Ok(results) + } + None => Err(DbError::TypeError), + } + } +} diff --git a/heromodels/src/herodb/error.rs b/heromodels/src/herodb/error.rs new file mode 100644 index 0000000..eecdff7 --- /dev/null +++ b/heromodels/src/herodb/error.rs @@ -0,0 +1,30 @@ +use thiserror::Error; +use std::fmt::Debug; + +/// Errors that can occur during database operations +#[derive(Error, Debug)] +pub enum DbError { + #[error("I/O error: {0}")] + IoError(#[from] std::io::Error), + + #[error("Serialization/Deserialization error: {0}")] + SerializationError(#[from] bincode::Error), + + #[error("Record not found for ID: {0}")] + NotFound(u32), + + #[error("Type mismatch during deserialization")] + TypeError, + + #[error("Transaction error: {0}")] + TransactionError(String), + + #[error("OurDB error: {0}")] + OurDbError(#[from] ourdb::Error), + + #[error("General database error: {0}")] + GeneralError(String), +} + +/// Result type for DB operations +pub type DbResult = Result; \ No newline at end of file diff --git a/heromodels/src/herodb/generic_store.rs b/heromodels/src/herodb/generic_store.rs new file mode 100644 index 0000000..b7bf993 --- /dev/null +++ b/heromodels/src/herodb/generic_store.rs @@ -0,0 +1,140 @@ +use crate::db::error::{DbError, DbResult}; +use crate::db::store::DbOperations; +use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; +use serde::{Serialize, de::DeserializeOwned}; +use std::marker::PhantomData; +use std::path::{Path, PathBuf}; +use std::any::Any; + +// Trait for getting ID from any serializable type +pub trait GetId { + fn get_id(&self) -> u32; +} + +/// A store implementation for any serializable type using OurDB as the backend +pub struct GenericStore { + db: OurDB, + path: PathBuf, + prefix: String, + _phantom: PhantomData, +} + +impl GenericStore { + /// Opens or creates an OurDB database at the specified path + pub fn open>(path: P, prefix: &str) -> DbResult { + let path_buf = path.as_ref().to_path_buf(); + let db_path = path_buf.join(prefix); + + // Create directory if it doesn't exist + std::fs::create_dir_all(&db_path).map_err(DbError::IoError)?; + + let config = OurDBConfig { + path: db_path.clone(), + incremental_mode: true, // Always use incremental mode for auto IDs + file_size: None, // Use default (500MB) + keysize: None, // Use default (4 bytes) + reset: None, // Don't reset existing database + }; + + let db = OurDB::new(config).map_err(DbError::OurDbError)?; + + Ok(Self { + db, + path: db_path, + prefix: prefix.to_string(), + _phantom: PhantomData, + }) + } + + /// Serializes an item to bytes + fn serialize(item: &T) -> DbResult> { + bincode::serialize(item).map_err(DbError::SerializationError) + } + + /// Deserializes bytes to an item + fn deserialize(data: &[u8]) -> DbResult { + bincode::deserialize(data).map_err(DbError::SerializationError) + } + + /// Gets the raw bytes for an item by ID + pub fn get_raw(&self, id: u32) -> DbResult> { + self.db.get(id).map_err(DbError::OurDbError) + } + + /// Lists all raw items as bytes + pub fn list_raw(&self) -> DbResult>> { + let items = self.db.list().map_err(DbError::OurDbError)?; + Ok(items) + } + + /// Get the prefix for this store + pub fn prefix(&self) -> &str { + &self.prefix + } +} + +impl DbOperations for GenericStore { + fn delete(&mut self, id: u32) -> DbResult<()> { + self.db.delete(id).map_err(DbError::OurDbError) + } + + fn get(&mut self, id: u32) -> DbResult> { + let data = self.db.get(id).map_err(DbError::OurDbError)?; + let item = Self::deserialize(&data)?; + Ok(Box::new(item)) + } + + fn list(&self) -> DbResult> { + let items = self.db.list().map_err(DbError::OurDbError)?; + let mut result = Vec::with_capacity(items.len()); + + for data in items { + let item = Self::deserialize(&data)?; + result.push(item); + } + + Ok(Box::new(result)) + } + + fn insert(&mut self, model: &dyn Any) -> DbResult<()> { + // Try to downcast to T + if let Some(item) = model.downcast_ref::() { + let data = Self::serialize(item)?; + let id = item.get_id(); + + let args = OurDBSetArgs { + id: Some(id), + data, + }; + + self.db.set(args).map_err(DbError::OurDbError) + } else { + Err(DbError::TypeError) + } + } + + fn insert_raw(&mut self, serialized: &[u8]) -> DbResult<()> { + // Deserialize to get the ID + let item = Self::deserialize(serialized)?; + let id = item.get_id(); + + let args = OurDBSetArgs { + id: Some(id), + data: serialized.to_vec(), + }; + + self.db.set(args).map_err(DbError::OurDbError) + } + + fn get_history(&mut self, id: u32, depth: u8) -> DbResult>> { + let history = self.db.get_history(id, depth).map_err(DbError::OurDbError)?; + let mut result = Vec::with_capacity(history.len()); + + for data in history { + let item = Self::deserialize(&data)?; + result.push(Box::new(item)); + } + + Ok(result) + } +} \ No newline at end of file diff --git a/heromodels/src/herodb/macros.rs b/heromodels/src/herodb/macros.rs new file mode 100644 index 0000000..66c001a --- /dev/null +++ b/heromodels/src/herodb/macros.rs @@ -0,0 +1,38 @@ +//! Macros for implementing model methods + +/// Macro to implement typed access methods on the DB struct for a given model +#[macro_export] +macro_rules! impl_model_methods { + ($model:ty, $singular:ident, $plural:ident) => { + impl DB { + paste::paste! { + /// Insert a model instance into the database + pub fn [](&mut self, item: $model) -> Result<(), Box> { + Ok(self.set(&item).map_err(|e| { + rhai::EvalAltResult::ErrorSystem("could not insert $singular".to_string(), Box::new(e)) + })?) + } + + /// Get a model instance by its ID + pub fn [](&mut self, id: u32) -> DbResult<$model> { + self.get::<$model>(id) + } + + /// Delete a model instance by its ID + pub fn [](&mut self, id: u32) -> DbResult<()> { + self.delete::<$model>(id) + } + + /// List all model instances + pub fn [](&mut self) -> DbResult> { + self.list::<$model>() + } + + /// Get history of a model instance + pub fn [](&mut self, id: u32, depth: u8) -> DbResult> { + self.get_history::<$model>(id, depth) + } + } + } + }; +} diff --git a/heromodels/src/herodb/mod.rs b/heromodels/src/herodb/mod.rs new file mode 100644 index 0000000..351c39d --- /dev/null +++ b/heromodels/src/herodb/mod.rs @@ -0,0 +1,33 @@ +// Export the error module +pub mod error; +pub use error::{DbError, DbResult}; + +// Export the model module +pub mod model; +pub use model::{Model, Storable, IndexKey, GetId}; + +// Export the store module +pub mod store; +pub use store::{DbOperations, OurDbStore}; + +// Export the generic store module +pub mod generic_store; +pub use generic_store::GenericStore; + +// Export the db module +pub mod db; +pub use db::{DB, DBBuilder, ModelRegistration, ModelRegistrar}; + +// Export the TST index module +pub mod tst_index; +pub use tst_index::TSTIndexManager; + +// Export macros for model methods +pub mod macros; + +// Export model-specific methods +pub mod model_methods; + +// Tests +#[cfg(test)] +mod tests; diff --git a/heromodels/src/herodb/model.rs b/heromodels/src/herodb/model.rs new file mode 100644 index 0000000..bad628b --- /dev/null +++ b/heromodels/src/herodb/model.rs @@ -0,0 +1,96 @@ +use crate::db::error::{DbError, DbResult}; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use std::fmt::Debug; + +/// Trait for models that can be serialized and deserialized +pub trait Storable: Serialize + for<'de> Deserialize<'de> + Sized { + /// Serializes the instance using bincode + fn to_bytes(&self) -> DbResult> { + bincode::serialize(self).map_err(DbError::SerializationError) + } + + /// Deserializes data from bytes into an instance + fn from_bytes(data: &[u8]) -> DbResult { + bincode::deserialize(data).map_err(DbError::SerializationError) + } +} + +/// Represents an index key for a model +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IndexKey { + /// The name of the index key + pub name: &'static str, + + /// The value of the index key for a specific model instance + pub value: String, +} + +/// Trait identifying a model suitable for the database +/// The 'static lifetime bound is required for type identification via Any +pub trait Model: Storable + Debug + Clone + Send + Sync + 'static { + /// Returns the unique ID for this model instance + fn get_id(&self) -> u32; + + /// Returns a prefix used for this model type in the database + /// Helps to logically separate different model types + fn db_prefix() -> &'static str; + + /// Returns a list of index keys for this model instance + /// These keys will be used to create additional indexes in the TST + /// The default implementation returns an empty vector + /// Override this method to provide custom indexes + fn db_keys(&self) -> Vec { + Vec::new() + } +} + +/// Trait for adapting any serializable struct to work with the database +/// This is a lighter-weight alternative to the Model trait +pub trait ModelAdapter { + /// Returns the unique ID for this model instance + fn get_id(&self) -> u32; + + /// Returns a prefix used for this model type in the database + fn db_prefix() -> &'static str; + + /// Returns a list of index keys for this model instance + fn db_keys(&self) -> Vec { + Vec::new() + } +} + +/// Trait for getting ID from any serializable type +pub trait GetId { + /// Returns the unique ID for this instance + fn get_id(&self) -> u32; +} + +/// Macro to automatically implement GetId for any struct with an id field of type u32 +#[macro_export] +macro_rules! impl_get_id { + ($type:ty) => { + impl GetId for $type { + fn get_id(&self) -> u32 { + self.id + } + } + }; +} + +/// Helper functions for serializing and deserializing any type +pub mod serialization { + use super::*; + + /// Serialize any serializable type to bytes + pub fn to_bytes(value: &T) -> DbResult> { + bincode::serialize(value).map_err(DbError::SerializationError) + } + + /// Deserialize bytes to any deserializable type + pub fn from_bytes(data: &[u8]) -> DbResult { + bincode::deserialize(data).map_err(DbError::SerializationError) + } +} + +// Note: We don't provide a blanket implementation of Storable +// Each model type must implement Storable explicitly \ No newline at end of file diff --git a/heromodels/src/herodb/model_methods.rs b/heromodels/src/herodb/model_methods.rs new file mode 100644 index 0000000..86734c4 --- /dev/null +++ b/heromodels/src/herodb/model_methods.rs @@ -0,0 +1,78 @@ +use crate::db::db::DB; +use crate::db::model::Model; +use crate::impl_model_methods; +use crate::DbResult; // Add DbResult import +use crate::models::biz::{Product, Sale, Currency, ExchangeRate, Service, Customer, Contract, Invoice}; +use crate::models::gov::{ + Company, Shareholder, Meeting, User, Vote, Resolution, + Committee + // ComplianceRequirement, ComplianceDocument, ComplianceAudit - These don't exist +}; +use crate::models::circle::{Circle, Member, Name, Wallet}; // Remove Asset + +// Implement model-specific methods for Product +impl_model_methods!(Product, product, products); + +// Implement model-specific methods for Sale +impl_model_methods!(Sale, sale, sales); + +// Implement model-specific methods for Currency +impl_model_methods!(Currency, currency, currencies); + +// Implement model-specific methods for ExchangeRate +impl_model_methods!(ExchangeRate, exchange_rate, exchange_rates); + +// Implement model-specific methods for Service +impl_model_methods!(Service, service, services); + +// Implement model-specific methods for Customer +impl_model_methods!(Customer, customer, customers); + +// Implement model-specific methods for Contract +impl_model_methods!(Contract, contract, contracts); + +// Implement model-specific methods for Invoice +impl_model_methods!(Invoice, invoice, invoices); + +// Implement model-specific methods for Company +impl_model_methods!(Company, company, companies); + +// Implement model-specific methods for Shareholder +impl_model_methods!(Shareholder, shareholder, shareholders); + +// Implement model-specific methods for Meeting +impl_model_methods!(Meeting, meeting, meetings); + +// Implement model-specific methods for User +impl_model_methods!(User, user, users); + +// Implement model-specific methods for Vote +impl_model_methods!(Vote, vote, votes); + +// Implement model-specific methods for Resolution +impl_model_methods!(Resolution, resolution, resolutions); + +// Implement model-specific methods for Committee +impl_model_methods!(Committee, committee, committees); + +// These models don't exist, so comment them out +// // Implement model-specific methods for ComplianceRequirement +// impl_model_methods!(ComplianceRequirement, compliance_requirement, compliance_requirements); + +// // Implement model-specific methods for ComplianceDocument +// impl_model_methods!(ComplianceDocument, compliance_document, compliance_documents); + +// // Implement model-specific methods for ComplianceAudit +// impl_model_methods!(ComplianceAudit, compliance_audit, compliance_audits); + +// Implement model-specific methods for Circle +impl_model_methods!(Circle, circle, circles); + +// Implement model-specific methods for Member +impl_model_methods!(Member, member, members); + +// Implement model-specific methods for Name +impl_model_methods!(Name, name, names); + +// Implement model-specific methods for Wallet +impl_model_methods!(Wallet, wallet, wallets); \ No newline at end of file diff --git a/heromodels/src/herodb/store.rs b/heromodels/src/herodb/store.rs new file mode 100644 index 0000000..0e11696 --- /dev/null +++ b/heromodels/src/herodb/store.rs @@ -0,0 +1,156 @@ +use crate::db::error::{DbError, DbResult}; +use crate::db::model::Model; +use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; +use std::marker::PhantomData; +use std::path::{Path, PathBuf}; +use std::any::Any; + +/// Trait for type-erased database operations +pub trait DbOperations: Send + Sync { + fn delete(&mut self, id: u32) -> DbResult<()>; + fn get(&mut self, id: u32) -> DbResult>; + fn list(&self) -> DbResult>; + fn insert(&mut self, model: &dyn Any) -> DbResult<()>; + fn insert_raw(&mut self, serialized: &[u8]) -> DbResult<()>; + fn get_history(&mut self, id: u32, depth: u8) -> DbResult>>; +} + +/// A store implementation using OurDB as the backend +pub struct OurDbStore { + db: OurDB, + path: PathBuf, + _phantom: PhantomData, +} + +impl OurDbStore { + /// Opens or creates an OurDB database at the specified path + pub fn open>(path: P) -> DbResult { + let path_buf = path.as_ref().to_path_buf(); + let db_path = path_buf.join(T::db_prefix()); + + // Create directory if it doesn't exist + std::fs::create_dir_all(&db_path).map_err(DbError::IoError)?; + + let config = OurDBConfig { + path: db_path.clone(), + incremental_mode: true, // Always use incremental mode for auto IDs + file_size: None, // Use default (500MB) + keysize: None, // Use default (4 bytes) + reset: None, // Don't reset existing database + }; + + let db = OurDB::new(config).map_err(DbError::OurDbError)?; + + Ok(Self { + db, + path: db_path, + _phantom: PhantomData, + }) + } + + /// Inserts or updates a model instance in the database + pub fn insert(&mut self, model: &T) -> DbResult<()> { + // Use the new method name + let data = model.to_bytes()?; + + // Don't pass the ID when using incremental mode + // OurDB will automatically assign an ID + self.db.set(OurDBSetArgs { + id: None, + data: &data, + }).map_err(DbError::OurDbError)?; + + Ok(()) + } + + /// Retrieves a model instance by its ID + pub fn get(&mut self, id: u32) -> DbResult { + let data = self.db.get(id).map_err(|e| { + match e { + ourdb::Error::NotFound(_) => DbError::NotFound(id), + _ => DbError::OurDbError(e), + } + })?; + + // Use the new method name + T::from_bytes(&data) + } + + /// Deletes a model instance by its ID + pub fn delete(&mut self, id: u32) -> DbResult<()> { + self.db.delete(id).map_err(|e| { + match e { + ourdb::Error::NotFound(_) => DbError::NotFound(id), + _ => DbError::OurDbError(e), + } + }) + } + + /// Lists all models of this type + pub fn list(&self) -> DbResult> { + // OurDB doesn't have a built-in list function, so we need to implement it + // This is a placeholder - in a real implementation, we would need to + // maintain a list of all IDs for each model type + Err(DbError::GeneralError("List operation not implemented yet".to_string())) + } + + /// Gets the history of a model by its ID + pub fn get_history(&mut self, id: u32, depth: u8) -> DbResult> { + let history_data = self.db.get_history(id, depth).map_err(|e| { + match e { + ourdb::Error::NotFound(_) => DbError::NotFound(id), + _ => DbError::OurDbError(e), + } + })?; + + let mut result = Vec::with_capacity(history_data.len()); + for data in history_data { + result.push(T::from_bytes(&data)?); + } + + Ok(result) + } +} + +impl DbOperations for OurDbStore { + fn delete(&mut self, id: u32) -> DbResult<()> { + self.delete(id) + } + + fn get(&mut self, id: u32) -> DbResult> { + let result = self.get(id)?; + Ok(Box::new(result)) + } + + fn list(&self) -> DbResult> { + // This doesn't require &mut self + let result = self.list()?; + Ok(Box::new(result)) + } + + fn insert(&mut self, model: &dyn Any) -> DbResult<()> { + // Downcast the Any to T + if let Some(model_t) = model.downcast_ref::() { + self.insert(model_t) + } else { + Err(DbError::TypeError) + } + } + + fn insert_raw(&mut self, serialized: &[u8]) -> DbResult<()> { + // Deserialize the raw bytes to a model + let model = T::from_bytes(serialized)?; + self.insert(&model) + } + + fn get_history(&mut self, id: u32, depth: u8) -> DbResult>> { + let history = self.get_history(id, depth)?; + let mut result = Vec::with_capacity(history.len()); + + for item in history { + result.push(Box::new(item) as Box); + } + + Ok(result) + } +} \ No newline at end of file diff --git a/heromodels/src/herodb/tests.rs b/heromodels/src/herodb/tests.rs new file mode 100644 index 0000000..924eee6 --- /dev/null +++ b/heromodels/src/herodb/tests.rs @@ -0,0 +1,98 @@ +use super::*; +use crate::db::model::Storable; +use serde::{Deserialize, Serialize}; +use tempfile::tempdir; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +struct TestModel { + id: u32, + name: String, +} + +impl Storable for TestModel {} + +impl Model for TestModel { + fn get_id(&self) -> u32 { + self.id + } + + fn db_prefix() -> &'static str { + "test" + } +} + +#[test] +fn test_tst_integration() { + // Create a temporary directory for the test + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path(); + + // Create a DB instance + let mut db = DB::new(path).unwrap(); + db.register::().unwrap(); + + // Create some test models + let model1 = TestModel { id: 1, name: "Test 1".to_string() }; + let model2 = TestModel { id: 2, name: "Test 2".to_string() }; + let model3 = TestModel { id: 3, name: "Test 3".to_string() }; + + // Insert the models + db.set(&model1).unwrap(); + db.set(&model2).unwrap(); + db.set(&model3).unwrap(); + + // List all models + let models = db.list::().unwrap(); + assert_eq!(models.len(), 3); + + // Verify that all models are in the list + assert!(models.contains(&model1)); + assert!(models.contains(&model2)); + assert!(models.contains(&model3)); + + // Delete a model + db.delete::(2).unwrap(); + + // List again + let models = db.list::().unwrap(); + assert_eq!(models.len(), 2); + assert!(models.contains(&model1)); + assert!(models.contains(&model3)); + assert!(!models.contains(&model2)); + + // Test transaction with commit + db.begin_transaction().unwrap(); + db.set(&model2).unwrap(); // Add back model2 + db.delete::(1).unwrap(); // Delete model1 + db.commit_transaction().unwrap(); + + // List again after transaction + let models = db.list::().unwrap(); + assert_eq!(models.len(), 2); + assert!(!models.contains(&model1)); + assert!(models.contains(&model2)); + assert!(models.contains(&model3)); + + // Test transaction with rollback + db.begin_transaction().unwrap(); + db.delete::(3).unwrap(); // Delete model3 + db.rollback_transaction().unwrap(); + + // List again after rollback + let models = db.list::().unwrap(); + assert_eq!(models.len(), 2); + assert!(!models.contains(&model1)); + assert!(models.contains(&model2)); + assert!(models.contains(&model3)); + + // Test the synchronize_tst_index method + // Since we can't directly access private fields, we'll just verify that + // the method runs without errors + db.synchronize_tst_index::().unwrap(); + + // Verify that our models are still accessible + let models = db.list::().unwrap(); + assert_eq!(models.len(), 2); + assert!(models.contains(&model2)); + assert!(models.contains(&model3)); +} \ No newline at end of file diff --git a/heromodels/src/herodb/tst_index.rs b/heromodels/src/herodb/tst_index.rs new file mode 100644 index 0000000..28fe019 --- /dev/null +++ b/heromodels/src/herodb/tst_index.rs @@ -0,0 +1,261 @@ +use crate::db::error::{DbError, DbResult}; +use crate::db::model::IndexKey; +use std::path::{Path, PathBuf}; +use std::collections::HashMap; +use tst::TST; + +/// Manages TST-based indexes for model objects +pub struct TSTIndexManager { + /// Base path for TST databases + base_path: PathBuf, + + /// Map of model prefixes to their TST instances + tst_instances: HashMap, +} + +impl TSTIndexManager { + /// Creates a new TST index manager + pub fn new>(base_path: P) -> DbResult { + let base_path = base_path.as_ref().to_path_buf(); + + // Create directory if it doesn't exist + std::fs::create_dir_all(&base_path).map_err(DbError::IoError)?; + + Ok(Self { + base_path, + tst_instances: HashMap::new(), + }) + } + + /// Gets or creates a TST instance for a model prefix + pub fn get_tst(&mut self, prefix: &str) -> DbResult<&mut TST> { + if !self.tst_instances.contains_key(prefix) { + // Create a new TST instance for this prefix + let tst_path = self.base_path.join(format!("{}_tst", prefix)); + let tst_path_str = tst_path.to_string_lossy().to_string(); + + // Create the TST + let tst = TST::new(&tst_path_str, false) + .map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?; + + // Insert it into the map + self.tst_instances.insert(prefix.to_string(), tst); + } + + // Return a mutable reference to the TST + Ok(self.tst_instances.get_mut(prefix).unwrap()) + } + + /// Adds or updates an object in the TST index with primary key + pub fn set(&mut self, prefix: &str, id: u32, data: Vec) -> DbResult<()> { + // Get the TST for this prefix + let tst = self.get_tst(prefix)?; + + // Create the primary key in the format prefix_id + let key = format!("{}_{}", prefix, id); + + // Set the key-value pair in the TST + tst.set(&key, data.clone()) + .map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?; + + Ok(()) + } + + /// Adds or updates an object in the TST index with additional index keys + pub fn set_with_indexes(&mut self, prefix: &str, id: u32, data: Vec, index_keys: &[IndexKey]) -> DbResult<()> { + // First set the primary key + self.set(prefix, id, data.clone())?; + + // Get the TST for this prefix + let tst = self.get_tst(prefix)?; + + // Add additional index keys + for index_key in index_keys { + // Create the index key in the format prefix_indexname_value + let key = format!("{}_{}_{}", prefix, index_key.name, index_key.value); + + // Set the key-value pair in the TST + // For index keys, we store the ID as the value + let id_bytes = id.to_be_bytes().to_vec(); + tst.set(&key, id_bytes) + .map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?; + } + + Ok(()) + } + + /// Removes an object from the TST index (primary key only) + pub fn delete(&mut self, prefix: &str, id: u32) -> DbResult<()> { + // Get the TST for this prefix + let tst = self.get_tst(prefix)?; + + // Create the key in the format prefix_id + let key = format!("{}_{}", prefix, id); + + // Delete the key from the TST + tst.delete(&key) + .map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?; + + Ok(()) + } + + /// Removes an object from the TST index including all index keys + pub fn delete_with_indexes(&mut self, prefix: &str, id: u32, index_keys: &[IndexKey]) -> DbResult<()> { + // First delete the primary key + self.delete(prefix, id)?; + + // Get the TST for this prefix + let tst = self.get_tst(prefix)?; + + // Delete additional index keys + for index_key in index_keys { + // Create the index key in the format prefix_indexname_value + let key = format!("{}_{}_{}", prefix, index_key.name, index_key.value); + + // Delete the key from the TST + tst.delete(&key) + .map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?; + } + + Ok(()) + } + + /// Lists all objects with a given prefix (primary keys only) + pub fn list(&mut self, prefix: &str) -> DbResult)>> { + // Get the TST for this prefix + let tst = self.get_tst(prefix)?; + + // Get all keys with this prefix followed by an underscore + let search_prefix = format!("{}_", prefix); + let keys = tst.list(&search_prefix) + .map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?; + + // Get all values for these keys + let mut result = Vec::with_capacity(keys.len()); + for key in keys { + // Check if this is a primary key (prefix_id) and not an index key (prefix_indexname_value) + let parts: Vec<&str> = key.split('_').collect(); + if parts.len() != 2 { + continue; // Skip index keys + } + + // Extract the ID from the key (format: prefix_id) + let id_str = parts[1]; + let id = id_str.parse::().map_err(|_| { + DbError::GeneralError(format!("Invalid ID in key: {}", key)) + })?; + + // Get the value from the TST + let data = tst.get(&key) + .map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?; + + result.push((id, data)); + } + + Ok(result) + } + + /// Finds objects by a specific index key + pub fn find_by_index(&mut self, prefix: &str, index_name: &str, index_value: &str) -> DbResult> { + // Get the TST for this prefix + let tst = self.get_tst(prefix)?; + + // Create the index key in the format prefix_indexname_value + let key = format!("{}_{}_{}", prefix, index_name, index_value); + + // Try to get the value from the TST + match tst.get(&key) { + Ok(id_bytes) => { + // Convert the bytes to a u32 ID + if id_bytes.len() == 4 { + let mut bytes = [0u8; 4]; + bytes.copy_from_slice(&id_bytes[0..4]); + let id = u32::from_be_bytes(bytes); + Ok(vec![id]) + } else { + Err(DbError::GeneralError(format!("Invalid ID bytes for key: {}", key))) + } + }, + Err(_) => Ok(Vec::new()), // No matches found + } + } + + /// Finds objects by a prefix of an index key + pub fn find_by_index_prefix(&mut self, prefix: &str, index_name: &str, index_value_prefix: &str) -> DbResult> { + // Get the TST for this prefix + let tst = self.get_tst(prefix)?; + + // Create the index key prefix in the format prefix_indexname_valueprefix + let key_prefix = format!("{}_{}_{}", prefix, index_name, index_value_prefix); + + // Get all keys with this prefix + let keys = tst.list(&key_prefix) + .map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?; + + // Extract the IDs from the values + let mut result = Vec::with_capacity(keys.len()); + for key in keys { + // Get the value from the TST + let id_bytes = tst.get(&key) + .map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?; + + // Convert the bytes to a u32 ID + if id_bytes.len() == 4 { + let mut bytes = [0u8; 4]; + bytes.copy_from_slice(&id_bytes[0..4]); + let id = u32::from_be_bytes(bytes); + result.push(id); + } + } + + Ok(result) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::tempdir; + + #[test] + fn test_tst_index_manager() { + // Create a temporary directory for the test + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path(); + + // Create a TST index manager + let mut manager = TSTIndexManager::new(path).unwrap(); + + // Test setting values + let data1 = vec![1, 2, 3]; + let data2 = vec![4, 5, 6]; + manager.set("test", 1, data1.clone()).unwrap(); + manager.set("test", 2, data2.clone()).unwrap(); + + // Test listing values + let items = manager.list("test").unwrap(); + assert_eq!(items.len(), 2); + + // Check that the values are correct + let mut found_data1 = false; + let mut found_data2 = false; + for (id, data) in items { + if id == 1 && data == data1 { + found_data1 = true; + } else if id == 2 && data == data2 { + found_data2 = true; + } + } + assert!(found_data1); + assert!(found_data2); + + // Test deleting a value + manager.delete("test", 1).unwrap(); + + // Test listing again + let items = manager.list("test").unwrap(); + assert_eq!(items.len(), 1); + assert_eq!(items[0].0, 2); + assert_eq!(items[0].1, data2); + } +} \ No newline at end of file diff --git a/heromodels/src/lib.rs b/heromodels/src/lib.rs index ada23e2..eecd6d0 100644 --- a/heromodels/src/lib.rs +++ b/heromodels/src/lib.rs @@ -1,12 +1,2 @@ -// Export core module -pub mod core; - -// Export userexample module -pub mod userexample; - -// Re-export key types for convenience -pub use core::{BaseModel, BaseModelData, IndexKey, ModelBuilder}; -pub use core::Comment; -pub use userexample::User; - -// No need to re-export macros as they are already exported at the crate root \ No newline at end of file +// Export the models module +pub mod models; \ No newline at end of file diff --git a/heromodels/src/core/comment.rs b/heromodels/src/models/core/comment.rs similarity index 82% rename from heromodels/src/core/comment.rs rename to heromodels/src/models/core/comment.rs index 0ae61a3..ca362aa 100644 --- a/heromodels/src/core/comment.rs +++ b/heromodels/src/models/core/comment.rs @@ -1,6 +1,5 @@ use serde::{Deserialize, Serialize}; -use crate::core::model::{BaseModel, BaseModelData, IndexKey, ModelBuilder}; -use crate::impl_model_builder; +use crate::models::core::model::{Model, BaseModelData, IndexKey}; /// Represents a comment on a model #[derive(Debug, Clone, Serialize, Deserialize)] @@ -33,7 +32,8 @@ impl Comment { } } -impl BaseModel for Comment { +// Implement the Model trait for Comment +impl Model for Comment { fn db_prefix() -> &'static str { "comment" } @@ -42,6 +42,10 @@ impl BaseModel for Comment { self.base_data.id } + fn base_data_mut(&mut self) -> &mut BaseModelData { + &mut self.base_data + } + fn db_keys(&self) -> Vec { vec![ IndexKey { @@ -51,6 +55,3 @@ impl BaseModel for Comment { ] } } - -// Implement ModelBuilder for Comment -impl_model_builder!(Comment); \ No newline at end of file diff --git a/heromodels/src/models/core/mod.rs b/heromodels/src/models/core/mod.rs new file mode 100644 index 0000000..6160f5d --- /dev/null +++ b/heromodels/src/models/core/mod.rs @@ -0,0 +1,7 @@ +// Export submodules +pub mod model; +pub mod comment; + +// Re-export key types for convenience +pub use model::{Model, BaseModelData, IndexKey, IndexKeyBuilder}; +pub use comment::Comment; \ No newline at end of file diff --git a/heromodels/src/core/model.rs b/heromodels/src/models/core/model.rs similarity index 87% rename from heromodels/src/core/model.rs rename to heromodels/src/models/core/model.rs index 4c59588..e289dc6 100644 --- a/heromodels/src/core/model.rs +++ b/heromodels/src/models/core/model.rs @@ -41,8 +41,8 @@ impl IndexKeyBuilder { } } -/// Base trait for all models -pub trait BaseModel: Debug + Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static { +/// Unified trait for all models +pub trait Model: Debug + Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static { /// Get the database prefix for this model type fn db_prefix() -> &'static str where Self: Sized; @@ -56,6 +56,21 @@ pub trait BaseModel: Debug + Clone + Serialize + for<'de> Deserialize<'de> + Sen /// Get the unique ID for this model fn get_id(&self) -> u32; + + /// Get a mutable reference to the base_data field + fn base_data_mut(&mut self) -> &mut BaseModelData; + + /// Set the ID for this model + fn id(mut self, id: u32) -> Self where Self: Sized { + self.base_data_mut().id = id; + self + } + + /// Build the model, updating the modified timestamp + fn build(mut self) -> Self where Self: Sized { + self.base_data_mut().update_modified(); + self + } } /// Base struct that all models should include @@ -164,29 +179,12 @@ impl BaseModelDataBuilder { } } -/// Trait for model builders that have a base_data field -pub trait ModelBuilder: Sized { - /// Get a mutable reference to the base_data field - fn base_data_mut(&mut self) -> &mut BaseModelData; - - /// Set the ID for this model - fn id(mut self, id: u32) -> Self { - self.base_data_mut().id = id; - self - } - - /// Build the model, updating the modified timestamp - fn build(mut self) -> Self { - self.base_data_mut().update_modified(); - self - } -} - -/// Macro to implement BaseModel for a struct that contains a base_data field of type BaseModelData +/// Macro to implement Model for a struct that contains a base_data field of type BaseModelData #[macro_export] -macro_rules! impl_base_model { +macro_rules! impl_model { + // Basic implementation with default db_keys ($type:ty, $prefix:expr) => { - impl $crate::core::model::BaseModel for $type { + impl $crate::core::model::Model for $type { fn db_prefix() -> &'static str { $prefix } @@ -194,15 +192,7 @@ macro_rules! impl_base_model { fn get_id(&self) -> u32 { self.base_data.id } - } - }; -} - -/// Macro to implement ModelBuilder for a struct that contains a base_data field of type BaseModelData -#[macro_export] -macro_rules! impl_model_builder { - ($type:ty) => { - impl $crate::core::model::ModelBuilder for $type { + fn base_data_mut(&mut self) -> &mut $crate::core::model::BaseModelData { &mut self.base_data } diff --git a/heromodels/src/models/lib.rs b/heromodels/src/models/lib.rs new file mode 100644 index 0000000..ef4d90c --- /dev/null +++ b/heromodels/src/models/lib.rs @@ -0,0 +1,11 @@ +// Export core module +pub mod core; + +// Export userexample module +pub mod userexample; + +// Re-export key types for convenience +pub use core::*; +pub use core::model::{Model, BaseModelData, IndexKey}; +pub use core::Comment; +pub use userexample::User; \ No newline at end of file diff --git a/heromodels/src/models/mod.rs b/heromodels/src/models/mod.rs new file mode 100644 index 0000000..478295a --- /dev/null +++ b/heromodels/src/models/mod.rs @@ -0,0 +1,8 @@ +// Export submodules +pub mod core; +pub mod userexample; + +// Re-export key types for convenience +pub use core::model::{Model, BaseModelData, IndexKey}; +pub use core::Comment; +pub use userexample::User; \ No newline at end of file diff --git a/heromodels/src/userexample/mod.rs b/heromodels/src/models/userexample/mod.rs similarity index 100% rename from heromodels/src/userexample/mod.rs rename to heromodels/src/models/userexample/mod.rs diff --git a/heromodels/src/userexample/user.rs b/heromodels/src/models/userexample/user.rs similarity index 90% rename from heromodels/src/userexample/user.rs rename to heromodels/src/models/userexample/user.rs index 0f071c5..140ca0d 100644 --- a/heromodels/src/userexample/user.rs +++ b/heromodels/src/models/userexample/user.rs @@ -1,6 +1,5 @@ use serde::{Deserialize, Serialize}; -use crate::core::model::{BaseModel, BaseModelData, IndexKey, ModelBuilder}; -use crate::impl_model_builder; +use crate::models::core::model::{Model, BaseModelData, IndexKey}; /// Represents a user in the system #[derive(Debug, Clone, Serialize, Deserialize)] @@ -78,8 +77,8 @@ impl User { } } -// Implement BaseModel for User -impl BaseModel for User { +// Implement the Model trait for User +impl Model for User { fn db_prefix() -> &'static str { "user" } @@ -88,6 +87,11 @@ impl BaseModel for User { self.base_data.id } + //WHY? + fn base_data_mut(&mut self) -> &mut BaseModelData { + &mut self.base_data + } + fn db_keys(&self) -> Vec { vec![ IndexKey { @@ -105,6 +109,3 @@ impl BaseModel for User { ] } } - -// Implement ModelBuilder for User -impl_model_builder!(User); \ No newline at end of file diff --git a/heromodels/src/userexample/lib.rs b/heromodels/src/userexample/lib.rs deleted file mode 100644 index e7538ce..0000000 --- a/heromodels/src/userexample/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ - -// Export user module -pub mod user; - -// Re-export User for convenience -pub use user::User; \ No newline at end of file diff --git a/model_trait_unification_plan.md b/model_trait_unification_plan.md new file mode 100644 index 0000000..402eb55 --- /dev/null +++ b/model_trait_unification_plan.md @@ -0,0 +1,277 @@ +# Model Trait Unification Plan + +## Introduction + +This document outlines the plan to unify the `BaseModel` and `ModelBuilder` traits in the heromodels crate into a single `Model` trait. The goal is to simplify the codebase, reduce boilerplate, and make the API more intuitive while maintaining all existing functionality. + +## Current Structure + +Currently, the codebase has two separate traits: + +1. **BaseModel** - Provides database-related functionality: + - `db_prefix()` - Returns a string prefix for database operations + - `db_keys()` - Returns index keys for the model + - `get_id()` - Returns the model's unique ID + +2. **ModelBuilder** - Provides builder pattern functionality: + - `base_data_mut()` - Gets a mutable reference to the base data + - `id()` - Sets the ID for the model + - `build()` - Finalizes the model by updating timestamps + +Each model (like User and Comment) implements both traits separately, and there are two separate macros for implementing these traits: + +```rust +// For BaseModel +impl_base_model!(User, "user"); + +// For ModelBuilder +impl_model_builder!(User); +``` + +This leads to duplication and cognitive overhead when working with models. + +## Proposed Structure + +We will create a unified `Model` trait that combines all the functionality from both existing traits: + +```mermaid +classDiagram + class Model { + <> + +db_prefix() static str + +db_keys() Vec~IndexKey~ + +get_id() u32 + +base_data_mut() &mut BaseModelData + +id(u32) Self + +build() Self + } + + class User { + +base_data BaseModelData + +username String + +email String + +full_name String + +is_active bool + } + + class Comment { + +base_data BaseModelData + +user_id u32 + +content String + } + + Model <|-- User + Model <|-- Comment +``` + +## Implementation Steps + +### 1. Update model.rs + +Create the new `Model` trait that combines all methods from both existing traits: + +```rust +/// Unified trait for all models +pub trait Model: Debug + Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static { + /// Get the database prefix for this model type + fn db_prefix() -> &'static str where Self: Sized; + + /// Returns a list of index keys for this model instance + /// These keys will be used to create additional indexes in the TST + fn db_keys(&self) -> Vec { + Vec::new() + } + + /// Get the unique ID for this model + fn get_id(&self) -> u32; + + /// Get a mutable reference to the base_data field + fn base_data_mut(&mut self) -> &mut BaseModelData; + + /// Set the ID for this model + fn id(mut self, id: u32) -> Self where Self: Sized { + self.base_data_mut().id = id; + self + } + + /// Build the model, updating the modified timestamp + fn build(mut self) -> Self where Self: Sized { + self.base_data_mut().update_modified(); + self + } +} +``` + +Create a new implementation macro that implements all required methods: + +```rust +/// Macro to implement Model for a struct that contains a base_data field of type BaseModelData +#[macro_export] +macro_rules! impl_model { + ($type:ty, $prefix:expr) => { + impl $crate::core::model::Model for $type { + fn db_prefix() -> &'static str { + $prefix + } + + fn get_id(&self) -> u32 { + self.base_data.id + } + + fn base_data_mut(&mut self) -> &mut $crate::core::model::BaseModelData { + &mut self.base_data + } + + // Other methods have default implementations + } + }; +} +``` + +Mark the old traits and macros as deprecated (or remove them entirely): + +```rust +#[deprecated( + since = "0.2.0", + note = "Use the unified Model trait instead" +)] +pub trait BaseModel { /* ... */ } + +#[deprecated( + since = "0.2.0", + note = "Use the unified Model trait instead" +)] +pub trait ModelBuilder { /* ... */ } + +#[deprecated( + since = "0.2.0", + note = "Use impl_model instead" +)] +#[macro_export] +macro_rules! impl_base_model { /* ... */ } + +#[deprecated( + since = "0.2.0", + note = "Use impl_model instead" +)] +#[macro_export] +macro_rules! impl_model_builder { /* ... */ } +``` + +### 2. Update User and Comment Implementations + +For User: + +```rust +// Implement Model for User +impl_model!(User, "user"); + +// Custom implementation of db_keys +impl Model for User { + fn db_keys(&self) -> Vec { + vec![ + IndexKey { + name: "username", + value: self.username.clone(), + }, + IndexKey { + name: "email", + value: self.email.clone(), + }, + IndexKey { + name: "is_active", + value: self.is_active.to_string(), + }, + ] + } +} +``` + +For Comment: + +```rust +// Implement Model for Comment +impl_model!(Comment, "comment"); + +// Custom implementation of db_keys +impl Model for Comment { + fn db_keys(&self) -> Vec { + vec![ + IndexKey { + name: "user_id", + value: self.user_id.to_string(), + }, + ] + } +} +``` + +### 3. Update Imports and References + +Update the lib.rs file to export the new trait and macro: + +```rust +// Export core module +pub mod core; + +// Export userexample module +pub mod userexample; + +// Re-export key types for convenience +pub use core::model::{Model, BaseModelData, IndexKey}; +pub use core::Comment; +pub use userexample::User; + +// Re-export macros +pub use crate::impl_model; +``` + +Update the example code to use the new trait: + +```rust +use heromodels::{Model, Comment, User}; + +fn main() { + println!("Hero Models - Basic Usage Example"); + println!("================================"); + + // Create a new user using the fluent interface + let user = User::new(1) + .username("johndoe") + .email("john.doe@example.com") + .full_name("John Doe") + .build(); + + println!("Created user: {:?}", user); + println!("User ID: {}", user.get_id()); + println!("User DB Prefix: {}", User::db_prefix()); + + // Create a comment for the user + let comment = Comment::new(1) + .user_id(2) // commenter's user ID + .content("This is a comment on the user") + .build(); + + println!("\nCreated comment: {:?}", comment); + println!("Comment ID: {}", comment.get_id()); + println!("Comment DB Prefix: {}", Comment::db_prefix()); +} +``` + +### 4. Testing + +Run the example to ensure it works as expected: + +```bash +cargo run --example basic_user_example +``` + +Verify that all functionality is preserved and that the output matches the expected output. + +## Benefits of This Approach + +1. **Simplified Code Structure**: One trait instead of two means less cognitive overhead +2. **Reduced Boilerplate**: A single macro implementation reduces repetitive code +3. **Improved Usability**: No need to import multiple traits or worry about which trait provides which method +4. **Maintained Functionality**: All existing functionality is preserved +5. **Better Encapsulation**: The model concept is more cohesive as a single trait \ No newline at end of file