diff --git a/01.workspace/heave/src/fun/sqlite_init_db.rs b/01.workspace/heave/src/fun/sqlite_init_db.rs index a04c62a..9c32f9c 100644 --- a/01.workspace/heave/src/fun/sqlite_init_db.rs +++ b/01.workspace/heave/src/fun/sqlite_init_db.rs @@ -6,6 +6,7 @@ pub fn run(path: &path::Path) -> result::Result<(), FailedTo> { CREATE TABLE IF NOT EXISTS entity ( id TEXT PRIMARY KEY, class TEXT NOT NULL, + subclass TEXT, ref_date INTEGER ); CREATE TABLE IF NOT EXISTS attribute ( diff --git a/01.workspace/heave/src/fun/sqlite_load_attributes.rs b/01.workspace/heave/src/fun/sqlite_load_attributes.rs index ceb8480..959b7de 100644 --- a/01.workspace/heave/src/fun/sqlite_load_attributes.rs +++ b/01.workspace/heave/src/fun/sqlite_load_attributes.rs @@ -38,6 +38,7 @@ mod tests { let mut entity = Entity { id: "entity1".to_string(), class: "class1".to_string(), + subclass: None, attributes: HashMap::new(), state: Default::default(), ref_date: None, @@ -77,6 +78,7 @@ mod tests { let mut entity = Entity { id: "entity2".to_string(), class: "class1".to_string(), + subclass: None, attributes: HashMap::new(), state: Default::default(), ref_date: None, @@ -101,6 +103,7 @@ mod tests { let mut entity = Entity { id: "entity3".to_string(), class: "class1".to_string(), + subclass: None, attributes: HashMap::new(), state: Default::default(), ref_date: None, diff --git a/01.workspace/heave/src/fun/sqlite_map_row_to_entity.rs b/01.workspace/heave/src/fun/sqlite_map_row_to_entity.rs index 30d5717..2839341 100644 --- a/01.workspace/heave/src/fun/sqlite_map_row_to_entity.rs +++ b/01.workspace/heave/src/fun/sqlite_map_row_to_entity.rs @@ -3,11 +3,13 @@ use crate::*; pub fn run(row: &rusqlite::Row) -> rusqlite::Result { let id: String = row.get(0)?; let class: String = row.get(1)?; - let ref_date: Option = row.get(2)?; + let subclass: Option = row.get(2)?; + let ref_date: Option = row.get(3)?; let entity = Entity { id, state: EntityState::Loaded, class, + subclass, ref_date, attributes: std::collections::HashMap::new(), }; @@ -26,9 +28,10 @@ mod tests { fn map_row_to_entity_should_correctly_map_valid_row() { // Verifies that a valid database row is correctly mapped to an Entity struct with all fields populated. let conn = Connection::open_in_memory().unwrap(); - let entity = get_entity_from_query(&conn, "SELECT 'e1', 'c1', 12345").unwrap(); + let entity = get_entity_from_query(&conn, "SELECT 'e1', 'c1', 's1', 12345").unwrap(); assert_eq!(entity.id, "e1"); assert_eq!(entity.class, "c1"); + assert_eq!(entity.subclass, Some("s1".to_string())); assert_eq!(entity.ref_date, Some(12345)); assert_eq!(entity.state, EntityState::Loaded); assert!(entity.attributes.is_empty()); @@ -37,9 +40,20 @@ mod tests { fn map_row_to_entity_should_handle_null_ref_date() { // Ensures that a row with a NULL 'ref_date' is successfully mapped to an Entity with 'ref_date' as None. let conn = Connection::open_in_memory().unwrap(); - let entity = get_entity_from_query(&conn, "SELECT 'e2', 'c2', NULL").unwrap(); + let entity = get_entity_from_query(&conn, "SELECT 'e2', 'c2', 's2', NULL").unwrap(); assert_eq!(entity.id, "e2"); assert_eq!(entity.class, "c2"); + assert_eq!(entity.subclass, Some("s2".to_string())); + assert_eq!(entity.ref_date, None); + } + #[test] + fn map_row_to_entity_should_handle_null_subclass() { + // Ensures that a row with a NULL 'subclass' is successfully mapped to an Entity with 'subclass' as None. + let conn = Connection::open_in_memory().unwrap(); + let entity = get_entity_from_query(&conn, "SELECT 'e2', 'c2', NULL, NULL").unwrap(); + assert_eq!(entity.id, "e2"); + assert_eq!(entity.class, "c2"); + assert_eq!(entity.subclass, None); assert_eq!(entity.ref_date, None); } #[test] diff --git a/01.workspace/heave/src/fun/sqlite_persist_catalog.rs b/01.workspace/heave/src/fun/sqlite_persist_catalog.rs index 403ce33..67a7eff 100644 --- a/01.workspace/heave/src/fun/sqlite_persist_catalog.rs +++ b/01.workspace/heave/src/fun/sqlite_persist_catalog.rs @@ -113,6 +113,7 @@ mod tests { let mut entity = Entity { id: "e1".to_string(), class: "c1".to_string(), + subclass: None, attributes: HashMap::new(), state: EntityState::New, ref_date: None, @@ -141,6 +142,7 @@ mod tests { let entity = Entity { id: "e1".to_string(), class: "c1".to_string(), + subclass: None, attributes: HashMap::new(), state: EntityState::ToDelete, ref_date: None, @@ -161,6 +163,7 @@ mod tests { let to_delete = Entity { id: "e1".to_string(), class: "c1".to_string(), + subclass: None, attributes: HashMap::new(), state: EntityState::ToDelete, ref_date: None, @@ -168,6 +171,7 @@ mod tests { let to_add = Entity { id: "e2".to_string(), class: "c2".to_string(), + subclass: None, attributes: HashMap::new(), state: EntityState::New, ref_date: None, @@ -190,6 +194,7 @@ mod tests { let unmodified = Entity { id: "e1".to_string(), class: "c1".to_string(), + subclass: None, attributes: HashMap::new(), state: EntityState::Loaded, ref_date: None, @@ -213,6 +218,7 @@ mod tests { let mut new_entity = Entity { id: "e_new".to_string(), class: "c1".to_string(), + subclass: None, attributes: HashMap::new(), state: EntityState::New, ref_date: None, diff --git a/01.workspace/heave/src/lib.rs b/01.workspace/heave/src/lib.rs index 234faf7..55a8b06 100644 --- a/01.workspace/heave/src/lib.rs +++ b/01.workspace/heave/src/lib.rs @@ -32,8 +32,9 @@ //! and the necessary `From` and `TryFrom` conversions. //! //! ```rust,no_run -//! use heave::{EAV, Catalog, Entity, Value, FailedTo, Comparison, Filter}; +//! use heave::*; //! use std::convert::{From, TryFrom}; +//! use std::result::Result; //! //! // Define a simple struct representing a product. //! #[derive(Debug, Default, PartialEq, Clone)] @@ -68,10 +69,10 @@ //! //! fn try_from(entity: Entity) -> Result { //! Ok(Self { -//! id: entity.id, -//! name: entity.unwrap("name").ok_or(FailedTo::ConvertEntity)?, -//! price: entity.unwrap("price").ok_or(FailedTo::ConvertEntity)?, -//! in_stock: entity.unwrap("in_stock").ok_or(FailedTo::ConvertEntity)?, +//! id: entity.id.clone(), +//! name: entity.unwrap("name").map_err(|_| FailedTo::ConvertEntity)?, +//! price: entity.unwrap("price").map_err(|_| FailedTo::ConvertEntity)?, +//! in_stock: entity.unwrap("in_stock").map_err(|_| FailedTo::ConvertEntity)?, //! }) //! } //! } diff --git a/01.workspace/heave/src/str/catalog.rs b/01.workspace/heave/src/str/catalog.rs index d11a016..2f500b0 100644 --- a/01.workspace/heave/src/str/catalog.rs +++ b/01.workspace/heave/src/str/catalog.rs @@ -161,6 +161,24 @@ impl Catalog { .filter(move |item| item.class == T::class()) .map(|item| T::try_from(item.clone()).map_err(|_| FailedTo::ConvertEntity)) } + /// Returns an iterator over all entities of a specific class and subclass + /// in the in-memory catalog. + /// + /// This is a purely in-memory operation and does not interact with the database. + /// + /// # Returns + /// + /// An iterator that yields items of type `T` from the in-memory cache. + pub fn list_by_class_and_subclass(&self, subclass: &str) -> impl Iterator> + where + T: EAV, + { + self.items + .values() + .filter(move |item| item.class == T::class()) + .filter(move |item| item.subclass == Some(subclass.to_string())) + .map(|item| T::try_from(item.clone()).map_err(|_| FailedTo::ConvertEntity)) + } /// Returns an iterator over entities in the in-memory catalog that match a given /// class, attribute, and value. /// diff --git a/01.workspace/heave/src/str/entity.rs b/01.workspace/heave/src/str/entity.rs index c761ace..0afda28 100644 --- a/01.workspace/heave/src/str/entity.rs +++ b/01.workspace/heave/src/str/entity.rs @@ -14,6 +14,10 @@ pub struct O { /// A string identifying the "type" or "class" of the entity (e.g., "product", "user"). /// This is used to group and query entities of the same kind. pub class: String, + /// A string identifying the "subtype" or "subclass" of the entity (e.g., + /// "computer", "phone", "customer"). This is used to group and query entities + /// of different subtype but of the same kind or inside the same domain (class) + pub subclass: Option, /// A map of attribute names to `Attribute` values, holding the actual data /// of the entity. pub attributes: std::collections::HashMap, @@ -34,6 +38,7 @@ impl Entity { state: EntityState::New, ref_date: None, class: T::class().to_string(), + subclass: None, attributes: std::collections::HashMap::::new(), } } @@ -52,6 +57,11 @@ impl Entity { self } + pub fn with_subclass(mut self, subclass: &str) -> Self { + self.subclass = Some(subclass.to_string()); + self + } + /// Sets the reference date of the entity. /// /// # Arguments