feat: catalog state is changed after persist, in memory state reflects db

This commit is contained in:
2025-10-17 12:21:40 +02:00
parent ced9998c75
commit 5b0aa07a07

View File

@@ -1,4 +1,5 @@
use crate::*; use crate::*;
use std::collections::HashMap;
/// Represents a catalog of entities that can be persisted to a SQLite database. /// Represents a catalog of entities that can be persisted to a SQLite database.
/// ///
@@ -7,7 +8,7 @@ use crate::*;
#[derive(Debug, Default, PartialEq, Clone)] #[derive(Debug, Default, PartialEq, Clone)]
pub struct O { pub struct O {
path: String, path: String,
pub(crate) items: std::collections::HashMap<String, Entity>, pub(crate) items: HashMap<String, Entity>,
} }
impl Catalog { impl Catalog {
/// Creates a new `Catalog` instance. /// Creates a new `Catalog` instance.
@@ -161,9 +162,23 @@ impl Catalog {
/// ///
/// - new entities will be written onto DB /// - new entities will be written onto DB
/// - marked for delition entities will be deleted /// - marked for delition entities will be deleted
pub fn persist(&self) -> result::Result<(), FailedTo> { pub fn persist(&mut self) -> result::Result<(), FailedTo> {
let path = path::Path::new(&self.path); let path = path::Path::new(&self.path);
sqlite::persist::catalog(path, self).map_err(|_| FailedTo::PersistCatalog)?; 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(()) Ok(())
} }
@@ -823,60 +838,109 @@ mod tests {
#[test] #[test]
fn persist_should_update_in_memory_state() { fn persist_should_update_in_memory_state() {
// After persisting, the in-memory state of entities should be considered. (e.g., should deleted items be removed from the 'items' map?). // After persisting, the in-memory state of entities should be considered.
// This test verifies the CURRENT behavior: in-memory state is NOT updated by `persist(&self)`. // (e.g., should deleted items be removed from the 'items' map, all other items should be marked as Loaded).
let db_path = "target/test_dbs/persist_should_update_in_memory_state.db"; let db_path = "target/test_dbs/persist_should_update_in_memory_state.db";
let path = std::path::Path::new(db_path); let path = std::path::Path::new(db_path);
std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::create_dir_all(path.parent().unwrap()).unwrap();
if path.exists() { if path.exists() {
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }
// 1. Setup: Create a catalog and pre-populate it with some data.
let mut catalog = Catalog::new(db_path); let mut catalog = Catalog::new(db_path);
catalog.init().unwrap(); catalog.init().unwrap();
// 1. Add items to get them into New, Updated, and ToDelete states. let item_to_update = Item {
catalog.upsert(Item { id: "update-me".to_string(),
id: "new".to_string(), name: "Original".to_string(),
..Default::default() ..Default::default()
}); // -> New };
catalog.upsert(Item { let item_to_delete = Item {
id: "update".to_string(), id: "delete-me".to_string(),
name: "Delete Me".to_string(),
..Default::default() ..Default::default()
}); };
catalog.persist().unwrap(); // Persist `update` so it exists in DB for the next step. let item_untouched = Item {
catalog.upsert(Item { id: "keep-me".to_string(),
id: "update".to_string(), name: "Keep Me".to_string(),
..Default::default()
};
catalog.upsert(item_to_update.clone());
catalog.upsert(item_to_delete.clone());
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.items.get("update-me").unwrap().state,
EntityState::Loaded
);
assert_eq!(
catalog.items.get("delete-me").unwrap().state,
EntityState::Loaded
);
assert_eq!(
catalog.items.get("keep-me").unwrap().state,
EntityState::Loaded
);
// 2. Manipulate the catalog to have entities in various states.
// A new item to be inserted.
let item_new = Item {
id: "add-me".to_string(),
name: "Add Me".to_string(),
..Default::default()
};
catalog.upsert(item_new.clone()); // State: New
// An updated version of an existing item.
let item_updated = Item {
id: "update-me".to_string(),
name: "Updated".to_string(), name: "Updated".to_string(),
..Default::default() ..Default::default()
}); // -> Updated };
catalog.upsert(Item { catalog.upsert(item_updated.clone()); // State: Updated
id: "delete".to_string(), // An item to be deleted.
..Default::default() catalog.delete("delete-me"); // State: ToDelete
}); // 'item_untouched' remains with state `Loaded`.
catalog.delete("delete"); // -> ToDelete // Check states before final persist
// 2. Check initial states before the main persist call. assert_eq!(catalog.items.get("add-me").unwrap().state, EntityState::New);
assert_eq!(catalog.items.get("new").unwrap().state, EntityState::New);
assert_eq!( assert_eq!(
catalog.items.get("update").unwrap().state, catalog.items.get("update-me").unwrap().state,
EntityState::Updated EntityState::Updated
); );
assert_eq!( assert_eq!(
catalog.items.get("delete").unwrap().state, catalog.items.get("delete-me").unwrap().state,
EntityState::ToDelete EntityState::ToDelete
); );
assert_eq!(
catalog.items.get("keep-me").unwrap().state,
EntityState::Loaded
);
assert_eq!(catalog.items.len(), 4);
// 3. Persist all changes. // 3. Persist all changes.
catalog.persist().unwrap(); catalog.persist().unwrap();
// 4. Verify that in-memory states have NOT changed, because persist takes &self. // 4. Verify the in-memory state after persisting.
assert_eq!(catalog.items.get("new").unwrap().state, EntityState::New); // The item marked for deletion should be gone.
assert!(!catalog.items.contains_key("delete-me"));
assert_eq!(catalog.items.len(), 3);
// All remaining items should have their state as `Loaded`.
let new_item_entity = catalog.items.get("add-me").unwrap();
assert_eq!(new_item_entity.state, EntityState::Loaded);
assert_eq!( assert_eq!(
catalog.items.get("update").unwrap().state, new_item_entity.value_of("name"),
EntityState::Updated Some(&Value::from("Add Me"))
); );
let updated_item_entity = catalog.items.get("update-me").unwrap();
assert_eq!(updated_item_entity.state, EntityState::Loaded);
assert_eq!( assert_eq!(
catalog.items.get("delete").unwrap().state, updated_item_entity.value_of("name"),
EntityState::ToDelete Some(&Value::from("Updated"))
); );
assert!(catalog.items.contains_key("delete")); // Deleted item is still in memory. let untouched_item_entity = catalog.items.get("keep-me").unwrap();
// Clean up. assert_eq!(untouched_item_entity.state, EntityState::Loaded);
assert_eq!(
untouched_item_entity.value_of("name"),
Some(&Value::from("Keep Me"))
);
// Clean up
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
} }