...
This commit is contained in:
parent
cad285fd59
commit
1708e6dd06
0
herodb/Cargo.lock → herodb_old/Cargo.lock
generated
0
herodb/Cargo.lock → herodb_old/Cargo.lock
generated
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user