...
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() {
|
fn main() {
|
||||||
println!("Hero Models - Basic Usage Example");
|
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