...
This commit is contained in:
@@ -1,144 +0,0 @@
|
||||
use bincode;
|
||||
use brotli::{CompressorReader, Decompressor};
|
||||
use rhai::CustomType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sled;
|
||||
use std::fmt::Debug;
|
||||
use std::io::Read;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::Path;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can occur during Sled database operations
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SledDBError {
|
||||
#[error("Sled database error: {0}")]
|
||||
SledError(#[from] sled::Error),
|
||||
#[error("Serialization/Deserialization error: {0}")]
|
||||
SerdeError(#[from] bincode::Error),
|
||||
#[error("Compression/Decompression error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("Record not found for ID: {0}")]
|
||||
NotFound(String),
|
||||
#[error("Type mismatch during deserialization")]
|
||||
TypeError,
|
||||
#[error("General database error: {0}")]
|
||||
GeneralError(String),
|
||||
}
|
||||
|
||||
/// Result type for Sled DB operations
|
||||
pub type SledDBResult<T> = Result<T, SledDBError>;
|
||||
|
||||
/// Trait for models that can be stored in the Sled database.
|
||||
/// Requires `Serialize` and `Deserialize` for the underlying storage mechanism.
|
||||
pub trait Storable: Serialize + for<'de> Deserialize<'de> + Sized {
|
||||
/// Serializes and compresses the instance using bincode and brotli.
|
||||
fn dump(&self) -> SledDBResult<Vec<u8>> {
|
||||
let encoded: Vec<u8> = bincode::serialize(self)?;
|
||||
|
||||
let mut compressed = Vec::new();
|
||||
// Default Brotli parameters: quality 5, lgwin 22 (window size)
|
||||
const BROTLI_QUALITY: u32 = 5;
|
||||
const BROTLI_LGWIN: u32 = 22;
|
||||
const BUFFER_SIZE: usize = 4096; // 4KB buffer
|
||||
|
||||
let mut compressor =
|
||||
CompressorReader::new(&encoded[..], BUFFER_SIZE, BROTLI_QUALITY, BROTLI_LGWIN);
|
||||
compressor.read_to_end(&mut compressed)?;
|
||||
|
||||
Ok(compressed)
|
||||
}
|
||||
|
||||
/// Deserializes and decompresses data from bytes into an instance.
|
||||
fn load_from_bytes(data: &[u8]) -> SledDBResult<Self> {
|
||||
let mut decompressed = Vec::new();
|
||||
const BUFFER_SIZE: usize = 4096; // 4KB buffer
|
||||
|
||||
let mut decompressor = Decompressor::new(data, BUFFER_SIZE);
|
||||
decompressor.read_to_end(&mut decompressed)?;
|
||||
|
||||
let decoded: Self = bincode::deserialize(&decompressed)?;
|
||||
Ok(decoded)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait identifying a model suitable for the Sled database.
|
||||
/// The 'static lifetime bound is required for type identification via Any
|
||||
pub trait SledModel: Storable + Debug + Clone + Send + Sync + 'static {
|
||||
/// Returns the unique ID for this model instance, used as the key in Sled.
|
||||
fn get_id(&self) -> String;
|
||||
|
||||
/// Returns a prefix used for this model type in the Sled database.
|
||||
/// Helps to logically separate different model types.
|
||||
fn db_prefix() -> &'static str;
|
||||
}
|
||||
|
||||
/// A generic database layer on top of Sled.
|
||||
#[derive(Clone)]
|
||||
pub struct SledDB<T: SledModel> {
|
||||
db: sled::Db,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: SledModel> SledDB<T> {
|
||||
/// Opens or creates a Sled database at the specified path.
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> SledDBResult<Self> {
|
||||
let db = sled::open(path)?;
|
||||
Ok(Self {
|
||||
db,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates the full Sled key using the model's prefix and ID.
|
||||
fn get_full_key(id: &str) -> Vec<u8> {
|
||||
format!("{}:{}", T::db_prefix(), id).into_bytes()
|
||||
}
|
||||
|
||||
/// Inserts or updates a model instance in the database.
|
||||
pub fn insert(&self, model: &T) -> SledDBResult<()> {
|
||||
let key = Self::get_full_key(&model.get_id());
|
||||
let value = model.dump()?;
|
||||
self.db.insert(key, value)?;
|
||||
// Optionally force a disk flush for durability, but it impacts performance.
|
||||
// self.db.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieves a model instance by its ID.
|
||||
pub fn get(&self, id: &str) -> SledDBResult<T> {
|
||||
let key = Self::get_full_key(id);
|
||||
match self.db.get(&key)? {
|
||||
Some(ivec) => T::load_from_bytes(&ivec),
|
||||
None => Err(SledDBError::NotFound(id.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes a model instance by its ID.
|
||||
pub fn delete(&self, id: &str) -> SledDBResult<()> {
|
||||
let key = Self::get_full_key(id);
|
||||
match self.db.remove(&key)? {
|
||||
Some(_) => Ok(()),
|
||||
None => Err(SledDBError::NotFound(id.to_string())),
|
||||
}
|
||||
// Optionally flush after delete
|
||||
// self.db.flush()?;
|
||||
}
|
||||
|
||||
/// Lists all models of this type.
|
||||
/// Warning: This can be inefficient for large datasets as it loads all models into memory.
|
||||
pub fn list(&self) -> SledDBResult<Vec<T>> {
|
||||
let prefix = format!("{}:", T::db_prefix());
|
||||
let mut models = Vec::new();
|
||||
for result in self.db.scan_prefix(prefix.as_bytes()) {
|
||||
let (_key, value) = result?;
|
||||
models.push(T::load_from_bytes(&value)?);
|
||||
}
|
||||
Ok(models)
|
||||
}
|
||||
|
||||
/// Provides access to the underlying Sled Db instance for advanced operations.
|
||||
pub fn raw_db(&self) -> &sled::Db {
|
||||
&self.db
|
||||
}
|
||||
}
|
@@ -1,4 +1,6 @@
|
||||
ere/// Macro to implement typed access methods on the DB struct for a given model
|
||||
//! 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) => {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
s using// Export the error module
|
||||
// Export the error module
|
||||
pub mod error;
|
||||
pub use error::{DbError, DbResult};
|
||||
|
||||
@@ -14,5 +14,8 @@ pub use store::{DbOperations, OurDbStore};
|
||||
pub mod db;
|
||||
pub use db::{DB, DBBuilder, ModelRegistration, ModelRegistrar};
|
||||
|
||||
// Export the base module (compatibility layer for migration)
|
||||
pub mod base;
|
||||
|
||||
// Export macros for model methods
|
||||
pub mod macros;
|
||||
|
Reference in New Issue
Block a user