Expand index trait to also include key type

Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
Lee Smet 2025-04-23 15:09:54 +02:00
parent 276cf3c8d8
commit 1343e61e0f
Signed by untrusted user who does not match committer: lee
GPG Key ID: 72CBFB5FDA7FE025
5 changed files with 75 additions and 58 deletions

View File

@ -77,11 +77,10 @@ fn main() {
// Load all active users using the IsActive field index // Load all active users using the IsActive field index
// TODO: expand Index type so it defines the type of the key // TODO: expand Index type so it defines the type of the key
let key = true.to_string();
let active_users = db let active_users = db
.collection::<User>() .collection::<User>()
.expect("can open user collection") .expect("can open user collection")
.get::<IsActive>(&key) .get::<IsActive>(&true)
.expect("can load stored users"); .expect("can load stored users");
// We should have 2 active users // We should have 2 active users
assert_eq!(active_users.len(), 2); assert_eq!(active_users.len(), 2);
@ -96,15 +95,14 @@ fn main() {
let active_users = db let active_users = db
.collection::<User>() .collection::<User>()
.expect("can open user collection") .expect("can open user collection")
.get::<IsActive>(&key) .get::<IsActive>(&true)
.expect("can load stored users"); .expect("can load stored users");
assert_eq!(active_users.len(), 1); assert_eq!(active_users.len(), 1);
// And verify we still have 2 inactive users // And verify we still have 2 inactive users
let key = false.to_string();
let inactive_users = db let inactive_users = db
.collection::<User>() .collection::<User>()
.expect("can open user collection") .expect("can open user collection")
.get::<IsActive>(&key) .get::<IsActive>(&false)
.expect("can load stored users"); .expect("can load stored users");
assert_eq!(inactive_users.len(), 2); assert_eq!(inactive_users.len(), 2);

View File

@ -22,7 +22,7 @@ where
type Error: std::fmt::Debug; type Error: std::fmt::Debug;
/// Get all items where the given index field is equal to key. /// Get all items where the given index field is equal to key.
fn get<I>(&self, key: K) -> Result<Vec<V>, Error<Self::Error>> fn get<I>(&self, key: &I::Key) -> Result<Vec<V>, Error<Self::Error>>
where where
I: Index<Model = V>; I: Index<Model = V>;
@ -33,7 +33,7 @@ where
fn set(&self, value: &V) -> Result<(), Error<Self::Error>>; fn set(&self, value: &V) -> Result<(), Error<Self::Error>>;
/// Delete all items from the db with a given index. /// Delete all items from the db with a given index.
fn delete<I>(&self, key: K) -> Result<(), Error<Self::Error>> fn delete<I>(&self, key: &I::Key) -> Result<(), Error<Self::Error>>
where where
I: Index<Model = V>; I: Index<Model = V>;
@ -66,4 +66,3 @@ impl<E> From<bincode::error::EncodeError> for Error<E> {
Error::Encode(value) Error::Encode(value)
} }
} }

View File

@ -42,12 +42,12 @@ where
{ {
type Error = tst::Error; type Error = tst::Error;
fn get<I>(&self, key: &str) -> Result<Vec<M>, super::Error<Self::Error>> fn get<I>(&self, key: &I::Key) -> Result<Vec<M>, super::Error<Self::Error>>
where where
I: Index<Model = M>, I: Index<Model = M>,
{ {
let mut index_db = self.index.lock().expect("can lock index DB"); let mut index_db = self.index.lock().expect("can lock index DB");
let index_key = Self::index_key(M::db_prefix(), I::key(), key); let index_key = Self::index_key(M::db_prefix(), I::key(), &key.to_string());
let Some(object_ids) = Self::get_tst_value::<HashSet<u32>>(&mut index_db, &index_key)? let Some(object_ids) = Self::get_tst_value::<HashSet<u32>>(&mut index_db, &index_key)?
else { else {
@ -140,12 +140,12 @@ where
Ok(()) Ok(())
} }
fn delete<I>(&self, key: &str) -> Result<(), super::Error<Self::Error>> fn delete<I>(&self, key: &I::Key) -> Result<(), super::Error<Self::Error>>
where where
I: Index<Model = M>, I: Index<Model = M>,
{ {
let mut index_db = self.index.lock().expect("can lock index db"); let mut index_db = self.index.lock().expect("can lock index db");
let key = Self::index_key(M::db_prefix(), I::key(), key); let key = Self::index_key(M::db_prefix(), I::key(), &key.to_string());
let raw_obj_ids = index_db.get(&key)?; let raw_obj_ids = index_db.get(&key)?;
let (obj_ids, _): (HashSet<u32>, _) = let (obj_ids, _): (HashSet<u32>, _) =
bincode::serde::decode_from_slice(&raw_obj_ids, BINCODE_CONFIG)?; bincode::serde::decode_from_slice(&raw_obj_ids, BINCODE_CONFIG)?;

View File

@ -6,7 +6,7 @@ use std::fmt::Debug;
pub struct IndexKey { pub struct IndexKey {
/// The name of the index key /// The name of the index key
pub name: &'static str, pub name: &'static str,
/// The value of the index key for a specific model instance /// The value of the index key for a specific model instance
pub value: String, pub value: String,
} }
@ -25,13 +25,13 @@ impl IndexKeyBuilder {
value: String::new(), value: String::new(),
} }
} }
/// Set the value for this index key /// Set the value for this index key
pub fn value(mut self, value: impl ToString) -> Self { pub fn value(mut self, value: impl ToString) -> Self {
self.value = value.to_string(); self.value = value.to_string();
self self
} }
/// Build the IndexKey /// Build the IndexKey
pub fn build(self) -> IndexKey { pub fn build(self) -> IndexKey {
IndexKey { IndexKey {
@ -42,10 +42,14 @@ impl IndexKeyBuilder {
} }
/// Unified trait for all models /// Unified trait for all models
pub trait Model: Debug + Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static { pub trait Model:
Debug + Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static
{
/// Get the database prefix for this model type /// Get the database prefix for this model type
fn db_prefix() -> &'static str where Self: Sized; fn db_prefix() -> &'static str
where
Self: Sized;
/// Returns a list of index keys for this model instance /// Returns a list of index keys for this model instance
/// These keys will be used to create additional indexes in the TST /// These keys will be used to create additional indexes in the TST
/// The default implementation returns an empty vector /// The default implementation returns an empty vector
@ -53,21 +57,27 @@ pub trait Model: Debug + Clone + Serialize + for<'de> Deserialize<'de> + Send +
fn db_keys(&self) -> Vec<IndexKey> { fn db_keys(&self) -> Vec<IndexKey> {
Vec::new() Vec::new()
} }
/// Get the unique ID for this model /// Get the unique ID for this model
fn get_id(&self) -> u32; fn get_id(&self) -> u32;
/// Get a mutable reference to the base_data field /// Get a mutable reference to the base_data field
fn base_data_mut(&mut self) -> &mut BaseModelData; fn base_data_mut(&mut self) -> &mut BaseModelData;
/// Set the ID for this model /// Set the ID for this model
fn id(mut self, id: u32) -> Self where Self: Sized { fn id(mut self, id: u32) -> Self
where
Self: Sized,
{
self.base_data_mut().id = id; self.base_data_mut().id = id;
self self
} }
/// Build the model, updating the modified timestamp /// Build the model, updating the modified timestamp
fn build(mut self) -> Self where Self: Sized { fn build(mut self) -> Self
where
Self: Sized,
{
self.base_data_mut().update_modified(); self.base_data_mut().update_modified();
self self
} }
@ -78,6 +88,8 @@ pub trait Index {
/// The model for which this is an index in the database /// The model for which this is an index in the database
type Model: Model; type Model: Model;
type Key: ToString + ?Sized;
/// The key of this index /// The key of this index
fn key() -> &'static str; fn key() -> &'static str;
} }
@ -87,13 +99,13 @@ pub trait Index {
pub struct BaseModelData { pub struct BaseModelData {
/// Unique incremental ID per circle /// Unique incremental ID per circle
pub id: u32, pub id: u32,
/// Unix epoch timestamp for creation time /// Unix epoch timestamp for creation time
pub created_at: i64, pub created_at: i64,
/// Unix epoch timestamp for last modification time /// Unix epoch timestamp for last modification time
pub modified_at: i64, pub modified_at: i64,
/// List of comment IDs referencing Comment objects /// List of comment IDs referencing Comment objects
pub comments: Vec<u32>, pub comments: Vec<u32>,
} }
@ -109,24 +121,24 @@ impl BaseModelData {
comments: Vec::new(), comments: Vec::new(),
} }
} }
/// Create a new BaseModelDataBuilder /// Create a new BaseModelDataBuilder
pub fn builder(id: u32) -> BaseModelDataBuilder { pub fn builder(id: u32) -> BaseModelDataBuilder {
BaseModelDataBuilder::new(id) BaseModelDataBuilder::new(id)
} }
/// Add a comment to this model /// Add a comment to this model
pub fn add_comment(&mut self, comment_id: u32) { pub fn add_comment(&mut self, comment_id: u32) {
self.comments.push(comment_id); self.comments.push(comment_id);
self.modified_at = chrono::Utc::now().timestamp(); self.modified_at = chrono::Utc::now().timestamp();
} }
/// Remove a comment from this model /// Remove a comment from this model
pub fn remove_comment(&mut self, comment_id: u32) { pub fn remove_comment(&mut self, comment_id: u32) {
self.comments.retain(|&id| id != comment_id); self.comments.retain(|&id| id != comment_id);
self.update_modified(); self.update_modified();
} }
/// Update the modified timestamp /// Update the modified timestamp
pub fn update_modified(&mut self) { pub fn update_modified(&mut self) {
self.modified_at = chrono::Utc::now().timestamp(); self.modified_at = chrono::Utc::now().timestamp();
@ -151,31 +163,31 @@ impl BaseModelDataBuilder {
comments: Vec::new(), comments: Vec::new(),
} }
} }
/// Set the created_at timestamp /// Set the created_at timestamp
pub fn created_at(mut self, timestamp: i64) -> Self { pub fn created_at(mut self, timestamp: i64) -> Self {
self.created_at = Some(timestamp); self.created_at = Some(timestamp);
self self
} }
/// Set the modified_at timestamp /// Set the modified_at timestamp
pub fn modified_at(mut self, timestamp: i64) -> Self { pub fn modified_at(mut self, timestamp: i64) -> Self {
self.modified_at = Some(timestamp); self.modified_at = Some(timestamp);
self self
} }
/// Add a comment ID /// Add a comment ID
pub fn add_comment(mut self, comment_id: u32) -> Self { pub fn add_comment(mut self, comment_id: u32) -> Self {
self.comments.push(comment_id); self.comments.push(comment_id);
self self
} }
/// Add multiple comment IDs /// Add multiple comment IDs
pub fn add_comments(mut self, comment_ids: Vec<u32>) -> Self { pub fn add_comments(mut self, comment_ids: Vec<u32>) -> Self {
self.comments.extend(comment_ids); self.comments.extend(comment_ids);
self self
} }
/// Build the BaseModelData /// Build the BaseModelData
pub fn build(self) -> BaseModelData { pub fn build(self) -> BaseModelData {
let now = chrono::Utc::now().timestamp(); let now = chrono::Utc::now().timestamp();
@ -197,14 +209,15 @@ macro_rules! impl_model {
fn db_prefix() -> &'static str { fn db_prefix() -> &'static str {
$prefix $prefix
} }
fn get_id(&self) -> u32 { fn get_id(&self) -> u32 {
self.base_data.id self.base_data.id
} }
fn base_data_mut(&mut self) -> &mut $crate::core::model::BaseModelData { fn base_data_mut(&mut self) -> &mut $crate::core::model::BaseModelData {
&mut self.base_data &mut self.base_data
} }
} }
}; };
} }

View File

@ -1,21 +1,21 @@
use crate::models::core::model::{BaseModelData, Index, IndexKey, Model};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::models::core::model::{Model, BaseModelData, IndexKey, Index};
/// Represents a user in the system /// Represents a user in the system
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User { pub struct User {
/// Base model data /// Base model data
pub base_data: BaseModelData, pub base_data: BaseModelData,
/// User's username /// User's username
pub username: String, pub username: String,
/// User's email address /// User's email address
pub email: String, pub email: String,
/// User's full name /// User's full name
pub full_name: String, pub full_name: String,
/// Whether the user is active /// Whether the user is active
pub is_active: bool, pub is_active: bool,
} }
@ -31,35 +31,35 @@ impl User {
is_active: true, is_active: true,
} }
} }
/// Set the username /// Set the username
pub fn username(mut self, username: impl ToString) -> Self { pub fn username(mut self, username: impl ToString) -> Self {
self.username = username.to_string(); self.username = username.to_string();
self self
} }
/// Set the email /// Set the email
pub fn email(mut self, email: impl ToString) -> Self { pub fn email(mut self, email: impl ToString) -> Self {
self.email = email.to_string(); self.email = email.to_string();
self self
} }
/// Set the full name /// Set the full name
pub fn full_name(mut self, full_name: impl ToString) -> Self { pub fn full_name(mut self, full_name: impl ToString) -> Self {
self.full_name = full_name.to_string(); self.full_name = full_name.to_string();
self self
} }
/// Set whether the user is active /// Set whether the user is active
pub fn is_active(mut self, is_active: bool) -> Self { pub fn is_active(mut self, is_active: bool) -> Self {
self.is_active = is_active; self.is_active = is_active;
self self
} }
/// Add a comment ID /// Add a comment ID
pub fn add_comment(mut self, comment_id: u32) -> Self { pub fn add_comment(mut self, comment_id: u32) -> Self {
self.base_data.add_comment(comment_id); self.base_data.add_comment(comment_id);
@ -70,7 +70,7 @@ impl User {
pub fn deactivate(&mut self) { pub fn deactivate(&mut self) {
self.is_active = false; self.is_active = false;
} }
/// Activate the user /// Activate the user
pub fn activate(&mut self) { pub fn activate(&mut self) {
self.is_active = true; self.is_active = true;
@ -82,16 +82,16 @@ impl Model for User {
fn db_prefix() -> &'static str { fn db_prefix() -> &'static str {
"user" "user"
} }
fn get_id(&self) -> u32 { fn get_id(&self) -> u32 {
self.base_data.id self.base_data.id
} }
//WHY? //WHY?
fn base_data_mut(&mut self) -> &mut BaseModelData { fn base_data_mut(&mut self) -> &mut BaseModelData {
&mut self.base_data &mut self.base_data
} }
fn db_keys(&self) -> Vec<IndexKey> { fn db_keys(&self) -> Vec<IndexKey> {
vec![ vec![
IndexKey { IndexKey {
@ -119,6 +119,8 @@ pub struct IsActive;
impl Index for UserName { impl Index for UserName {
type Model = User; type Model = User;
type Key = str;
fn key() -> &'static str { fn key() -> &'static str {
"username" "username"
} }
@ -127,6 +129,8 @@ impl Index for UserName {
impl Index for Email { impl Index for Email {
type Model = User; type Model = User;
type Key = str;
fn key() -> &'static str { fn key() -> &'static str {
"email" "email"
} }
@ -135,7 +139,10 @@ impl Index for Email {
impl Index for IsActive { impl Index for IsActive {
type Model = User; type Model = User;
type Key = bool;
fn key() -> &'static str { fn key() -> &'static str {
"is_active" "is_active"
} }
} }