...
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use heromodels::{BaseModel, Comment, User, ModelBuilder};
|
||||
use heromodels::models::{Model, Comment, User};
|
||||
|
||||
fn main() {
|
||||
println!("Hero Models - Basic Usage Example");
|
||||
|
@@ -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;
|
@@ -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;
|
646
heromodels/src/herodb/db.rs
Normal file
646
heromodels/src/herodb/db.rs
Normal file
@@ -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<u8>,
|
||||
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<DbOperation>,
|
||||
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<TypeId, Arc<RwLock<dyn DbOperations>>>,
|
||||
|
||||
// TST index manager
|
||||
tst_index: Arc<RwLock<TSTIndexManager>>,
|
||||
|
||||
// Transaction state
|
||||
transaction: Arc<RwLock<Option<TransactionState>>>,
|
||||
}
|
||||
|
||||
/// Builder for DB that allows registering models
|
||||
#[derive(Clone, CustomType)]
|
||||
pub struct DBBuilder {
|
||||
base_path: PathBuf,
|
||||
model_registrations: Vec<Arc<dyn ModelRegistration>>,
|
||||
}
|
||||
|
||||
/// Trait for model registration
|
||||
pub trait ModelRegistration: Send + Sync {
|
||||
fn register(&self, path: &Path) -> DbResult<(TypeId, Arc<RwLock<dyn DbOperations>>)>;
|
||||
}
|
||||
|
||||
/// Implementation of ModelRegistration for any Model type
|
||||
pub struct ModelRegistrar<T: Model> {
|
||||
phantom: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Model> ModelRegistrar<T> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of ModelRegistration for any serializable type that implements GetId
|
||||
pub struct TypeRegistrar<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static> {
|
||||
prefix: &'static str,
|
||||
phantom: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static> TypeRegistrar<T> {
|
||||
pub fn new(prefix: &'static str) -> Self {
|
||||
Self {
|
||||
prefix,
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Model> ModelRegistration for ModelRegistrar<T> {
|
||||
fn register(&self, path: &Path) -> DbResult<(TypeId, Arc<RwLock<dyn DbOperations>>)> {
|
||||
let store = OurDbStore::<T>::open(path.join(T::db_prefix()))?;
|
||||
Ok((TypeId::of::<T>(), Arc::new(RwLock::new(store)) as Arc<RwLock<dyn DbOperations>>))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static> ModelRegistration for TypeRegistrar<T> {
|
||||
fn register(&self, path: &Path) -> DbResult<(TypeId, Arc<RwLock<dyn DbOperations>>)> {
|
||||
let store = GenericStore::<T>::open(path, self.prefix)?;
|
||||
Ok((TypeId::of::<T>(), Arc::new(RwLock::new(store)) as Arc<RwLock<dyn DbOperations>>))
|
||||
}
|
||||
}
|
||||
|
||||
impl DBBuilder {
|
||||
/// Create a new DB builder
|
||||
pub fn new<P: Into<PathBuf>>(base_path: P) -> Self {
|
||||
Self {
|
||||
base_path: base_path.into(),
|
||||
model_registrations: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_path<P: Into<PathBuf>>(base_path: P) -> Self {
|
||||
Self {
|
||||
base_path: base_path.into(),
|
||||
model_registrations: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a model type with the DB
|
||||
pub fn register_model<T: Model>(mut self) -> Self {
|
||||
self.model_registrations
|
||||
.push(Arc::new(ModelRegistrar::<T>::new()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Register any serializable type with the DB
|
||||
pub fn register_type<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static>(
|
||||
mut self,
|
||||
prefix: &'static str
|
||||
) -> Self {
|
||||
self.model_registrations
|
||||
.push(Arc::new(TypeRegistrar::<T>::new(prefix)));
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the DB with the registered models
|
||||
pub fn build(self) -> Result<DB, Box<EvalAltResult>> {
|
||||
let base_path = self.base_path;
|
||||
|
||||
// Ensure base directory exists
|
||||
if !base_path.exists() {
|
||||
std::fs::create_dir_all(&base_path).map_err(|e| {
|
||||
EvalAltResult::ErrorSystem("Could not create base dir".to_string(), Box::new(e))
|
||||
})?;
|
||||
}
|
||||
|
||||
// Register all models
|
||||
let mut type_map: HashMap<TypeId, Arc<RwLock<dyn DbOperations>>> = 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<P: Into<PathBuf>>(base_path: P) -> DbResult<Self> {
|
||||
let base_path = base_path.into();
|
||||
|
||||
// Ensure base directory exists
|
||||
if !base_path.exists() {
|
||||
std::fs::create_dir_all(&base_path)?;
|
||||
}
|
||||
|
||||
// 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<T: Model>(&self, model: &T) -> DbResult<()> {
|
||||
// Try to acquire a write lock on the transaction
|
||||
let mut tx_guard = self.transaction.write().unwrap();
|
||||
|
||||
// Check if there's an active transaction
|
||||
if let Some(tx_state) = tx_guard.as_mut() {
|
||||
if tx_state.active {
|
||||
// Serialize the model for later use
|
||||
let serialized = 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::<T>(),
|
||||
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::<T>()) {
|
||||
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<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static>(
|
||||
&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::<T>(),
|
||||
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::<T>()) {
|
||||
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<T: Model>(&self, id: u32) -> Option<Result<Option<T>, DbError>> {
|
||||
// Try to acquire a read lock on the transaction
|
||||
let tx_guard = self.transaction.read().unwrap();
|
||||
|
||||
if let Some(tx_state) = tx_guard.as_ref() {
|
||||
if !tx_state.active {
|
||||
return None;
|
||||
}
|
||||
|
||||
let type_id = TypeId::of::<T>();
|
||||
|
||||
// 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<T: Model>(&self, id: u32) -> DbResult<T> {
|
||||
// First check if there's a pending value in the current transaction
|
||||
if let Some(tx_result) = self.check_transaction::<T>(id) {
|
||||
match tx_result {
|
||||
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::<T>()) {
|
||||
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::<T>() {
|
||||
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<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static>(
|
||||
&self,
|
||||
id: u32
|
||||
) -> DbResult<T> {
|
||||
// If not found in transaction, get from database
|
||||
match self.type_map.get(&TypeId::of::<T>()) {
|
||||
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::<T>() {
|
||||
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<T: Model>(&self, id: u32) -> DbResult<()> {
|
||||
// Try to acquire a write lock on the transaction
|
||||
let mut tx_guard = self.transaction.write().unwrap();
|
||||
|
||||
// Check if there's an active transaction
|
||||
if let Some(tx_state) = tx_guard.as_mut() {
|
||||
if tx_state.active {
|
||||
// Record a Delete operation in the transaction
|
||||
tx_state.operations.push(DbOperation::Delete {
|
||||
model_type: TypeId::of::<T>(),
|
||||
id,
|
||||
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::<T>()) {
|
||||
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<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static>(
|
||||
&self,
|
||||
id: u32,
|
||||
prefix: &str
|
||||
) -> DbResult<()> {
|
||||
// Execute directly
|
||||
match self.type_map.get(&TypeId::of::<T>()) {
|
||||
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<T: Model>(&self) -> DbResult<Vec<T>> {
|
||||
match self.type_map.get(&TypeId::of::<T>()) {
|
||||
Some(db_ops) => {
|
||||
let db_ops_guard = db_ops.read().unwrap();
|
||||
let any_result = db_ops_guard.list()?;
|
||||
|
||||
// Try to downcast to Vec<T>
|
||||
match any_result.downcast::<Vec<T>>() {
|
||||
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<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static>(
|
||||
&self
|
||||
) -> DbResult<Vec<T>> {
|
||||
match self.type_map.get(&TypeId::of::<T>()) {
|
||||
Some(db_ops) => {
|
||||
let db_ops_guard = db_ops.read().unwrap();
|
||||
let any_result = db_ops_guard.list()?;
|
||||
|
||||
// Try to downcast to Vec<T>
|
||||
match any_result.downcast::<Vec<T>>() {
|
||||
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<T: Model>(&self, id: u32, depth: u8) -> DbResult<Vec<T>> {
|
||||
match self.type_map.get(&TypeId::of::<T>()) {
|
||||
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::<T>() {
|
||||
Ok(boxed_t) => results.push(*boxed_t),
|
||||
Err(_) => return Err(DbError::TypeError),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
None => Err(DbError::TypeError),
|
||||
}
|
||||
}
|
||||
}
|
30
heromodels/src/herodb/error.rs
Normal file
30
heromodels/src/herodb/error.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use thiserror::Error;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Errors that can occur during database operations
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DbError {
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error("Serialization/Deserialization error: {0}")]
|
||||
SerializationError(#[from] bincode::Error),
|
||||
|
||||
#[error("Record not found for ID: {0}")]
|
||||
NotFound(u32),
|
||||
|
||||
#[error("Type mismatch during deserialization")]
|
||||
TypeError,
|
||||
|
||||
#[error("Transaction error: {0}")]
|
||||
TransactionError(String),
|
||||
|
||||
#[error("OurDB error: {0}")]
|
||||
OurDbError(#[from] ourdb::Error),
|
||||
|
||||
#[error("General database error: {0}")]
|
||||
GeneralError(String),
|
||||
}
|
||||
|
||||
/// Result type for DB operations
|
||||
pub type DbResult<T> = Result<T, DbError>;
|
140
heromodels/src/herodb/generic_store.rs
Normal file
140
heromodels/src/herodb/generic_store.rs
Normal file
@@ -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<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static> {
|
||||
db: OurDB,
|
||||
path: PathBuf,
|
||||
prefix: String,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static> GenericStore<T> {
|
||||
/// Opens or creates an OurDB database at the specified path
|
||||
pub fn open<P: AsRef<Path>>(path: P, prefix: &str) -> DbResult<Self> {
|
||||
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<Vec<u8>> {
|
||||
bincode::serialize(item).map_err(DbError::SerializationError)
|
||||
}
|
||||
|
||||
/// Deserializes bytes to an item
|
||||
fn deserialize(data: &[u8]) -> DbResult<T> {
|
||||
bincode::deserialize(data).map_err(DbError::SerializationError)
|
||||
}
|
||||
|
||||
/// Gets the raw bytes for an item by ID
|
||||
pub fn get_raw(&self, id: u32) -> DbResult<Vec<u8>> {
|
||||
self.db.get(id).map_err(DbError::OurDbError)
|
||||
}
|
||||
|
||||
/// Lists all raw items as bytes
|
||||
pub fn list_raw(&self) -> DbResult<Vec<Vec<u8>>> {
|
||||
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<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static> DbOperations for GenericStore<T> {
|
||||
fn delete(&mut self, id: u32) -> DbResult<()> {
|
||||
self.db.delete(id).map_err(DbError::OurDbError)
|
||||
}
|
||||
|
||||
fn get(&mut self, id: u32) -> DbResult<Box<dyn Any>> {
|
||||
let data = self.db.get(id).map_err(DbError::OurDbError)?;
|
||||
let item = Self::deserialize(&data)?;
|
||||
Ok(Box::new(item))
|
||||
}
|
||||
|
||||
fn list(&self) -> DbResult<Box<dyn Any>> {
|
||||
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::<T>() {
|
||||
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<Vec<Box<dyn Any>>> {
|
||||
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)
|
||||
}
|
||||
}
|
38
heromodels/src/herodb/macros.rs
Normal file
38
heromodels/src/herodb/macros.rs
Normal file
@@ -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 [<insert_ $singular>](&mut self, item: $model) -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
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 [<get_ $singular>](&mut self, id: u32) -> DbResult<$model> {
|
||||
self.get::<$model>(id)
|
||||
}
|
||||
|
||||
/// Delete a model instance by its ID
|
||||
pub fn [<delete_ $singular>](&mut self, id: u32) -> DbResult<()> {
|
||||
self.delete::<$model>(id)
|
||||
}
|
||||
|
||||
/// List all model instances
|
||||
pub fn [<list_ $plural>](&mut self) -> DbResult<Vec<$model>> {
|
||||
self.list::<$model>()
|
||||
}
|
||||
|
||||
/// Get history of a model instance
|
||||
pub fn [<get_ $singular _history>](&mut self, id: u32, depth: u8) -> DbResult<Vec<$model>> {
|
||||
self.get_history::<$model>(id, depth)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
33
heromodels/src/herodb/mod.rs
Normal file
33
heromodels/src/herodb/mod.rs
Normal file
@@ -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;
|
96
heromodels/src/herodb/model.rs
Normal file
96
heromodels/src/herodb/model.rs
Normal file
@@ -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<Vec<u8>> {
|
||||
bincode::serialize(self).map_err(DbError::SerializationError)
|
||||
}
|
||||
|
||||
/// Deserializes data from bytes into an instance
|
||||
fn from_bytes(data: &[u8]) -> DbResult<Self> {
|
||||
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<IndexKey> {
|
||||
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<IndexKey> {
|
||||
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<T: Serialize>(value: &T) -> DbResult<Vec<u8>> {
|
||||
bincode::serialize(value).map_err(DbError::SerializationError)
|
||||
}
|
||||
|
||||
/// Deserialize bytes to any deserializable type
|
||||
pub fn from_bytes<T: DeserializeOwned>(data: &[u8]) -> DbResult<T> {
|
||||
bincode::deserialize(data).map_err(DbError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
// Note: We don't provide a blanket implementation of Storable
|
||||
// Each model type must implement Storable explicitly
|
78
heromodels/src/herodb/model_methods.rs
Normal file
78
heromodels/src/herodb/model_methods.rs
Normal file
@@ -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);
|
156
heromodels/src/herodb/store.rs
Normal file
156
heromodels/src/herodb/store.rs
Normal file
@@ -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<Box<dyn Any>>;
|
||||
fn list(&self) -> DbResult<Box<dyn Any>>;
|
||||
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<Vec<Box<dyn Any>>>;
|
||||
}
|
||||
|
||||
/// A store implementation using OurDB as the backend
|
||||
pub struct OurDbStore<T: Model> {
|
||||
db: OurDB,
|
||||
path: PathBuf,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Model> OurDbStore<T> {
|
||||
/// Opens or creates an OurDB database at the specified path
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> DbResult<Self> {
|
||||
let path_buf = path.as_ref().to_path_buf();
|
||||
let db_path = path_buf.join(T::db_prefix());
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
std::fs::create_dir_all(&db_path).map_err(DbError::IoError)?;
|
||||
|
||||
let config = OurDBConfig {
|
||||
path: db_path.clone(),
|
||||
incremental_mode: true, // Always use incremental mode for auto IDs
|
||||
file_size: None, // Use default (500MB)
|
||||
keysize: None, // Use default (4 bytes)
|
||||
reset: None, // Don't reset existing database
|
||||
};
|
||||
|
||||
let db = OurDB::new(config).map_err(DbError::OurDbError)?;
|
||||
|
||||
Ok(Self {
|
||||
db,
|
||||
path: db_path,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Inserts or updates a model instance in the database
|
||||
pub fn insert(&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<T> {
|
||||
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<Vec<T>> {
|
||||
// OurDB doesn't have a built-in list function, so we need to implement it
|
||||
// This is a placeholder - in a real implementation, we would need to
|
||||
// maintain a list of all IDs for each model type
|
||||
Err(DbError::GeneralError("List operation not implemented yet".to_string()))
|
||||
}
|
||||
|
||||
/// Gets the history of a model by its ID
|
||||
pub fn get_history(&mut self, id: u32, depth: u8) -> DbResult<Vec<T>> {
|
||||
let history_data = self.db.get_history(id, depth).map_err(|e| {
|
||||
match e {
|
||||
ourdb::Error::NotFound(_) => DbError::NotFound(id),
|
||||
_ => DbError::OurDbError(e),
|
||||
}
|
||||
})?;
|
||||
|
||||
let mut result = Vec::with_capacity(history_data.len());
|
||||
for data in history_data {
|
||||
result.push(T::from_bytes(&data)?);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Model> DbOperations for OurDbStore<T> {
|
||||
fn delete(&mut self, id: u32) -> DbResult<()> {
|
||||
self.delete(id)
|
||||
}
|
||||
|
||||
fn get(&mut self, id: u32) -> DbResult<Box<dyn Any>> {
|
||||
let result = self.get(id)?;
|
||||
Ok(Box::new(result))
|
||||
}
|
||||
|
||||
fn list(&self) -> DbResult<Box<dyn Any>> {
|
||||
// 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::<T>() {
|
||||
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<Vec<Box<dyn Any>>> {
|
||||
let history = self.get_history(id, depth)?;
|
||||
let mut result = Vec::with_capacity(history.len());
|
||||
|
||||
for item in history {
|
||||
result.push(Box::new(item) as Box<dyn Any>);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
98
heromodels/src/herodb/tests.rs
Normal file
98
heromodels/src/herodb/tests.rs
Normal file
@@ -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::<TestModel>().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::<TestModel>().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::<TestModel>(2).unwrap();
|
||||
|
||||
// List again
|
||||
let models = db.list::<TestModel>().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::<TestModel>(1).unwrap(); // Delete model1
|
||||
db.commit_transaction().unwrap();
|
||||
|
||||
// List again after transaction
|
||||
let models = db.list::<TestModel>().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::<TestModel>(3).unwrap(); // Delete model3
|
||||
db.rollback_transaction().unwrap();
|
||||
|
||||
// List again after rollback
|
||||
let models = db.list::<TestModel>().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::<TestModel>().unwrap();
|
||||
|
||||
// Verify that our models are still accessible
|
||||
let models = db.list::<TestModel>().unwrap();
|
||||
assert_eq!(models.len(), 2);
|
||||
assert!(models.contains(&model2));
|
||||
assert!(models.contains(&model3));
|
||||
}
|
261
heromodels/src/herodb/tst_index.rs
Normal file
261
heromodels/src/herodb/tst_index.rs
Normal file
@@ -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<String, TST>,
|
||||
}
|
||||
|
||||
impl TSTIndexManager {
|
||||
/// Creates a new TST index manager
|
||||
pub fn new<P: AsRef<Path>>(base_path: P) -> DbResult<Self> {
|
||||
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<u8>) -> 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<u8>, 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<Vec<(u32, Vec<u8>)>> {
|
||||
// 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::<u32>().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<Vec<u32>> {
|
||||
// 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<Vec<u32>> {
|
||||
// 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);
|
||||
}
|
||||
}
|
@@ -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
|
||||
// Export the models module
|
||||
pub mod models;
|
@@ -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<IndexKey> {
|
||||
vec![
|
||||
IndexKey {
|
||||
@@ -51,6 +55,3 @@ impl BaseModel for Comment {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Implement ModelBuilder for Comment
|
||||
impl_model_builder!(Comment);
|
7
heromodels/src/models/core/mod.rs
Normal file
7
heromodels/src/models/core/mod.rs
Normal file
@@ -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;
|
@@ -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
|
||||
}
|
11
heromodels/src/models/lib.rs
Normal file
11
heromodels/src/models/lib.rs
Normal file
@@ -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;
|
8
heromodels/src/models/mod.rs
Normal file
8
heromodels/src/models/mod.rs
Normal file
@@ -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;
|
@@ -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<IndexKey> {
|
||||
vec![
|
||||
IndexKey {
|
||||
@@ -105,6 +109,3 @@ impl BaseModel for User {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Implement ModelBuilder for User
|
||||
impl_model_builder!(User);
|
@@ -1,6 +0,0 @@
|
||||
|
||||
// Export user module
|
||||
pub mod user;
|
||||
|
||||
// Re-export User for convenience
|
||||
pub use user::User;
|
Reference in New Issue
Block a user