feat: catalog state is changed after persist, in memory state reflects db
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user