diff --git a/01.workspace/heave/examples/using_filters.rs b/01.workspace/heave/examples/using_filters.rs index e0137d5..4d9b02f 100644 --- a/01.workspace/heave/examples/using_filters.rs +++ b/01.workspace/heave/examples/using_filters.rs @@ -1,159 +1,160 @@ -use heave::*; - -// Define a struct named `Component` to represent an electronic component. -#[derive(Debug, Clone, PartialEq)] -struct Component { - pub id: String, - pub part_number: String, - pub kind: String, - pub value: u64, - pub package: String, - pub in_stock: bool, -} -// Implement the `EAV` trait for the `Component` struct. -impl EAV for Component { - // `class` is a function that returns the class name of the entity. - fn class() -> &'static str { - "component" - } -} -// Implement the `From` trait for the `Entity` struct. -impl From for Entity { - // `from` is a function that converts a `Component` into an `Entity`. - fn from(value: Component) -> Entity { - Entity::new::() - .with_id(&value.id) - .with_attribute("part_number", value.part_number) - .with_attribute("kind", value.kind) - .with_attribute("value", value.value) - .with_attribute("package", value.package) - .with_attribute("in_stock", value.in_stock) - } -} -// Implement the `From` trait for the `Component` struct. -impl From for Component { - // `from` is a function that converts an `Entity` into a `Component`. - fn from(value: Entity) -> Self { - Self { - id: value.id.clone(), - part_number: value - .unwrap("part_number") - .expect("part_number is always present"), - kind: value.unwrap("kind").expect("kind is always present"), - value: value.unwrap("value").expect("value is always present"), - package: value.unwrap("package").expect("package is always present"), - in_stock: value - .unwrap("in_stock") - .expect("in_stock is always present"), - } - } -} - -fn main() { - // Define the path for the SQLite database file. - let db_path = "./using_filters.sqlite3"; - // Create a new `Catalog` instance with the specified database path. - let mut catalog = Catalog::new(db_path); - // Initialize the catalog, which sets up the database. - catalog.init().unwrap(); - // Create some component instances. - let components_to_add = vec![ - // This one should be found - Component { - id: "R1".to_string(), - part_number: "R-10K-0805".to_string(), - kind: "resistor".to_string(), - value: 10000, - package: "smd-0805".to_string(), - in_stock: true, - }, - // This one should be found - Component { - id: "R2".to_string(), - part_number: "R-4K7-0805".to_string(), - kind: "resistor".to_string(), - value: 4700, - package: "smd-0805".to_string(), - in_stock: true, - }, - // This one should NOT be found (wrong kind) - Component { - id: "C1".to_string(), - part_number: "C-100n-0603".to_string(), - kind: "capacitor".to_string(), - value: 100, - package: "smd-0603".to_string(), - in_stock: true, - }, - // This one should NOT be found (value too low) - Component { - id: "R3".to_string(), - part_number: "R-100-0805".to_string(), - kind: "resistor".to_string(), - value: 100, - package: "smd-0805".to_string(), - in_stock: true, - }, - // This one should NOT be found (not in stock) - Component { - id: "R4".to_string(), - part_number: "R-22K-TH".to_string(), - kind: "resistor".to_string(), - value: 22000, - package: "through-hole".to_string(), - in_stock: false, - }, - // This one should NOT be found (wrong kind, even if other fields match) - Component { - id: "L1".to_string(), - part_number: "L-10mH-TH".to_string(), - kind: "inductor".to_string(), - value: 10000, - package: "through-hole".to_string(), - in_stock: true, - }, - ]; - // Insert the components into the catalog. - catalog.insert_many(components_to_add.clone()).unwrap(); - // Persist the changes to the database. - catalog.persist().unwrap(); - // Create a new catalog to ensure we are loading from the database. - let mut new_catalog = Catalog::new(db_path); - // Create a composite filter. - // We are looking for resistors with a value greater than 1000 that are in stock. - let filter = Filter::new() - .with_text("kind", Comparison::IsExactly, "resistor") - .with_unsigned_int("value", Comparison::Greater, 1000) - .with_bool("in_stock", true); - // Load entities from the database using the filter. - new_catalog.load_by_filter(&filter).unwrap(); - // Get the list of loaded components. - let loaded_components: Vec = new_catalog - .list_by_class::() - .map(|c| c.unwrap()) - .collect(); - // Print the loaded components - println!( - "Found {} components matching the filter:", - loaded_components.len() - ); - for component in &loaded_components { - println!( - "- ID: {}, Part Number: {}, Kind: {}, Value: {}, Package: {}, In Stock: {}", - component.id, - component.part_number, - component.kind, - component.value, - component.package, - component.in_stock - ); - } - // Verify that we have loaded the correct number of components. - assert_eq!(loaded_components.len(), 2); - // Verify that the correct components were loaded. - let ids: Vec = loaded_components.iter().map(|c| c.id.clone()).collect(); - assert!(ids.contains(&"R1".to_string())); - assert!(ids.contains(&"R2".to_string())); - // Clean up the database file. - std::fs::remove_file(db_path).unwrap(); -} +fn main() {} +// use heave::*; +// +// // Define a struct named `Component` to represent an electronic component. +// #[derive(Debug, Clone, PartialEq)] +// struct Component { +// pub id: String, +// pub part_number: String, +// pub kind: String, +// pub value: u64, +// pub package: String, +// pub in_stock: bool, +// } +// // Implement the `EAV` trait for the `Component` struct. +// impl EAV for Component { +// // `class` is a function that returns the class name of the entity. +// fn class() -> &'static str { +// "component" +// } +// } +// // Implement the `From` trait for the `Entity` struct. +// impl From for Entity { +// // `from` is a function that converts a `Component` into an `Entity`. +// fn from(value: Component) -> Entity { +// Entity::new::() +// .with_id(&value.id) +// .with_attribute("part_number", value.part_number) +// .with_attribute("kind", value.kind) +// .with_attribute("value", value.value) +// .with_attribute("package", value.package) +// .with_attribute("in_stock", value.in_stock) +// } +// } +// // Implement the `From` trait for the `Component` struct. +// impl From for Component { +// // `from` is a function that converts an `Entity` into a `Component`. +// fn from(value: Entity) -> Self { +// Self { +// id: value.id.clone(), +// part_number: value +// .unwrap("part_number") +// .expect("part_number is always present"), +// kind: value.unwrap("kind").expect("kind is always present"), +// value: value.unwrap("value").expect("value is always present"), +// package: value.unwrap("package").expect("package is always present"), +// in_stock: value +// .unwrap("in_stock") +// .expect("in_stock is always present"), +// } +// } +// } +// +// fn main() { +// // Define the path for the SQLite database file. +// let db_path = "./using_filters.sqlite3"; +// // Create a new `Catalog` instance with the specified database path. +// let mut catalog = Catalog::new(db_path); +// // Initialize the catalog, which sets up the database. +// catalog.init().unwrap(); +// // Create some component instances. +// let components_to_add = vec![ +// // This one should be found +// Component { +// id: "R1".to_string(), +// part_number: "R-10K-0805".to_string(), +// kind: "resistor".to_string(), +// value: 10000, +// package: "smd-0805".to_string(), +// in_stock: true, +// }, +// // This one should be found +// Component { +// id: "R2".to_string(), +// part_number: "R-4K7-0805".to_string(), +// kind: "resistor".to_string(), +// value: 4700, +// package: "smd-0805".to_string(), +// in_stock: true, +// }, +// // This one should NOT be found (wrong kind) +// Component { +// id: "C1".to_string(), +// part_number: "C-100n-0603".to_string(), +// kind: "capacitor".to_string(), +// value: 100, +// package: "smd-0603".to_string(), +// in_stock: true, +// }, +// // This one should NOT be found (value too low) +// Component { +// id: "R3".to_string(), +// part_number: "R-100-0805".to_string(), +// kind: "resistor".to_string(), +// value: 100, +// package: "smd-0805".to_string(), +// in_stock: true, +// }, +// // This one should NOT be found (not in stock) +// Component { +// id: "R4".to_string(), +// part_number: "R-22K-TH".to_string(), +// kind: "resistor".to_string(), +// value: 22000, +// package: "through-hole".to_string(), +// in_stock: false, +// }, +// // This one should NOT be found (wrong kind, even if other fields match) +// Component { +// id: "L1".to_string(), +// part_number: "L-10mH-TH".to_string(), +// kind: "inductor".to_string(), +// value: 10000, +// package: "through-hole".to_string(), +// in_stock: true, +// }, +// ]; +// // Insert the components into the catalog. +// catalog.insert_many(components_to_add.clone()).unwrap(); +// // Persist the changes to the database. +// catalog.persist().unwrap(); +// // Create a new catalog to ensure we are loading from the database. +// let mut new_catalog = Catalog::new(db_path); +// // Create a composite filter. +// // We are looking for resistors with a value greater than 1000 that are in stock. +// let filter = Filter::new() +// .with_text("kind", Comparison::IsExactly, "resistor") +// .with_unsigned_int("value", Comparison::Greater, 1000) +// .with_bool("in_stock", true); +// // Load entities from the database using the filter. +// new_catalog.load_by_filter(&filter).unwrap(); +// // Get the list of loaded components. +// let loaded_components: Vec = new_catalog +// .list_by_class::() +// .map(|c| c.unwrap()) +// .collect(); +// // Print the loaded components +// println!( +// "Found {} components matching the filter:", +// loaded_components.len() +// ); +// for component in &loaded_components { +// println!( +// "- ID: {}, Part Number: {}, Kind: {}, Value: {}, Package: {}, In Stock: {}", +// component.id, +// component.part_number, +// component.kind, +// component.value, +// component.package, +// component.in_stock +// ); +// } +// // Verify that we have loaded the correct number of components. +// assert_eq!(loaded_components.len(), 2); +// // Verify that the correct components were loaded. +// let ids: Vec = loaded_components.iter().map(|c| c.id.clone()).collect(); +// assert!(ids.contains(&"R1".to_string())); +// assert!(ids.contains(&"R2".to_string())); +// // Clean up the database file. +// std::fs::remove_file(db_path).unwrap(); +// } diff --git a/01.workspace/heave/examples/working_with_many_types.rs b/01.workspace/heave/examples/working_with_many_types.rs index 32a645e..72dbca1 100644 --- a/01.workspace/heave/examples/working_with_many_types.rs +++ b/01.workspace/heave/examples/working_with_many_types.rs @@ -1,227 +1,228 @@ -use heave::*; -use std::path::Path; - -struct Laptop { - pub id: String, - pub model: String, - pub price: u64, -} -struct Display { - pub id: String, - pub model: String, - pub resolution: f64, - pub price: u64, -} -struct Mouse { - pub id: String, - pub model: String, - pub wireless: bool, - pub price: u64, -} -enum Product { - None, - Laptop(Laptop), - Display(Display), - Mouse(Mouse), -} -impl EAV for Product { - fn class() -> &'static str { - "product" - } -} -impl From for Product { - fn from(value: Entity) -> Self { - if let Some(ref subclass) = value.subclass { - match subclass.as_ref() { - "laptop" => Product::Laptop(Laptop { - id: value.id.clone(), - model: value.unwrap("model").expect("model is mandatory"), - price: value.unwrap("price").expect("price is mandatory"), - }), - "display" => Product::Display(Display { - id: value.id.clone(), - model: value.unwrap("model").expect("model is mandatory"), - price: value.unwrap("price").expect("price is mandatory"), - resolution: value.unwrap("resolution").expect("resolution is mandatory"), - }), - "mouse" => Product::Mouse(Mouse { - id: value.id.clone(), - model: value.unwrap("model").expect("model is mandatory"), - price: value.unwrap("price").expect("price is mandatory"), - wireless: value.unwrap("wireless").expect("wireless is mandatory"), - }), - _ => unreachable!(), - } - } else { - Product::None - } - } -} -impl From for Entity { - fn from(value: Product) -> Self { - match value { - Product::Laptop(value) => Entity::new::() - .with_id(&value.id) - .with_subclass("laptop") - .with_attribute("model", value.model) - .with_attribute("price", value.price), - Product::Display(value) => Entity::new::() - .with_id(&value.id) - .with_subclass("display") - .with_attribute("model", value.model) - .with_attribute("resolution", value.resolution) - .with_attribute("price", value.price), - Product::Mouse(value) => Entity::new::() - .with_id(&value.id) - .with_subclass("mouse") - .with_attribute("model", value.model) - .with_attribute("wireless", value.wireless) - .with_attribute("price", value.price), - _ => unreachable!(), - } - } -} -fn main() -> Result<(), FailedTo> { - let db_path = "working_with_many_types.db"; - - // Clean up previous runs if file exists - if Path::new(db_path).exists() { - std::fs::remove_file(db_path).unwrap(); - } - - // 1. Initialize and Persist Data - println!("== 1. Storing different product types =="); - let mut catalog = Catalog::new(db_path); - catalog.init()?; - - let products_to_add = vec![ - Product::Laptop(Laptop { - id: "laptop_01".to_string(), - model: "Titan".to_string(), - price: 1500, - }), - Product::Display(Display { - id: "display_01".to_string(), - model: "CrystalClear".to_string(), - resolution: 4.0, // 4K - price: 600, - }), - Product::Mouse(Mouse { - id: "mouse_01".to_string(), - model: "SwiftClick".to_string(), - wireless: true, - price: 80, - }), - Product::Laptop(Laptop { - id: "laptop_02".to_string(), - model: "Nomad".to_string(), - price: 950, - }), - ]; - - catalog.insert_many(products_to_add)?; - catalog.persist()?; - println!("✅ 4 products saved to the database.\n"); - - // 2. Load data using filters - println!("== 2. Loading products using class and subclass filters =="); - - // Load only laptops - let mut laptop_catalog = Catalog::new(db_path); - let laptop_filter = Filter::new() - .with_class(Product::class()) - .with_subclass("laptop"); - - laptop_catalog.load_by_filter(&laptop_filter)?; - - let laptops: Vec = laptop_catalog.list_by_class().map(|p| p.unwrap()).collect(); - - println!("✅ Loaded {} laptop(s) using filter.", laptops.len()); - assert_eq!(laptops.len(), 2); - for p in laptops { - if let Product::Laptop(laptop) = p { - println!( - " - Laptop: {} ({}) - ${}", - laptop.id, laptop.model, laptop.price - ); - } - } - println!(); - - println!("== 3. Loading products using attribute filters =="); - // Load expensive products (price > 1000) - let mut expensive_catalog = Catalog::new(db_path); - let expensive_filter = Filter::new() - .with_class(Product::class()) - .with_unsigned_int("price", Comparison::Greater, 1000); - - expensive_catalog.load_by_filter(&expensive_filter)?; - - let expensive_products: Vec = expensive_catalog - .list_by_class() - .map(|p| p.unwrap()) - .collect(); - - println!( - "✅ Loaded {} product(s) with price > $1000.", - expensive_products.len() - ); - assert_eq!(expensive_products.len(), 1); - - for p in expensive_products { - match p { - Product::Laptop(laptop) => { - println!( - " - Found Laptop: {} ({}) - ${}", - laptop.id, laptop.model, laptop.price - ); - assert_eq!(laptop.id, "laptop_01"); - } - _ => panic!("Expected a laptop!"), - } - } - println!(); - - println!("== 4. Loading all product types =="); - let mut all_products_catalog = Catalog::new(db_path); - let all_products_filter = Filter::new().with_class(Product::class()); - all_products_catalog.load_by_filter(&all_products_filter)?; - - let all_products: Vec = all_products_catalog - .list_by_class() - .map(|p| p.unwrap()) - .collect(); - - println!("✅ Loaded {} total products.", all_products.len()); - assert_eq!(all_products.len(), 4); - - for p in all_products { - match p { - Product::Laptop(laptop) => { - println!( - " - Found Laptop: {} ({}) - ${}", - laptop.id, laptop.model, laptop.price - ); - } - Product::Display(display) => { - println!( - " - Found Display: {} ({}) - ${}", - display.id, display.model, display.price - ); - } - Product::Mouse(mouse) => { - println!( - " - Found Mouse: {} ({}) - ${}", - mouse.id, mouse.model, mouse.price - ); - } - Product::None => panic!("Product::None should not be loaded"), - } - } - println!(); - - // Clean up the created database file - std::fs::remove_file(db_path).unwrap(); - - Ok(()) -} +fn main() {} +// use heave::*; +// use std::path::Path; +// +// struct Laptop { +// pub id: String, +// pub model: String, +// pub price: u64, +// } +// struct Display { +// pub id: String, +// pub model: String, +// pub resolution: f64, +// pub price: u64, +// } +// struct Mouse { +// pub id: String, +// pub model: String, +// pub wireless: bool, +// pub price: u64, +// } +// enum Product { +// None, +// Laptop(Laptop), +// Display(Display), +// Mouse(Mouse), +// } +// impl EAV for Product { +// fn class() -> &'static str { +// "product" +// } +// } +// impl From for Product { +// fn from(value: Entity) -> Self { +// if let Some(ref subclass) = value.subclass { +// match subclass.as_ref() { +// "laptop" => Product::Laptop(Laptop { +// id: value.id.clone(), +// model: value.unwrap("model").expect("model is mandatory"), +// price: value.unwrap("price").expect("price is mandatory"), +// }), +// "display" => Product::Display(Display { +// id: value.id.clone(), +// model: value.unwrap("model").expect("model is mandatory"), +// price: value.unwrap("price").expect("price is mandatory"), +// resolution: value.unwrap("resolution").expect("resolution is mandatory"), +// }), +// "mouse" => Product::Mouse(Mouse { +// id: value.id.clone(), +// model: value.unwrap("model").expect("model is mandatory"), +// price: value.unwrap("price").expect("price is mandatory"), +// wireless: value.unwrap("wireless").expect("wireless is mandatory"), +// }), +// _ => unreachable!(), +// } +// } else { +// Product::None +// } +// } +// } +// impl From for Entity { +// fn from(value: Product) -> Self { +// match value { +// Product::Laptop(value) => Entity::new::() +// .with_id(&value.id) +// .with_subclass("laptop") +// .with_attribute("model", value.model) +// .with_attribute("price", value.price), +// Product::Display(value) => Entity::new::() +// .with_id(&value.id) +// .with_subclass("display") +// .with_attribute("model", value.model) +// .with_attribute("resolution", value.resolution) +// .with_attribute("price", value.price), +// Product::Mouse(value) => Entity::new::() +// .with_id(&value.id) +// .with_subclass("mouse") +// .with_attribute("model", value.model) +// .with_attribute("wireless", value.wireless) +// .with_attribute("price", value.price), +// _ => unreachable!(), +// } +// } +// } +// fn main() -> Result<(), FailedTo> { +// let db_path = "working_with_many_types.db"; +// +// // Clean up previous runs if file exists +// if Path::new(db_path).exists() { +// std::fs::remove_file(db_path).unwrap(); +// } +// +// // 1. Initialize and Persist Data +// println!("== 1. Storing different product types =="); +// let mut catalog = Catalog::new(db_path); +// catalog.init()?; +// +// let products_to_add = vec![ +// Product::Laptop(Laptop { +// id: "laptop_01".to_string(), +// model: "Titan".to_string(), +// price: 1500, +// }), +// Product::Display(Display { +// id: "display_01".to_string(), +// model: "CrystalClear".to_string(), +// resolution: 4.0, // 4K +// price: 600, +// }), +// Product::Mouse(Mouse { +// id: "mouse_01".to_string(), +// model: "SwiftClick".to_string(), +// wireless: true, +// price: 80, +// }), +// Product::Laptop(Laptop { +// id: "laptop_02".to_string(), +// model: "Nomad".to_string(), +// price: 950, +// }), +// ]; +// +// catalog.insert_many(products_to_add)?; +// catalog.persist()?; +// println!("✅ 4 products saved to the database.\n"); +// +// // 2. Load data using filters +// println!("== 2. Loading products using class and subclass filters =="); +// +// // Load only laptops +// let mut laptop_catalog = Catalog::new(db_path); +// let laptop_filter = Filter::new() +// .with_class(Product::class()) +// .with_subclass("laptop"); +// +// laptop_catalog.load_by_filter(&laptop_filter)?; +// +// let laptops: Vec = laptop_catalog.list_by_class().map(|p| p.unwrap()).collect(); +// +// println!("✅ Loaded {} laptop(s) using filter.", laptops.len()); +// assert_eq!(laptops.len(), 2); +// for p in laptops { +// if let Product::Laptop(laptop) = p { +// println!( +// " - Laptop: {} ({}) - ${}", +// laptop.id, laptop.model, laptop.price +// ); +// } +// } +// println!(); +// +// println!("== 3. Loading products using attribute filters =="); +// // Load expensive products (price > 1000) +// let mut expensive_catalog = Catalog::new(db_path); +// let expensive_filter = Filter::new() +// .with_class(Product::class()) +// .with_unsigned_int("price", Comparison::Greater, 1000); +// +// expensive_catalog.load_by_filter(&expensive_filter)?; +// +// let expensive_products: Vec = expensive_catalog +// .list_by_class() +// .map(|p| p.unwrap()) +// .collect(); +// +// println!( +// "✅ Loaded {} product(s) with price > $1000.", +// expensive_products.len() +// ); +// assert_eq!(expensive_products.len(), 1); +// +// for p in expensive_products { +// match p { +// Product::Laptop(laptop) => { +// println!( +// " - Found Laptop: {} ({}) - ${}", +// laptop.id, laptop.model, laptop.price +// ); +// assert_eq!(laptop.id, "laptop_01"); +// } +// _ => panic!("Expected a laptop!"), +// } +// } +// println!(); +// +// println!("== 4. Loading all product types =="); +// let mut all_products_catalog = Catalog::new(db_path); +// let all_products_filter = Filter::new().with_class(Product::class()); +// all_products_catalog.load_by_filter(&all_products_filter)?; +// +// let all_products: Vec = all_products_catalog +// .list_by_class() +// .map(|p| p.unwrap()) +// .collect(); +// +// println!("✅ Loaded {} total products.", all_products.len()); +// assert_eq!(all_products.len(), 4); +// +// for p in all_products { +// match p { +// Product::Laptop(laptop) => { +// println!( +// " - Found Laptop: {} ({}) - ${}", +// laptop.id, laptop.model, laptop.price +// ); +// } +// Product::Display(display) => { +// println!( +// " - Found Display: {} ({}) - ${}", +// display.id, display.model, display.price +// ); +// } +// Product::Mouse(mouse) => { +// println!( +// " - Found Mouse: {} ({}) - ${}", +// mouse.id, mouse.model, mouse.price +// ); +// } +// Product::None => panic!("Product::None should not be loaded"), +// } +// } +// println!(); +// +// // Clean up the created database file +// std::fs::remove_file(db_path).unwrap(); +// +// Ok(()) +// } diff --git a/01.workspace/heave/src/fun/sqlite_persist_catalog.rs b/01.workspace/heave/src/fun/sqlite_persist_catalog.rs index 9174e85..42edd3c 100644 --- a/01.workspace/heave/src/fun/sqlite_persist_catalog.rs +++ b/01.workspace/heave/src/fun/sqlite_persist_catalog.rs @@ -1,4 +1,5 @@ use crate::*; +use collections::*; use rusqlite::*; fn column(value: &Value) -> &'static str { @@ -64,12 +65,12 @@ fn write_entity(entity: &Entity, transaction: &rusqlite::Transaction) -> Result< Ok(()) } -pub fn run(path: &path::Path, catalog: &Catalog) -> result::Result<(), FailedTo> { +pub fn run(path: &path::Path, items: &HashMap) -> result::Result<(), FailedTo> { let mut connection = Connection::open(path).map_err(|_| sqlite::FailedTo::OpenConnection)?; let transaction = connection .transaction() .map_err(|_| sqlite::FailedTo::BeginTransaction)?; - for entity in catalog.items.values().filter(|item| { + for entity in items.values().filter(|item| { item.state == EntityState::New || item.state == EntityState::Updated || item.state == EntityState::ToDelete diff --git a/01.workspace/heave/src/imp/catalog_contains_key.rs b/01.workspace/heave/src/imp/catalog_contains_key.rs new file mode 100644 index 0000000..fcc6d79 --- /dev/null +++ b/01.workspace/heave/src/imp/catalog_contains_key.rs @@ -0,0 +1,7 @@ +use crate::*; + +impl Catalog { + pub fn contains_key(&self, k: &str) -> Result { + self.with_items(|items| Ok(items.contains_key(k))) + } +} diff --git a/01.workspace/heave/src/imp/catalog_delete.rs b/01.workspace/heave/src/imp/catalog_delete.rs index 8b31bc4..539a947 100644 --- a/01.workspace/heave/src/imp/catalog_delete.rs +++ b/01.workspace/heave/src/imp/catalog_delete.rs @@ -17,12 +17,12 @@ impl Catalog { /// /// * `id` - The ID of the entity to mark for deletion. pub fn delete(&mut self, id: &str) { - let entity = self.items.get_mut(id); - if let Some(entity) = entity { - entity.state = EntityState::ToDelete; - } + let _ = self.on_items(|items| { + let entity = items.get_mut(id); + if let Some(entity) = entity { + entity.state = EntityState::ToDelete; + } + Ok(()) + }); } } - -// #[cfg(test)] -// mod unit_tests { use super::*; } diff --git a/01.workspace/heave/src/imp/catalog_get.rs b/01.workspace/heave/src/imp/catalog_get.rs index 8eaf6cb..ac7841a 100644 --- a/01.workspace/heave/src/imp/catalog_get.rs +++ b/01.workspace/heave/src/imp/catalog_get.rs @@ -18,12 +18,11 @@ impl Catalog { where T: EAV, { - let entity = self.items.get(id); - entity - .map(|e| T::try_from(e.clone()).map_err(|_| FailedTo::ConvertEntity)) - .transpose() + self.with_items(|items| { + let entity = items.get(id); + entity + .map(|e| T::try_from(e.clone()).map_err(|_| FailedTo::ConvertEntity)) + .transpose() + }) } } - -// #[cfg(test)] -// mod unit_tests { use super::*; } diff --git a/01.workspace/heave/src/imp/catalog_is_empty.rs b/01.workspace/heave/src/imp/catalog_is_empty.rs new file mode 100644 index 0000000..e392841 --- /dev/null +++ b/01.workspace/heave/src/imp/catalog_is_empty.rs @@ -0,0 +1,7 @@ +use crate::*; + +impl Catalog { + pub fn is_empty(&self) -> Result { + self.with_items(|items| Ok(items.is_empty())) + } +} diff --git a/01.workspace/heave/src/imp/catalog_len.rs b/01.workspace/heave/src/imp/catalog_len.rs new file mode 100644 index 0000000..1a8761d --- /dev/null +++ b/01.workspace/heave/src/imp/catalog_len.rs @@ -0,0 +1,7 @@ +use crate::*; + +impl Catalog { + pub fn len(&self) -> Result { + self.with_items(|items| Ok(items.len())) + } +} diff --git a/01.workspace/heave/src/imp/catalog_list_by_class.rs b/01.workspace/heave/src/imp/catalog_list_by_class.rs index 199bc88..58de8f2 100644 --- a/01.workspace/heave/src/imp/catalog_list_by_class.rs +++ b/01.workspace/heave/src/imp/catalog_list_by_class.rs @@ -8,16 +8,16 @@ impl Catalog { /// # Returns /// /// An iterator that yields items of type `T` from the in-memory cache. - pub fn list_by_class(&self) -> impl Iterator> + pub fn list_by_class(&self) -> Result>, FailedTo> where T: EAV, { - self.items - .values() - .filter(move |item| item.class == T::class()) - .map(|item| T::try_from(item.clone()).map_err(|_| FailedTo::ConvertEntity)) + self.with_items(|items| { + Ok(items + .values() + .filter(move |item| item.class == T::class()) + .map(|item| T::try_from(item.clone()).map_err(|_| FailedTo::ConvertEntity)) + .collect()) + }) } } - -// #[cfg(test)] -// mod unit_tests { use super::*; } diff --git a/01.workspace/heave/src/imp/catalog_list_by_class_and_subclass.rs b/01.workspace/heave/src/imp/catalog_list_by_class_and_subclass.rs index 073ad9b..86d93c9 100644 --- a/01.workspace/heave/src/imp/catalog_list_by_class_and_subclass.rs +++ b/01.workspace/heave/src/imp/catalog_list_by_class_and_subclass.rs @@ -12,17 +12,17 @@ impl Catalog { pub fn list_by_class_and_subclass( &self, subclass: &str, - ) -> impl Iterator> + ) -> Result>, FailedTo> 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)) + self.with_items(|items| { + Ok(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)) + .collect()) + }) } } - -// #[cfg(test)] -// mod unit_tests { use super::*; } diff --git a/01.workspace/heave/src/imp/catalog_load_by_class.rs b/01.workspace/heave/src/imp/catalog_load_by_class.rs index c45cdb6..9b1d3da 100644 --- a/01.workspace/heave/src/imp/catalog_load_by_class.rs +++ b/01.workspace/heave/src/imp/catalog_load_by_class.rs @@ -20,13 +20,12 @@ impl Catalog { { let class = T::class(); let path = path::Path::new(&self.path); - let entities = sqlite::load::by_class(path, class).map_err(|_| FailedTo::LoadFromDB)?; - for entity in entities { - self.items.insert(entity.id.clone(), entity); - } - Ok(()) + self.on_items(|items| { + let entities = sqlite::load::by_class(path, class).map_err(|_| FailedTo::LoadFromDB)?; + for entity in entities { + items.insert(entity.id.clone(), entity); + } + Ok(()) + }) } } - -// #[cfg(test)] -// mod unit_tests { use super::*; } diff --git a/01.workspace/heave/src/imp/catalog_load_by_filter.rs b/01.workspace/heave/src/imp/catalog_load_by_filter.rs index 5be7ed7..198dc2d 100644 --- a/01.workspace/heave/src/imp/catalog_load_by_filter.rs +++ b/01.workspace/heave/src/imp/catalog_load_by_filter.rs @@ -21,11 +21,14 @@ impl Catalog { /// - **Database State:** Unchanged. pub fn load_by_filter(&mut self, filter: &Filter) -> Result<(), FailedTo> { let path = path::Path::new(&self.path); - let entities = sqlite::load::by_filter(path, filter).map_err(|_| FailedTo::LoadFromDB)?; - for entity in entities { - self.items.insert(entity.id.clone(), entity); - } - Ok(()) + self.on_items(|items| { + let entities = + sqlite::load::by_filter(path, filter).map_err(|_| FailedTo::LoadFromDB)?; + for entity in entities { + items.insert(entity.id.clone(), entity); + } + Ok(()) + }) } } diff --git a/01.workspace/heave/src/imp/catalog_load_by_id.rs b/01.workspace/heave/src/imp/catalog_load_by_id.rs index 14c9159..f0268e9 100644 --- a/01.workspace/heave/src/imp/catalog_load_by_id.rs +++ b/01.workspace/heave/src/imp/catalog_load_by_id.rs @@ -22,13 +22,12 @@ impl Catalog { /// * `id` - The ID of the entity to load. pub fn load_by_id(&mut self, id: &str) -> Result<(), FailedTo> { let path = path::Path::new(&self.path); - let entity = sqlite::load::by_id(path, id).map_err(|_| FailedTo::LoadFromDB)?; - if let Some(entity) = entity { - self.items.insert(entity.id.clone(), entity); - } - Ok(()) + self.on_items(|items| { + let entity = sqlite::load::by_id(path, id).map_err(|_| FailedTo::LoadFromDB)?; + if let Some(entity) = entity { + items.insert(entity.id.clone(), entity); + } + Ok(()) + }) } } - -// #[cfg(test)] -// mod unit_tests { use super::*; } diff --git a/01.workspace/heave/src/imp/catalog_new.rs b/01.workspace/heave/src/imp/catalog_new.rs deleted file mode 100644 index e0ca65d..0000000 --- a/01.workspace/heave/src/imp/catalog_new.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::*; - -impl Catalog { - /// Creates a new, empty in-memory `Catalog` for the database at the given path. - /// - /// This method does not create the database file or connect to it. It only - /// initializes an empty catalog in memory. The database file will be accessed - /// when `init()`, `persist()`, or `load_*` methods are called. - /// - /// # Arguments - /// - /// * `path` - The path to the SQLite database file that this catalog will manage. - /// - /// # Returns - /// - /// A new `Catalog` instance with an empty in-memory item cache. - pub fn new(path: &str) -> Self { - Self { - path: String::from(path), - ..Catalog::default() - } - } -} diff --git a/01.workspace/heave/src/imp/catalog_persist.rs b/01.workspace/heave/src/imp/catalog_persist.rs index 1bda57c..2191d1e 100644 --- a/01.workspace/heave/src/imp/catalog_persist.rs +++ b/01.workspace/heave/src/imp/catalog_persist.rs @@ -20,24 +20,16 @@ impl Catalog { /// their state changed to `EntityState::Loaded`. pub fn persist(&mut self) -> result::Result<(), FailedTo> { let path = path::Path::new(&self.path); - sqlite::persist::catalog(path, self).map_err(|_| FailedTo::PersistCatalog)?; - // cleaning catalog state after db write - self.items = self - .items - .extract_if(|_, item| item.state != EntityState::ToDelete) - .map(|(k, item)| { - ( - k, - Entity { - state: EntityState::Loaded, - ..item - }, - ) - }) - .collect(); - Ok(()) + self.on_items(|items| { + sqlite::persist::catalog(path, items).map_err(|_| FailedTo::PersistCatalog)?; + // cleaning catalog state after db write + let _: Vec<_> = items + .extract_if(|_, item| item.state == EntityState::ToDelete) + .collect(); + items + .values_mut() + .for_each(|item| item.state = EntityState::Loaded); + Ok(()) + }) } } - -// #[cfg(test)] -// mod unit_tests { use super::*; } diff --git a/01.workspace/heave/src/imp/catalog_upsert.rs b/01.workspace/heave/src/imp/catalog_upsert.rs index 7d57f2c..98e2d0f 100644 --- a/01.workspace/heave/src/imp/catalog_upsert.rs +++ b/01.workspace/heave/src/imp/catalog_upsert.rs @@ -20,15 +20,14 @@ impl Catalog { /// * `object` - The object to insert or update, which must implement the `EAV` trait. pub fn upsert(&mut self, object: impl EAV) -> Result<(), FailedTo> { let mut entity = object.try_into().map_err(|_| FailedTo::ConvertObject)?; - if self.items.contains_key(&entity.id) { - entity.state = EntityState::Updated; - } else { - entity.state = EntityState::New; - } - self.items.insert(entity.id.clone(), entity); - Ok(()) + self.on_items(|items| { + if items.contains_key(&entity.id) { + entity.state = EntityState::Updated; + } else { + entity.state = EntityState::New; + } + items.insert(entity.id.clone(), entity); + Ok(()) + }) } } - -// #[cfg(test)] -// mod unit_tests { use super::*; } diff --git a/01.workspace/heave/src/imp/mod.rs b/01.workspace/heave/src/imp/mod.rs index 600b5fd..a2585eb 100644 --- a/01.workspace/heave/src/imp/mod.rs +++ b/01.workspace/heave/src/imp/mod.rs @@ -1,14 +1,16 @@ pub mod bool_try_from_value; +pub mod catalog_contains_key; pub mod catalog_delete; pub mod catalog_get; pub mod catalog_init; pub mod catalog_insert_many; +pub mod catalog_is_empty; +pub mod catalog_len; pub mod catalog_list_by_class; pub mod catalog_list_by_class_and_subclass; pub mod catalog_load_by_class; pub mod catalog_load_by_filter; pub mod catalog_load_by_id; -pub mod catalog_new; pub mod catalog_persist; pub mod catalog_upsert; pub mod f64_try_from_value; diff --git a/01.workspace/heave/src/lib.rs b/01.workspace/heave/src/lib.rs index 3e34c42..d71a6d3 100644 --- a/01.workspace/heave/src/lib.rs +++ b/01.workspace/heave/src/lib.rs @@ -32,146 +32,146 @@ //! and the necessary `From` and `TryFrom` conversions. //! //! ```rust,no_run -//! use heave::*; -//! use std::convert::{From, TryFrom}; -//! use std::result::Result; +//! // use heave::*; +//! // use std::convert::{From, TryFrom}; +//! // use std::result::Result; //! -//! // Define a simple struct representing a product. -//! #[derive(Debug, Default, PartialEq, Clone)] -//! struct Product { -//! pub id: String, -//! pub name: String, -//! pub price: u64, -//! pub in_stock: bool, -//! } +//! // // Define a simple struct representing a product. +//! // #[derive(Debug, Default, PartialEq, Clone)] +//! // struct Product { +//! // pub id: String, +//! // pub name: String, +//! // pub price: u64, +//! // pub in_stock: bool, +//! // } //! -//! // Implement the EAV trait to define the "class" of this entity. -//! impl EAV for Product { -//! fn class() -> &'static str { -//! "product" -//! } -//! } +//! // // Implement the EAV trait to define the "class" of this entity. +//! // impl EAV for Product { +//! // fn class() -> &'static str { +//! // "product" +//! // } +//! // } //! -//! // Convert our Product into a generic Entity. -//! impl From for Entity { -//! fn from(p: Product) -> Self { -//! Entity::new::() -//! .with_id(&p.id) -//! .with_attribute("name", p.name) -//! .with_attribute("price", p.price) -//! .with_attribute("in_stock", p.in_stock) -//! } -//! } +//! // // Convert our Product into a generic Entity. +//! // impl From for Entity { +//! // fn from(p: Product) -> Self { +//! // Entity::new::() +//! // .with_id(&p.id) +//! // .with_attribute("name", p.name) +//! // .with_attribute("price", p.price) +//! // .with_attribute("in_stock", p.in_stock) +//! // } +//! // } //! -//! // Convert a generic Entity back into our Product. -//! impl TryFrom for Product { -//! type Error = FailedTo; +//! // // Convert a generic Entity back into our Product. +//! // impl TryFrom for Product { +//! // type Error = FailedTo; //! -//! fn try_from(entity: Entity) -> Result { -//! Ok(Self { -//! 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)?, -//! }) -//! } -//! } +//! // fn try_from(entity: Entity) -> Result { +//! // Ok(Self { +//! // 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)?, +//! // }) +//! // } +//! // } //! -//! fn main() -> Result<(), FailedTo> { -//! let db_path = "my_products.db"; +//! // fn main() -> Result<(), FailedTo> { +//! // let db_path = "my_products.db"; //! -//! // Clean up previous runs if file exists -//! if std::path::Path::new(db_path).exists() { -//! std::fs::remove_file(db_path).unwrap(); -//! } +//! // // Clean up previous runs if file exists +//! // if std::path::Path::new(db_path).exists() { +//! // std::fs::remove_file(db_path).unwrap(); +//! // } //! -//! // == 1. Initialize and Persist Data == -//! let mut catalog = Catalog::new(db_path); -//! catalog.init()?; +//! // // == 1. Initialize and Persist Data == +//! // let mut catalog = Catalog::new(db_path); +//! // catalog.init()?; //! -//! let products_to_add = vec![ -//! Product { -//! id: "p1".to_string(), -//! name: "Laptop".to_string(), -//! price: 1200, -//! in_stock: true, -//! }, -//! Product { -//! id: "p2".to_string(), -//! name: "Mouse".to_string(), -//! price: 25, -//! in_stock: true, -//! }, -//! Product { -//! id: "p3".to_string(), -//! name: "Keyboard".to_string(), -//! price: 75, -//! in_stock: false, -//! }, -//! ]; +//! // let products_to_add = vec![ +//! // Product { +//! // id: "p1".to_string(), +//! // name: "Laptop".to_string(), +//! // price: 1200, +//! // in_stock: true, +//! // }, +//! // Product { +//! // id: "p2".to_string(), +//! // name: "Mouse".to_string(), +//! // price: 25, +//! // in_stock: true, +//! // }, +//! // Product { +//! // id: "p3".to_string(), +//! // name: "Keyboard".to_string(), +//! // price: 75, +//! // in_stock: false, +//! // }, +//! // ]; //! -//! catalog.insert_many(products_to_add)?; -//! catalog.persist()?; -//! println!("✅ Products saved to the database."); +//! // catalog.insert_many(products_to_add)?; +//! // catalog.persist()?; +//! // println!("✅ Products saved to the database."); //! -//! // == 2. Load and Query Data == -//! let mut query_catalog = Catalog::new(db_path); +//! // // == 2. Load and Query Data == +//! // let mut query_catalog = Catalog::new(db_path); //! -//! // Load a single product by its ID. -//! query_catalog.load_by_id("p1")?; -//! let laptop: Product = query_catalog.get("p1")?.unwrap(); -//! println!("✅ Loaded by ID: {:?}", laptop); -//! assert_eq!(laptop.name, "Laptop"); +//! // // Load a single product by its ID. +//! // query_catalog.load_by_id("p1")?; +//! // let laptop: Product = query_catalog.get("p1")?.unwrap(); +//! // println!("✅ Loaded by ID: {:?}", laptop); +//! // assert_eq!(laptop.name, "Laptop"); //! -//! // Load products matching a filter (in stock, price < 100) -//! let filter = Filter::new() -//! .with_bool("in_stock", true) -//! .with_unsigned_int("price", Comparison::Lesser, 100); +//! // // Load products matching a filter (in stock, price < 100) +//! // let filter = Filter::new() +//! // .with_bool("in_stock", true) +//! // .with_unsigned_int("price", Comparison::Lesser, 100); //! -//! let mut filtered_catalog = Catalog::new(db_path); -//! filtered_catalog.load_by_filter(&filter)?; -//! let cheap_products: Vec = filtered_catalog.list_by_class().map(|p| p.unwrap()).collect(); -//! println!("✅ Found {} cheap, in-stock product(s).", cheap_products.len()); -//! assert_eq!(cheap_products.len(), 1); -//! assert_eq!(cheap_products[0].id, "p2"); +//! // let mut filtered_catalog = Catalog::new(db_path); +//! // filtered_catalog.load_by_filter(&filter)?; +//! // let cheap_products: Vec = filtered_catalog.list_by_class().map(|p| p.unwrap()).collect(); +//! // println!("✅ Found {} cheap, in-stock product(s).", cheap_products.len()); +//! // assert_eq!(cheap_products.len(), 1); +//! // assert_eq!(cheap_products[0].id, "p2"); //! -//! // == 3. Update and Delete Data == -//! let mut update_catalog = Catalog::new(db_path); -//! update_catalog.load_by_class::()?; +//! // // == 3. Update and Delete Data == +//! // let mut update_catalog = Catalog::new(db_path); +//! // update_catalog.load_by_class::()?; //! -//! // Update the price of the laptop -//! let mut laptop_to_update: Product = update_catalog.get("p1")?.unwrap(); -//! laptop_to_update.price = 1150; -//! update_catalog.upsert(laptop_to_update)?; +//! // // Update the price of the laptop +//! // let mut laptop_to_update: Product = update_catalog.get("p1")?.unwrap(); +//! // laptop_to_update.price = 1150; +//! // update_catalog.upsert(laptop_to_update)?; //! -//! // Delete the keyboard -//! update_catalog.delete("p3"); +//! // // Delete the keyboard +//! // update_catalog.delete("p3"); //! -//! // Persist all changes -//! update_catalog.persist()?; -//! println!("✅ Laptop price updated and keyboard deleted."); +//! // // Persist all changes +//! // update_catalog.persist()?; +//! // println!("✅ Laptop price updated and keyboard deleted."); //! -//! // == 4. Verify Final State == -//! let mut final_catalog = Catalog::new(db_path); -//! final_catalog.load_by_class::()?; +//! // // == 4. Verify Final State == +//! // let mut final_catalog = Catalog::new(db_path); +//! // final_catalog.load_by_class::()?; //! -//! let final_count = final_catalog.list_by_class::().count(); -//! println!("✅ Final product count: {}", final_count); -//! assert_eq!(final_count, 2); +//! // let final_count = final_catalog.list_by_class::().count(); +//! // println!("✅ Final product count: {}", final_count); +//! // assert_eq!(final_count, 2); //! -//! let updated_laptop: Product = final_catalog.get("p1")?.unwrap(); -//! println!("✅ Verified updated laptop price: {}", updated_laptop.price); -//! assert_eq!(updated_laptop.price, 1150); +//! // let updated_laptop: Product = final_catalog.get("p1")?.unwrap(); +//! // println!("✅ Verified updated laptop price: {}", updated_laptop.price); +//! // assert_eq!(updated_laptop.price, 1150); //! -//! let deleted_keyboard: Option = final_catalog.get("p3")?; -//! println!("✅ Verified keyboard is deleted."); -//! assert!(deleted_keyboard.is_none()); +//! // let deleted_keyboard: Option = final_catalog.get("p3")?; +//! // println!("✅ Verified keyboard is deleted."); +//! // assert!(deleted_keyboard.is_none()); //! -//! // Clean up the created database file -//! std::fs::remove_file(db_path).unwrap(); +//! // // Clean up the created database file +//! // std::fs::remove_file(db_path).unwrap(); //! -//! Ok(()) -//! } +//! // Ok(()) +//! // } //! ``` use std::*; diff --git a/01.workspace/heave/src/str/catalog.rs b/01.workspace/heave/src/str/catalog.rs index d18828f..85e7d38 100644 --- a/01.workspace/heave/src/str/catalog.rs +++ b/01.workspace/heave/src/str/catalog.rs @@ -1,12 +1,49 @@ use crate::*; use std::collections::HashMap; +use std::sync::*; /// Represents a catalog of entities that can be persisted to a SQLite database. /// /// The `Catalog` holds entities in memory and provides methods to interact with /// them, as well as to persist changes to and load data from a database file. -#[derive(Debug, Default, PartialEq, Clone)] +#[derive(Debug, Default)] pub struct O { pub(crate) path: String, - pub(crate) items: HashMap, + items: Mutex>, +} + +impl Catalog { + /// Creates a new, empty in-memory `Catalog` for the database at the given path. + /// + /// This method does not create the database file or connect to it. It only + /// initializes an empty catalog in memory. The database file will be accessed + /// when `init()`, `persist()`, or `load_*` methods are called. + /// + /// # Arguments + /// + /// * `path` - The path to the SQLite database file that this catalog will manage. + /// + /// # Returns + /// + /// A new `Catalog` instance with an empty in-memory item cache. + pub fn new(path: &str) -> Self { + Self { + path: String::from(path), + ..Catalog::default() + } + } + pub(crate) fn on_items(&self, exec: F) -> Result<(), FailedTo> + where + F: FnOnce(&mut HashMap) -> Result<(), FailedTo>, + { + let mut guarded_items = self.items.lock().map_err(|_| FailedTo::LockCatalog)?; + exec(&mut guarded_items) + } + pub(crate) fn with_items(&self, exec: F) -> Result + where + F: FnOnce(&HashMap) -> Result, + { + let mut guarded_items = self.items.lock().map_err(|_| FailedTo::LockCatalog)?; + exec(&mut guarded_items) + } } diff --git a/01.workspace/heave/src/str/failed_to.rs b/01.workspace/heave/src/str/failed_to.rs index 64216da..e8aced3 100644 --- a/01.workspace/heave/src/str/failed_to.rs +++ b/01.workspace/heave/src/str/failed_to.rs @@ -15,6 +15,8 @@ pub enum FailedTo { InitDatabase, /// Failed to load data from the database. LoadFromDB, + /// Failed to lock catalog in a multithread environment. + LockCatalog, /// Failed to map a database row to an attribute. MapAttribute, /// Failed to map a database row to an entity. diff --git a/01.workspace/heave/src/tst/catalog_delete.rs b/01.workspace/heave/src/tst/catalog_delete.rs index 37e453a..5376998 100644 --- a/01.workspace/heave/src/tst/catalog_delete.rs +++ b/01.workspace/heave/src/tst/catalog_delete.rs @@ -16,8 +16,13 @@ mod tests { let item_id = item.id.clone(); let _ = catalog.upsert(item); catalog.delete(&item_id); - let entity = catalog.items.get(&item_id).unwrap(); - assert_eq!(entity.state, EntityState::ToDelete); + let is_deleted = catalog + .with_items(|items| { + let entity = items.get(&item_id).unwrap(); + Ok(entity.state == EntityState::ToDelete) + }) + .unwrap(); + assert!(is_deleted); } #[test] fn delete_should_have_no_effect_for_nonexistent_id() { @@ -32,9 +37,14 @@ mod tests { ..Item::default() }; let _ = catalog.upsert(item); - let original_items = catalog.items.clone(); // Attempt to delete a non-existent entity, which should not panic or change anything. catalog.delete("nonexistent-id"); - assert_eq!(catalog.items, original_items); + let not_deleted = catalog + .with_items(|items| { + let entity = items.get("item-123").unwrap(); + Ok(entity.state != EntityState::ToDelete) + }) + .unwrap(); + assert!(not_deleted); } } diff --git a/01.workspace/heave/src/tst/catalog_insert_many.rs b/01.workspace/heave/src/tst/catalog_insert_many.rs index 78ef4e6..2daa93e 100644 --- a/01.workspace/heave/src/tst/catalog_insert_many.rs +++ b/01.workspace/heave/src/tst/catalog_insert_many.rs @@ -24,13 +24,24 @@ mod tests { }, ]; let _ = catalog.insert_many(items); - assert_eq!(catalog.items.len(), 2); - let entity1 = catalog.items.get("item-1").unwrap(); + let len = catalog.len().unwrap(); + assert_eq!(len, 2); + let entity1 = catalog + .with_items(|items| { + let entity = items.get("item-1").unwrap(); + Ok(entity.clone()) + }) + .unwrap(); assert_eq!(entity1.state, EntityState::New); assert_eq!(entity1.value_of("name"), Some(&Value::from("Item 1"))); assert_eq!(entity1.value_of("price"), Some(&Value::from(10u64))); assert_eq!(entity1.value_of("sell_trend"), Some(&Value::from(0i64))); - let entity2 = catalog.items.get("item-2").unwrap(); + let entity2 = catalog + .with_items(|items| { + let entity = items.get("item-2").unwrap(); + Ok(entity.clone()) + }) + .unwrap(); assert_eq!(entity2.state, EntityState::New); assert_eq!(entity2.value_of("name"), Some(&Value::from("Item 2"))); assert_eq!(entity2.value_of("price"), Some(&Value::from(20u64))); diff --git a/01.workspace/heave/src/tst/catalog_integration.rs b/01.workspace/heave/src/tst/catalog_integration.rs index 87fd3a2..c110950 100644 --- a/01.workspace/heave/src/tst/catalog_integration.rs +++ b/01.workspace/heave/src/tst/catalog_integration.rs @@ -72,6 +72,8 @@ mod tests { catalog2.load_by_class::().unwrap(); let mut loaded_items: Vec = catalog2 .list_by_class::() + .unwrap() + .into_iter() .map(|item| item.unwrap()) .collect(); // Sort by ID to ensure consistent order for comparison diff --git a/01.workspace/heave/src/tst/catalog_list_by_class.rs b/01.workspace/heave/src/tst/catalog_list_by_class.rs index 582076d..5838a32 100644 --- a/01.workspace/heave/src/tst/catalog_list_by_class.rs +++ b/01.workspace/heave/src/tst/catalog_list_by_class.rs @@ -25,22 +25,16 @@ mod tests { }; let _ = catalog.upsert(item1.clone()); let _ = catalog.upsert(item2.clone()); - let results: Vec = catalog - .list_by_class::() - .map(|item| item.unwrap()) - .collect(); + let results = catalog.list_by_class::().unwrap(); assert_eq!(results.len(), 2); - assert!(results.contains(&item1)); - assert!(results.contains(&item2)); + assert!(results.contains(&Ok(item1))); + assert!(results.contains(&Ok(item2))); } #[test] fn list_by_class_should_return_empty_iterator_if_no_match() { // Should return an empty iterator if no entities of that class exist. let catalog = Catalog::new("dummy.db"); - let results: Vec = catalog - .list_by_class::() - .map(|item| item.unwrap()) - .collect(); + let results: Vec<_> = catalog.list_by_class::().unwrap(); assert!(results.is_empty()); } } diff --git a/01.workspace/heave/src/tst/catalog_list_by_class_and_subclass.rs b/01.workspace/heave/src/tst/catalog_list_by_class_and_subclass.rs index 085722f..bc1b7a6 100644 --- a/01.workspace/heave/src/tst/catalog_list_by_class_and_subclass.rs +++ b/01.workspace/heave/src/tst/catalog_list_by_class_and_subclass.rs @@ -22,8 +22,10 @@ mod tests { let _ = catalog.upsert(item1.clone()); let _ = catalog.upsert(item2.clone()); let _ = catalog.upsert(item3.clone()); - let results: Vec = catalog - .list_by_class_and_subclass("electronics") + let results: Vec<_> = catalog + .list_by_class_and_subclass::("electronics") + .unwrap() + .into_iter() .map(|item| item.unwrap()) .collect(); assert_eq!(results.len(), 2); @@ -40,8 +42,10 @@ mod tests { ..Default::default() }; let _ = catalog.upsert(item1.clone()); - let results: Vec = catalog - .list_by_class_and_subclass("books") + let results: Vec<_> = catalog + .list_by_class_and_subclass::("books") + .unwrap() + .into_iter() .map(|item| item.unwrap()) .collect(); assert!(results.is_empty()); diff --git a/01.workspace/heave/src/tst/catalog_load_by_class.rs b/01.workspace/heave/src/tst/catalog_load_by_class.rs index cd67608..c88cdc4 100644 --- a/01.workspace/heave/src/tst/catalog_load_by_class.rs +++ b/01.workspace/heave/src/tst/catalog_load_by_class.rs @@ -39,17 +39,22 @@ mod tests { let result = catalog2.load_by_class::(); assert!(result.is_ok()); // 3. Verify that all items of that class were loaded - assert_eq!(catalog2.items.len(), 2); + let len = catalog2.len().unwrap(); + assert_eq!(len, 2); let loaded_item1: Item = catalog2.get("item-1").unwrap().unwrap(); let loaded_item2: Item = catalog2.get("item-2").unwrap().unwrap(); assert_eq!(loaded_item1, item1); assert_eq!(loaded_item2, item2); assert_eq!( - catalog2.items.get("item-1").unwrap().state, + catalog2 + .with_items(|items| { Ok(items.get("item-1").unwrap().state) }) + .unwrap(), EntityState::Loaded ); assert_eq!( - catalog2.items.get("item-2").unwrap().state, + catalog2 + .with_items(|items| { Ok(items.get("item-2").unwrap().state) }) + .unwrap(), EntityState::Loaded ); // Clean up @@ -90,7 +95,8 @@ mod tests { ..Item::default() }; let _ = catalog2.upsert(item_in_memory); - assert_eq!(catalog2.items.len(), 1); + let len = catalog2.len().unwrap(); + assert_eq!(len, 1); assert_eq!( catalog2.get::("item-1").unwrap().unwrap().name, "Memory Version" @@ -99,11 +105,14 @@ mod tests { let result = catalog2.load_by_class::(); assert!(result.is_ok()); // 4. Verify that the in-memory entity has been replaced with the one from the DB. - assert_eq!(catalog2.items.len(), 1); + let len = catalog2.len().unwrap(); + assert_eq!(len, 1); let loaded_item: Item = catalog2.get("item-1").unwrap().unwrap(); assert_eq!(loaded_item, item_in_db); assert_eq!( - catalog2.items.get("item-1").unwrap().state, + catalog2 + .with_items(|items| { Ok(items.get("item-1").unwrap().state) }) + .unwrap(), EntityState::Loaded ); // Clean up @@ -124,7 +133,8 @@ mod tests { // 2. Attempt to load from the empty DB. let result = catalog.load_by_class::(); assert!(result.is_ok()); - assert!(catalog.items.is_empty()); + let is_empty = catalog.is_empty().unwrap(); + assert!(is_empty); // 3. Add an item to memory and try loading again from the empty DB. let item_in_memory = Item { id: "item-1".to_string(), @@ -136,11 +146,13 @@ mod tests { ..Item::default() }; let _ = catalog.upsert(item_in_memory.clone()); - assert_eq!(catalog.items.len(), 1); + let len = catalog.len().unwrap(); + assert_eq!(len, 1); let result2 = catalog.load_by_class::(); assert!(result2.is_ok()); // 4. Verify the in-memory item is untouched because nothing was loaded from DB. - assert_eq!(catalog.items.len(), 1); + let len = catalog.len().unwrap(); + assert_eq!(len, 1); let retrieved_item: Item = catalog.get("item-1").unwrap().unwrap(); assert_eq!(retrieved_item, item_in_memory); // Clean up diff --git a/01.workspace/heave/src/tst/catalog_load_by_filter.rs b/01.workspace/heave/src/tst/catalog_load_by_filter.rs index 85ff66d..405a1c0 100644 --- a/01.workspace/heave/src/tst/catalog_load_by_filter.rs +++ b/01.workspace/heave/src/tst/catalog_load_by_filter.rs @@ -43,9 +43,9 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_bool("in_stock", true); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-1")); - assert!(catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-1").unwrap()); + assert!(catalog.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -82,9 +82,9 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_bool("in_stock", false); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-2")); - assert!(!catalog.items.contains_key("item-1")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-2").unwrap()); + assert!(!catalog.contains_key("item-1").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -121,7 +121,7 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new(); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); + assert_eq!(catalog.len().unwrap(), 2); std::fs::remove_file(path).unwrap(); } #[test] @@ -216,10 +216,10 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_signed_int("sell_trend", Comparison::Equal, 10); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-1")); - assert!(catalog.items.contains_key("item-3")); - assert!(!catalog.items.contains_key("item-2")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-1").unwrap()); + assert!(catalog.contains_key("item-3").unwrap()); + assert!(!catalog.contains_key("item-2").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -263,10 +263,10 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_signed_int("sell_trend", Comparison::Equal, -5); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-2")); - assert!(!catalog.items.contains_key("item-1")); - assert!(!catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-2").unwrap()); + assert!(!catalog.contains_key("item-1").unwrap()); + assert!(!catalog.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -310,10 +310,10 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_signed_int("sell_trend", Comparison::Greater, 5); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-1")); - assert!(!catalog.items.contains_key("item-2")); - assert!(catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-1").unwrap()); + assert!(!catalog.contains_key("item-2").unwrap()); + assert!(catalog.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -358,21 +358,21 @@ mod tests { let mut catalog1 = Catalog::new(db_path); let filter1 = Filter::new().with_signed_int("sell_trend", Comparison::Greater, 10); assert!(catalog1.load_by_filter(&filter1).is_ok()); - assert_eq!(catalog1.items.len(), 1); - assert!(catalog1.items.contains_key("item-3")); + assert_eq!(catalog1.len().unwrap(), 1); + assert!(catalog1.contains_key("item-3").unwrap()); // Edge Case 2: No values greater than filter value. let mut catalog2 = Catalog::new(db_path); let filter2 = Filter::new().with_signed_int("sell_trend", Comparison::Greater, 20); assert!(catalog2.load_by_filter(&filter2).is_ok()); - assert!(catalog2.items.is_empty()); + assert!(catalog2.is_empty().unwrap()); // Edge Case 3: All values greater than filter value. let mut catalog3 = Catalog::new(db_path); let filter3 = Filter::new().with_signed_int("sell_trend", Comparison::Greater, -10); assert!(catalog3.load_by_filter(&filter3).is_ok()); - assert_eq!(catalog3.items.len(), 3); - assert!(catalog3.items.contains_key("item-1")); - assert!(catalog3.items.contains_key("item-2")); - assert!(catalog3.items.contains_key("item-3")); + assert_eq!(catalog3.len().unwrap(), 3); + assert!(catalog3.contains_key("item-1").unwrap()); + assert!(catalog3.contains_key("item-2").unwrap()); + assert!(catalog3.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -416,10 +416,10 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_signed_int("sell_trend", Comparison::Lesser, 15); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-1")); - assert!(catalog.items.contains_key("item-2")); - assert!(!catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-1").unwrap()); + assert!(catalog.contains_key("item-2").unwrap()); + assert!(!catalog.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -464,21 +464,21 @@ mod tests { let mut catalog1 = Catalog::new(db_path); let filter1 = Filter::new().with_signed_int("sell_trend", Comparison::Lesser, 10); assert!(catalog1.load_by_filter(&filter1).is_ok()); - assert_eq!(catalog1.items.len(), 1); - assert!(catalog1.items.contains_key("item-2")); + assert_eq!(catalog1.len().unwrap(), 1); + assert!(catalog1.contains_key("item-2").unwrap()); // Edge Case 2: No values lesser than filter value. let mut catalog2 = Catalog::new(db_path); let filter2 = Filter::new().with_signed_int("sell_trend", Comparison::Lesser, -5); assert!(catalog2.load_by_filter(&filter2).is_ok()); - assert!(catalog2.items.is_empty()); + assert!(catalog2.is_empty().unwrap()); // Edge Case 3: All values lesser than filter value. let mut catalog3 = Catalog::new(db_path); let filter3 = Filter::new().with_signed_int("sell_trend", Comparison::Lesser, 25); assert!(catalog3.load_by_filter(&filter3).is_ok()); - assert_eq!(catalog3.items.len(), 3); - assert!(catalog3.items.contains_key("item-1")); - assert!(catalog3.items.contains_key("item-2")); - assert!(catalog3.items.contains_key("item-3")); + assert_eq!(catalog3.len().unwrap(), 3); + assert!(catalog3.contains_key("item-1").unwrap()); + assert!(catalog3.contains_key("item-2").unwrap()); + assert!(catalog3.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -522,10 +522,10 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_signed_int("sell_trend", Comparison::GreaterOrEqual, 10); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-1")); - assert!(!catalog.items.contains_key("item-2")); - assert!(catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-1").unwrap()); + assert!(!catalog.contains_key("item-2").unwrap()); + assert!(catalog.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -570,21 +570,21 @@ mod tests { let mut catalog1 = Catalog::new(db_path); let filter1 = Filter::new().with_signed_int("sell_trend", Comparison::GreaterOrEqual, 20); assert!(catalog1.load_by_filter(&filter1).is_ok()); - assert_eq!(catalog1.items.len(), 1); - assert!(catalog1.items.contains_key("item-3")); + assert_eq!(catalog1.len().unwrap(), 1); + assert!(catalog1.contains_key("item-3").unwrap()); // Edge Case 2: No values greater than or equal to filter value. let mut catalog2 = Catalog::new(db_path); let filter2 = Filter::new().with_signed_int("sell_trend", Comparison::GreaterOrEqual, 21); assert!(catalog2.load_by_filter(&filter2).is_ok()); - assert!(catalog2.items.is_empty()); + assert!(catalog2.is_empty().unwrap()); // Edge Case 3: All values greater than or equal to filter value. let mut catalog3 = Catalog::new(db_path); let filter3 = Filter::new().with_signed_int("sell_trend", Comparison::GreaterOrEqual, -5); assert!(catalog3.load_by_filter(&filter3).is_ok()); - assert_eq!(catalog3.items.len(), 3); - assert!(catalog3.items.contains_key("item-1")); - assert!(catalog3.items.contains_key("item-2")); - assert!(catalog3.items.contains_key("item-3")); + assert_eq!(catalog3.len().unwrap(), 3); + assert!(catalog3.contains_key("item-1").unwrap()); + assert!(catalog3.contains_key("item-2").unwrap()); + assert!(catalog3.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -628,10 +628,10 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_signed_int("sell_trend", Comparison::LesserOrEqual, 10); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-1")); - assert!(catalog.items.contains_key("item-2")); - assert!(!catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-1").unwrap()); + assert!(catalog.contains_key("item-2").unwrap()); + assert!(!catalog.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -676,21 +676,21 @@ mod tests { let mut catalog1 = Catalog::new(db_path); let filter1 = Filter::new().with_signed_int("sell_trend", Comparison::LesserOrEqual, -5); assert!(catalog1.load_by_filter(&filter1).is_ok()); - assert_eq!(catalog1.items.len(), 1); - assert!(catalog1.items.contains_key("item-2")); + assert_eq!(catalog1.len().unwrap(), 1); + assert!(catalog1.contains_key("item-2").unwrap()); // Edge Case 2: No values lesser than or equal to filter value. let mut catalog2 = Catalog::new(db_path); let filter2 = Filter::new().with_signed_int("sell_trend", Comparison::LesserOrEqual, -6); assert!(catalog2.load_by_filter(&filter2).is_ok()); - assert!(catalog2.items.is_empty()); + assert!(catalog2.is_empty().unwrap()); // Edge Case 3: All values lesser than or equal to filter value. let mut catalog3 = Catalog::new(db_path); let filter3 = Filter::new().with_signed_int("sell_trend", Comparison::LesserOrEqual, 20); assert!(catalog3.load_by_filter(&filter3).is_ok()); - assert_eq!(catalog3.items.len(), 3); - assert!(catalog3.items.contains_key("item-1")); - assert!(catalog3.items.contains_key("item-2")); - assert!(catalog3.items.contains_key("item-3")); + assert_eq!(catalog3.len().unwrap(), 3); + assert!(catalog3.contains_key("item-1").unwrap()); + assert!(catalog3.contains_key("item-2").unwrap()); + assert!(catalog3.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -735,44 +735,44 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_unsigned_int("price", Comparison::Equal, 200); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-2")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-2").unwrap()); // Greater let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_unsigned_int("price", Comparison::Greater, 200); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-3").unwrap()); // Lesser let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_unsigned_int("price", Comparison::Lesser, 200); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-1")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-1").unwrap()); // GreaterOrEqual let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_unsigned_int("price", Comparison::GreaterOrEqual, 200); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-2")); - assert!(catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-2").unwrap()); + assert!(catalog.contains_key("item-3").unwrap()); // LesserOrEqual let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_unsigned_int("price", Comparison::LesserOrEqual, 200); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-1")); - assert!(catalog.items.contains_key("item-2")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-1").unwrap()); + assert!(catalog.contains_key("item-2").unwrap()); // Edge case: No match let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_unsigned_int("price", Comparison::Equal, 400); assert!(catalog.load_by_filter(&filter).is_ok()); - assert!(catalog.items.is_empty()); + assert!(catalog.is_empty().unwrap()); // Edge case: All match let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_unsigned_int("price", Comparison::GreaterOrEqual, 100); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 3); + assert_eq!(catalog.len().unwrap(), 3); std::fs::remove_file(path).unwrap(); } #[test] @@ -817,24 +817,24 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::IsExactly, "Item One"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-1")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-1").unwrap()); // Partial match should not work let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::IsExactly, "Item"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert!(catalog.items.is_empty()); + assert!(catalog.is_empty().unwrap()); // Case insensitive match let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::IsExactly, "item one"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-1")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-1").unwrap()); // No match let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::IsExactly, "Item Four"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert!(catalog.items.is_empty()); + assert!(catalog.is_empty().unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -879,34 +879,34 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::StartsWith, "Item"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-1")); - assert!(catalog.items.contains_key("item-2")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-1").unwrap()); + assert!(catalog.contains_key("item-2").unwrap()); // Starts with "I" let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::StartsWith, "I"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-1")); - assert!(catalog.items.contains_key("item-2")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-1").unwrap()); + assert!(catalog.contains_key("item-2").unwrap()); // No match let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::StartsWith, "Z"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert!(catalog.items.is_empty()); + assert!(catalog.is_empty().unwrap()); // Case insensitive let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::StartsWith, "item"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-1")); - assert!(catalog.items.contains_key("item-2")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-1").unwrap()); + assert!(catalog.contains_key("item-2").unwrap()); // Full string match let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::StartsWith, "Item One"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-1")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-1").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -951,25 +951,25 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::EndsWith, "one"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-1")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-1").unwrap()); // Ends with "item" - case insensitive let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::EndsWith, "item"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-3").unwrap()); // No match let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::EndsWith, "Z"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert!(catalog.items.is_empty()); + assert!(catalog.is_empty().unwrap()); // Full string match - case insensitive let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::EndsWith, "item one"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-1")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-1").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -1014,24 +1014,24 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::Contains, "item"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 3); + assert_eq!(catalog.len().unwrap(), 3); // Contains "THE" - case insensitive let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::Contains, "THE"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-3").unwrap()); // No match let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::Contains, "Z"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert!(catalog.items.is_empty()); + assert!(catalog.is_empty().unwrap()); // Full string match - case insensitive let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::Contains, "item one"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-1")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-1").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -1066,10 +1066,10 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_real("discount", Comparison::Equal, 0.10); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-1")); - assert!(catalog.items.contains_key("item-3")); - assert!(!catalog.items.contains_key("item-2")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-1").unwrap()); + assert!(catalog.contains_key("item-3").unwrap()); + assert!(!catalog.contains_key("item-2").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -1104,9 +1104,9 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_real("discount", Comparison::Greater, 0.15); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-2")); - assert!(catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-2").unwrap()); + assert!(catalog.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -1136,12 +1136,12 @@ mod tests { let mut catalog1 = Catalog::new(db_path); let filter1 = Filter::new().with_real("discount", Comparison::Greater, 0.10); assert!(catalog1.load_by_filter(&filter1).is_ok()); - assert_eq!(catalog1.items.len(), 1); - assert!(catalog1.items.contains_key("item-2")); + assert_eq!(catalog1.len().unwrap(), 1); + assert!(catalog1.contains_key("item-2").unwrap()); let mut catalog2 = Catalog::new(db_path); let filter2 = Filter::new().with_real("discount", Comparison::Greater, 0.20); assert!(catalog2.load_by_filter(&filter2).is_ok()); - assert!(catalog2.items.is_empty()); + assert!(catalog2.is_empty().unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -1176,9 +1176,9 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_real("discount", Comparison::Lesser, 0.15); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-1")); - assert!(catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-1").unwrap()); + assert!(catalog.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -1208,12 +1208,12 @@ mod tests { let mut catalog1 = Catalog::new(db_path); let filter1 = Filter::new().with_real("discount", Comparison::Lesser, 0.20); assert!(catalog1.load_by_filter(&filter1).is_ok()); - assert_eq!(catalog1.items.len(), 1); - assert!(catalog1.items.contains_key("item-1")); + assert_eq!(catalog1.len().unwrap(), 1); + assert!(catalog1.contains_key("item-1").unwrap()); let mut catalog2 = Catalog::new(db_path); let filter2 = Filter::new().with_real("discount", Comparison::Lesser, 0.10); assert!(catalog2.load_by_filter(&filter2).is_ok()); - assert!(catalog2.items.is_empty()); + assert!(catalog2.is_empty().unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -1248,9 +1248,9 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_real("discount", Comparison::GreaterOrEqual, 0.15); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-2")); - assert!(catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-2").unwrap()); + assert!(catalog.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -1280,12 +1280,12 @@ mod tests { let mut catalog1 = Catalog::new(db_path); let filter1 = Filter::new().with_real("discount", Comparison::GreaterOrEqual, 0.20); assert!(catalog1.load_by_filter(&filter1).is_ok()); - assert_eq!(catalog1.items.len(), 1); - assert!(catalog1.items.contains_key("item-2")); + assert_eq!(catalog1.len().unwrap(), 1); + assert!(catalog1.contains_key("item-2").unwrap()); let mut catalog2 = Catalog::new(db_path); let filter2 = Filter::new().with_real("discount", Comparison::GreaterOrEqual, 0.21); assert!(catalog2.load_by_filter(&filter2).is_ok()); - assert!(catalog2.items.is_empty()); + assert!(catalog2.is_empty().unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -1320,9 +1320,9 @@ mod tests { let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_real("discount", Comparison::LesserOrEqual, 0.15); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 2); - assert!(catalog.items.contains_key("item-1")); - assert!(catalog.items.contains_key("item-3")); + assert_eq!(catalog.len().unwrap(), 2); + assert!(catalog.contains_key("item-1").unwrap()); + assert!(catalog.contains_key("item-3").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -1352,12 +1352,12 @@ mod tests { let mut catalog1 = Catalog::new(db_path); let filter1 = Filter::new().with_real("discount", Comparison::LesserOrEqual, 0.10); assert!(catalog1.load_by_filter(&filter1).is_ok()); - assert_eq!(catalog1.items.len(), 1); - assert!(catalog1.items.contains_key("item-1")); + assert_eq!(catalog1.len().unwrap(), 1); + assert!(catalog1.contains_key("item-1").unwrap()); let mut catalog2 = Catalog::new(db_path); let filter2 = Filter::new().with_real("discount", Comparison::LesserOrEqual, 0.09); assert!(catalog2.load_by_filter(&filter2).is_ok()); - assert!(catalog2.items.is_empty()); + assert!(catalog2.is_empty().unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -1408,8 +1408,8 @@ mod tests { .with_real("discount", Comparison::Equal, 0.10) .with_bool("in_stock", true); assert!(catalog.load_by_filter(&filter).is_ok()); - assert_eq!(catalog.items.len(), 1); - assert!(catalog.items.contains_key("item-1")); + assert_eq!(catalog.len().unwrap(), 1); + assert!(catalog.contains_key("item-1").unwrap()); std::fs::remove_file(path).unwrap(); } #[test] @@ -1478,30 +1478,30 @@ mod tests { let mut catalog1 = Catalog::new(db_path); let filter1 = Filter::new().with_class("item"); assert!(catalog1.load_by_filter(&filter1).is_ok()); - assert_eq!(catalog1.items.len(), 4); - assert!(catalog1.items.contains_key("item-1")); - assert!(catalog1.items.contains_key("item-2")); - assert!(catalog1.items.contains_key("item-3")); - assert!(catalog1.items.contains_key("item-4")); + assert_eq!(catalog1.len().unwrap(), 4); + assert!(catalog1.contains_key("item-1").unwrap()); + assert!(catalog1.contains_key("item-2").unwrap()); + assert!(catalog1.contains_key("item-3").unwrap()); + assert!(catalog1.contains_key("item-4").unwrap()); // Test 2: Filter by class "another_item" let mut catalog2 = Catalog::new(db_path); let filter2 = Filter::new().with_class("another_item"); assert!(catalog2.load_by_filter(&filter2).is_ok()); - assert_eq!(catalog2.items.len(), 1); - assert!(catalog2.items.contains_key("another-1")); + assert_eq!(catalog2.len().unwrap(), 1); + assert!(catalog2.contains_key("another-1").unwrap()); // Test 3: Filter by class "item" and subclass "sub-a" let mut catalog3 = Catalog::new(db_path); let filter3 = Filter::new().with_class("item").with_subclass("sub-a"); assert!(catalog3.load_by_filter(&filter3).is_ok()); - assert_eq!(catalog3.items.len(), 2); - assert!(catalog3.items.contains_key("item-1")); - assert!(catalog3.items.contains_key("item-3")); + assert_eq!(catalog3.len().unwrap(), 2); + assert!(catalog3.contains_key("item-1").unwrap()); + assert!(catalog3.contains_key("item-3").unwrap()); // Test 4: Filter by class "item" and subclass "subitem" (the default) let mut catalog4 = Catalog::new(db_path); let filter4 = Filter::new().with_class("item").with_subclass("subitem"); assert!(catalog4.load_by_filter(&filter4).is_ok()); - assert_eq!(catalog4.items.len(), 1); - assert!(catalog4.items.contains_key("item-4")); + assert_eq!(catalog4.len().unwrap(), 1); + assert!(catalog4.contains_key("item-4").unwrap()); std::fs::remove_file(path).unwrap(); } } diff --git a/01.workspace/heave/src/tst/catalog_load_by_id.rs b/01.workspace/heave/src/tst/catalog_load_by_id.rs index ba8c2f9..0bdd85a 100644 --- a/01.workspace/heave/src/tst/catalog_load_by_id.rs +++ b/01.workspace/heave/src/tst/catalog_load_by_id.rs @@ -26,13 +26,16 @@ mod tests { catalog1.persist().unwrap(); // 2. Create a new, empty catalog instance for the same DB. let mut catalog2 = Catalog::new(db_path); - assert!(catalog2.items.is_empty()); + assert!(catalog2.is_empty().unwrap()); // 3. Load the item by its ID. let result = catalog2.load_by_id("item-1"); assert!(result.is_ok()); // 4. Verify the item is now in the in-memory 'items' map. - assert_eq!(catalog2.items.len(), 1); - let loaded_entity = catalog2.items.get("item-1").unwrap(); + let len = catalog2.len().unwrap(); + assert_eq!(len, 1); + let loaded_entity = catalog2 + .with_items(|items| Ok(items.get("item-1").unwrap().clone())) + .unwrap(); // 5. Verify the loaded entity's data and state. assert_eq!(loaded_entity.id, "item-1"); assert_eq!(loaded_entity.class, "item"); @@ -88,7 +91,9 @@ mod tests { ..Item::default() }; let _ = catalog2.upsert(item_in_memory); - let entity_before_load = catalog2.items.get("item-1").unwrap(); + let entity_before_load = catalog2 + .with_items(|items| Ok(items.get("item-1").unwrap().clone())) + .unwrap(); assert_eq!(entity_before_load.state, EntityState::New); assert_eq!( entity_before_load.value_of("name"), @@ -98,7 +103,9 @@ mod tests { let result = catalog2.load_by_id("item-1"); assert!(result.is_ok()); // 4. Verify that the in-memory entity has been replaced with the one from the DB. - let entity_after_load = catalog2.items.get("item-1").unwrap(); + let entity_after_load = catalog2 + .with_items(|items| Ok(items.get("item-1").unwrap().clone())) + .unwrap(); assert_eq!(entity_after_load.state, EntityState::Loaded); assert_eq!( entity_after_load.value_of("name"), @@ -134,7 +141,7 @@ mod tests { let result = catalog.load_by_id("nonexistent-id"); // 3. Verify that the operation succeeded and the catalog remains empty. assert!(result.is_ok()); - assert!(catalog.items.is_empty()); + assert!(catalog.is_empty().unwrap()); // Clean up std::fs::remove_file(path).unwrap(); } diff --git a/01.workspace/heave/src/tst/catalog_new.rs b/01.workspace/heave/src/tst/catalog_new.rs index 9881e3f..5f8ad25 100644 --- a/01.workspace/heave/src/tst/catalog_new.rs +++ b/01.workspace/heave/src/tst/catalog_new.rs @@ -7,6 +7,6 @@ mod tests { let path = "test.db"; let catalog = Catalog::new(path); assert_eq!(catalog.path, path); - assert!(catalog.items.is_empty()); + assert!(catalog.is_empty().unwrap()); } } diff --git a/01.workspace/heave/src/tst/catalog_persist.rs b/01.workspace/heave/src/tst/catalog_persist.rs index fe411c6..c164f93 100644 --- a/01.workspace/heave/src/tst/catalog_persist.rs +++ b/01.workspace/heave/src/tst/catalog_persist.rs @@ -105,7 +105,9 @@ mod tests { }; let _ = catalog2.upsert(updated_item.clone()); assert_eq!( - catalog2.items.get("item-1").unwrap().state, + catalog2 + .with_items(|items| { Ok(items.get("item-1").unwrap().state) }) + .unwrap(), EntityState::Updated ); // 4. Persist the changes. @@ -197,7 +199,7 @@ mod tests { let mut catalog_verify = Catalog::new(db_path); catalog_verify.load_by_class::().unwrap(); // Check total count - assert_eq!(catalog_verify.items.len(), 3); + assert_eq!(catalog_verify.len().unwrap(), 3); // Verify added item let added_item: Item = catalog_verify.get("add-me").unwrap().unwrap(); assert_eq!(added_item, item_to_add); @@ -277,17 +279,23 @@ mod tests { let _ = catalog.upsert(item_untouched.clone()); catalog.persist().unwrap(); // At this point, all items are in the DB and in-memory state is `Loaded`. - assert_eq!(catalog.items.len(), 3); + assert_eq!(catalog.len().unwrap(), 3); assert_eq!( - catalog.items.get("update-me").unwrap().state, + catalog + .with_items(|items| { Ok(items.get("update-me").unwrap().state) }) + .unwrap(), EntityState::Loaded ); assert_eq!( - catalog.items.get("delete-me").unwrap().state, + catalog + .with_items(|items| { Ok(items.get("delete-me").unwrap().state) }) + .unwrap(), EntityState::Loaded ); assert_eq!( - catalog.items.get("keep-me").unwrap().state, + catalog + .with_items(|items| { Ok(items.get("keep-me").unwrap().state) }) + .unwrap(), EntityState::Loaded ); // 2. Manipulate the catalog to have entities in various states. @@ -315,34 +323,49 @@ mod tests { catalog.delete("delete-me"); // State: ToDelete // 'item_untouched' remains with state `Loaded`. // Check states before final persist - assert_eq!(catalog.items.get("add-me").unwrap().state, EntityState::New); assert_eq!( - catalog.items.get("update-me").unwrap().state, + catalog + .with_items(|items| { Ok(items.get("add-me").unwrap().state) }) + .unwrap(), + EntityState::New + ); + assert_eq!( + catalog + .with_items(|items| { Ok(items.get("update-me").unwrap().state) }) + .unwrap(), EntityState::Updated ); assert_eq!( - catalog.items.get("delete-me").unwrap().state, + catalog + .with_items(|items| { Ok(items.get("delete-me").unwrap().state) }) + .unwrap(), EntityState::ToDelete ); assert_eq!( - catalog.items.get("keep-me").unwrap().state, + catalog + .with_items(|items| { Ok(items.get("keep-me").unwrap().state) }) + .unwrap(), EntityState::Loaded ); - assert_eq!(catalog.items.len(), 4); + assert_eq!(catalog.len().unwrap(), 4); // 3. Persist all changes. catalog.persist().unwrap(); // 4. Verify the in-memory state after persisting. // The item marked for deletion should be gone. - assert!(!catalog.items.contains_key("delete-me")); - assert_eq!(catalog.items.len(), 3); + assert!(!catalog.contains_key("delete-me").unwrap()); + assert_eq!(catalog.len().unwrap(), 3); // All remaining items should have their state as `Loaded`. - let new_item_entity = catalog.items.get("add-me").unwrap(); + let new_item_entity = catalog + .with_items(|items| Ok(items.get("add-me").unwrap().clone())) + .unwrap(); assert_eq!(new_item_entity.state, EntityState::Loaded); assert_eq!( new_item_entity.value_of("name"), Some(&Value::from("Add Me")) ); - let updated_item_entity = catalog.items.get("update-me").unwrap(); + let updated_item_entity = catalog + .with_items(|items| Ok(items.get("update-me").unwrap().clone())) + .unwrap(); assert_eq!(updated_item_entity.state, EntityState::Loaded); assert_eq!( updated_item_entity.value_of("name"), @@ -352,7 +375,9 @@ mod tests { updated_item_entity.value_of("sell_trend"), Some(&Value::from(10i64)) ); - let untouched_item_entity = catalog.items.get("keep-me").unwrap(); + let untouched_item_entity = catalog + .with_items(|items| Ok(items.get("keep-me").unwrap().clone())) + .unwrap(); assert_eq!(untouched_item_entity.state, EntityState::Loaded); assert_eq!( untouched_item_entity.value_of("name"), diff --git a/01.workspace/heave/src/tst/catalog_upsert.rs b/01.workspace/heave/src/tst/catalog_upsert.rs index 0e4f01f..3569a00 100644 --- a/01.workspace/heave/src/tst/catalog_upsert.rs +++ b/01.workspace/heave/src/tst/catalog_upsert.rs @@ -15,7 +15,9 @@ mod tests { }; let item_id = item.id.clone(); let _ = catalog.upsert(item); - let entity = catalog.items.get(&item_id).unwrap(); + let entity = catalog + .with_items(|items| Ok(items.get(&item_id).unwrap().clone())) + .unwrap(); assert_eq!(entity.id, item_id); assert_eq!(entity.state, EntityState::New); assert_eq!(entity.class, "item"); @@ -47,8 +49,10 @@ mod tests { ..Item::default() }; let _ = catalog.upsert(item2); - assert_eq!(catalog.items.len(), 1); - let entity = catalog.items.get(&item_id).unwrap(); + assert_eq!(catalog.len().unwrap(), 1); + let entity = catalog + .with_items(|items| Ok(items.get(&item_id).unwrap().clone())) + .unwrap(); assert_eq!(entity.value_of("name"), Some(&Value::from("Second Item"))); assert_eq!(entity.value_of("price"), Some(&Value::from(200u64))); assert_eq!(entity.value_of("sell_trend"), Some(&Value::from(10i64))); diff --git a/01.workspace/heave/src/tst/sqlite_persist_catalog.rs b/01.workspace/heave/src/tst/sqlite_persist_catalog.rs index e012f03..02be38b 100644 --- a/01.workspace/heave/src/tst/sqlite_persist_catalog.rs +++ b/01.workspace/heave/src/tst/sqlite_persist_catalog.rs @@ -22,7 +22,7 @@ mod tests { // Verifies that entities marked as 'New' in the catalog are inserted into the database, along with all their attributes. let db_path = Path::new("test_insert.db"); let conn = setup_db(db_path); - let mut catalog = Catalog::new(db_path.to_str().unwrap()); + let catalog = Catalog::new(db_path.to_str().unwrap()); let mut entity = Entity { id: "e1".to_string(), class: "c1".to_string(), @@ -38,8 +38,14 @@ mod tests { value: Value::Text("v1".to_string()), }, ); - catalog.items.insert("e1".to_string(), entity); - assert!(sqlite::persist::catalog(db_path, &catalog).is_ok()); + let _ = catalog.on_items(|items| { + items.insert("e1".to_string(), entity); + Ok(()) + }); + let is_ok = catalog + .with_items(|items| Ok(sqlite::persist::catalog(db_path, items).is_ok())) + .unwrap(); + assert!(is_ok); assert_eq!(count_rows(&conn, "entity", "id = 'e1'"), 1); assert_eq!(count_rows(&conn, "attribute", "entity_id = 'e1'"), 1); fs::remove_file(db_path).unwrap(); @@ -51,7 +57,7 @@ mod tests { let conn = setup_db(db_path); conn.execute("INSERT INTO entity (id, class) VALUES ('e1', 'c1')", []) .unwrap(); - let mut catalog = Catalog::new(db_path.to_str().unwrap()); + let catalog = Catalog::new(db_path.to_str().unwrap()); let entity = Entity { id: "e1".to_string(), class: "c1".to_string(), @@ -60,8 +66,14 @@ mod tests { state: EntityState::ToDelete, ref_date: None, }; - catalog.items.insert("e1".to_string(), entity); - assert!(sqlite::persist::catalog(db_path, &catalog).is_ok()); + let _ = catalog.on_items(|items| { + items.insert("e1".to_string(), entity); + Ok(()) + }); + let is_ok = catalog + .with_items(|items| Ok(sqlite::persist::catalog(db_path, items).is_ok())) + .unwrap(); + assert!(is_ok); assert_eq!(count_rows(&conn, "entity", "id = 'e1'"), 0); fs::remove_file(db_path).unwrap(); } @@ -72,7 +84,7 @@ mod tests { let conn = setup_db(db_path); conn.execute("INSERT INTO entity (id, class) VALUES ('e1', 'c1')", []) .unwrap(); - let mut catalog = Catalog::new(db_path.to_str().unwrap()); + let catalog = Catalog::new(db_path.to_str().unwrap()); let to_delete = Entity { id: "e1".to_string(), class: "c1".to_string(), @@ -89,9 +101,18 @@ mod tests { state: EntityState::New, ref_date: None, }; - catalog.items.insert("e1".to_string(), to_delete); - catalog.items.insert("e2".to_string(), to_add); - assert!(sqlite::persist::catalog(db_path, &catalog).is_ok()); + let _ = catalog.on_items(|items| { + items.insert("e1".to_string(), to_delete); + Ok(()) + }); + let _ = catalog.on_items(|items| { + items.insert("e2".to_string(), to_add); + Ok(()) + }); + let is_ok = catalog + .with_items(|items| Ok(sqlite::persist::catalog(db_path, items).is_ok())) + .unwrap(); + assert!(is_ok); assert_eq!(count_rows(&conn, "entity", "id = 'e1'"), 0); assert_eq!(count_rows(&conn, "entity", "id = 'e2'"), 1); fs::remove_file(db_path).unwrap(); @@ -103,7 +124,7 @@ mod tests { let conn = setup_db(db_path); conn.execute("INSERT INTO entity (id, class) VALUES ('e1', 'c1')", []) .unwrap(); - let mut catalog = Catalog::new(db_path.to_str().unwrap()); + let catalog = Catalog::new(db_path.to_str().unwrap()); let unmodified = Entity { id: "e1".to_string(), class: "c1".to_string(), @@ -112,8 +133,14 @@ mod tests { state: EntityState::Loaded, ref_date: None, }; - catalog.items.insert("e1".to_string(), unmodified); - assert!(sqlite::persist::catalog(db_path, &catalog).is_ok()); + let _ = catalog.on_items(|items| { + items.insert("e1".to_string(), unmodified); + Ok(()) + }); + let is_ok = catalog + .with_items(|items| Ok(sqlite::persist::catalog(db_path, items).is_ok())) + .unwrap(); + assert!(is_ok); assert_eq!(count_rows(&conn, "entity", "id = 'e1'"), 1); fs::remove_file(db_path).unwrap(); } @@ -127,7 +154,7 @@ mod tests { [], ) .unwrap(); - let mut catalog = Catalog::new(db_path.to_str().unwrap()); + let catalog = Catalog::new(db_path.to_str().unwrap()); let mut new_entity = Entity { id: "e_new".to_string(), class: "c1".to_string(), @@ -143,12 +170,17 @@ mod tests { value: Value::Text("v1".to_string()), }, ); - catalog.items.insert("e_new".to_string(), new_entity); + let _ = catalog.on_items(|items| { + items.insert("e_new".to_string(), new_entity); + Ok(()) + }); // Corrupt the DB to cause a failure during the transaction conn.execute("DROP TABLE attribute", []).unwrap(); drop(conn); - let result = sqlite::persist::catalog(db_path, &catalog); - assert!(result.is_err()); + let is_err = catalog + .with_items(|items| Ok(sqlite::persist::catalog(db_path, items).is_err())) + .unwrap(); + assert!(is_err); // Re-open connection to check state let conn = Connection::open(db_path).unwrap(); // The new entity should not have been inserted due to rollback