feat: add load_by_filter to catalog with possibility to filter by bool attribute
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
|
pub mod sqlite_build_params;
|
||||||
|
pub mod sqlite_build_statement;
|
||||||
pub mod sqlite_init_db;
|
pub mod sqlite_init_db;
|
||||||
pub mod sqlite_load_attributes;
|
pub mod sqlite_load_attributes;
|
||||||
pub mod sqlite_load_by_class;
|
pub mod sqlite_load_by_class;
|
||||||
|
pub mod sqlite_load_by_filter;
|
||||||
pub mod sqlite_load_by_id;
|
pub mod sqlite_load_by_id;
|
||||||
pub mod sqlite_map_row_to_attribute;
|
pub mod sqlite_map_row_to_attribute;
|
||||||
pub mod sqlite_map_row_to_entity;
|
pub mod sqlite_map_row_to_entity;
|
||||||
|
|||||||
55
01.workspace/heave/src/fun/sqlite_build_params.rs
Normal file
55
01.workspace/heave/src/fun/sqlite_build_params.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use crate::*;
|
||||||
|
use rusqlite::*;
|
||||||
|
|
||||||
|
pub fn run(filter: &Filter) -> Result<Vec<Box<dyn ToSql>>, FailedTo> {
|
||||||
|
let mut params: Vec<Box<dyn ToSql>> = Vec::new();
|
||||||
|
for condition in filter.conditions() {
|
||||||
|
match condition.1 {
|
||||||
|
Condition::Bool(value) => params.push(Box::new(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Ensures that an empty vector is returned when the filter has no conditions.
|
||||||
|
#[test]
|
||||||
|
fn returns_empty_vec_for_empty_filter() {
|
||||||
|
let filter = Filter::new();
|
||||||
|
let params = run(&filter).unwrap();
|
||||||
|
assert!(params.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifies that a single boolean condition is correctly converted to a ToSql parameter.
|
||||||
|
#[test]
|
||||||
|
fn returns_params_for_one_bool_condition() {
|
||||||
|
let filter = Filter::new().with_bool("is_active", true);
|
||||||
|
let params = run(&filter).unwrap();
|
||||||
|
assert_eq!(params.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
params[0].to_sql().unwrap(),
|
||||||
|
rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Integer(1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks that multiple boolean conditions are correctly converted and ordered.
|
||||||
|
#[test]
|
||||||
|
fn returns_params_for_multiple_bool_conditions() {
|
||||||
|
let filter = Filter::new()
|
||||||
|
.with_bool("is_active", true)
|
||||||
|
.with_bool("is_deleted", false);
|
||||||
|
let params = run(&filter).unwrap();
|
||||||
|
assert_eq!(params.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
params[0].to_sql().unwrap(),
|
||||||
|
rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Integer(1))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
params[1].to_sql().unwrap(),
|
||||||
|
rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Integer(0))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
01.workspace/heave/src/fun/sqlite_build_statement.rs
Normal file
53
01.workspace/heave/src/fun/sqlite_build_statement.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
use crate::*;
|
||||||
|
|
||||||
|
const BASE_SELECT: &str = r#"SELECT * FROM entity WHERE 1=1"#;
|
||||||
|
|
||||||
|
pub fn run(filter: &Filter) -> Result<String, FailedTo> {
|
||||||
|
let mut statement = String::from(BASE_SELECT);
|
||||||
|
for (i, (name, condition)) in filter.conditions().enumerate() {
|
||||||
|
let fragment = match *condition {
|
||||||
|
Condition::Bool(_) => format!(
|
||||||
|
" AND id IN (SELECT entity_id FROM attribute WHERE id = '{}' AND value_bool = ?{})",
|
||||||
|
name,
|
||||||
|
i + 1
|
||||||
|
),
|
||||||
|
};
|
||||||
|
statement.push_str(&fragment);
|
||||||
|
}
|
||||||
|
Ok(statement)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::Filter;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builds_statement_with_no_conditions() {
|
||||||
|
let filter = Filter::new();
|
||||||
|
let statement = run(&filter).unwrap();
|
||||||
|
assert_eq!(statement, BASE_SELECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builds_statement_with_one_bool_condition() {
|
||||||
|
let filter = Filter::new().with_bool("is_active", true);
|
||||||
|
let statement = run(&filter).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
statement,
|
||||||
|
"SELECT * FROM entity WHERE 1=1 AND id IN (SELECT entity_id FROM attribute WHERE id = 'is_active' AND value_bool = ?1)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builds_statement_with_multiple_bool_conditions() {
|
||||||
|
let filter = Filter::new()
|
||||||
|
.with_bool("is_active", true)
|
||||||
|
.with_bool("is_deleted", false);
|
||||||
|
let statement = run(&filter).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
statement,
|
||||||
|
"SELECT * FROM entity WHERE 1=1 AND id IN (SELECT entity_id FROM attribute WHERE id = 'is_active' AND value_bool = ?1) AND id IN (SELECT entity_id FROM attribute WHERE id = 'is_deleted' AND value_bool = ?2)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
121
01.workspace/heave/src/fun/sqlite_load_by_filter.rs
Normal file
121
01.workspace/heave/src/fun/sqlite_load_by_filter.rs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
use crate::*;
|
||||||
|
use rusqlite::*;
|
||||||
|
|
||||||
|
pub fn run(path: &path::Path, filter: &Filter) -> Result<Vec<Entity>, FailedTo> {
|
||||||
|
let mut entities = Vec::<Entity>::new();
|
||||||
|
let mut connection = Connection::open(path).map_err(|_| sqlite::FailedTo::OpenConnection)?;
|
||||||
|
let mut transaction = connection
|
||||||
|
.transaction()
|
||||||
|
.map_err(|_| sqlite::FailedTo::BeginTransaction)?;
|
||||||
|
transaction.set_drop_behavior(DropBehavior::Commit);
|
||||||
|
let select_entity_by_filter = sqlite::build::statement(filter)?;
|
||||||
|
let params = sqlite::build::params(filter)?;
|
||||||
|
let params: Vec<&dyn ToSql> = params.iter().map(|p| p.as_ref() as &dyn ToSql).collect();
|
||||||
|
let mut statement = transaction
|
||||||
|
.prepare(&select_entity_by_filter)
|
||||||
|
.map_err(|_| sqlite::FailedTo::PrepareStatement)?;
|
||||||
|
let result = statement
|
||||||
|
.query_map(¶ms[..], sqlite::map::row_to_entity)
|
||||||
|
.map_err(|_| sqlite::FailedTo::ExecuteQuery)?;
|
||||||
|
for entity in result {
|
||||||
|
let mut entity = entity.map_err(|_| FailedTo::MapEntity)?;
|
||||||
|
sqlite::load::attributes(&transaction, &mut entity)?;
|
||||||
|
entity.state = EntityState::Loaded;
|
||||||
|
entities.push(entity);
|
||||||
|
}
|
||||||
|
Ok(entities)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{Filter, fun};
|
||||||
|
use std::{fs, path::Path};
|
||||||
|
fn setup_db(db_path: &Path) {
|
||||||
|
let _ = fs::remove_file(db_path);
|
||||||
|
fun::sqlite_init_db::run(db_path).unwrap();
|
||||||
|
let conn = Connection::open(db_path).unwrap();
|
||||||
|
conn.execute("INSERT INTO entity (id, class) VALUES ('e1', 'c1')", [])
|
||||||
|
.unwrap();
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO attribute (id, entity_id, value_bool) VALUES ('active', 'e1', 1)",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
conn.execute("INSERT INTO entity (id, class) VALUES ('e2', 'c1')", [])
|
||||||
|
.unwrap();
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO attribute (id, entity_id, value_bool) VALUES ('active', 'e2', 0)",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
conn.execute("INSERT INTO entity (id, class) VALUES ('e3', 'c2')", [])
|
||||||
|
.unwrap();
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO attribute (id, entity_id, value_bool) VALUES ('active', 'e3', 1)",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO attribute (id, entity_id, value_bool) VALUES ('deleted', 'e3', 1)",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
// Verifies that entities matching the filter criteria are correctly loaded.
|
||||||
|
#[test]
|
||||||
|
fn loads_entities_with_matching_filter() {
|
||||||
|
let db_path = Path::new("test_load_matching.db");
|
||||||
|
setup_db(db_path);
|
||||||
|
let filter = Filter::new().with_bool("active", true);
|
||||||
|
let entities = run(db_path, &filter).unwrap();
|
||||||
|
assert_eq!(entities.len(), 2);
|
||||||
|
let ids: Vec<_> = entities.iter().map(|e| e.id.clone()).collect();
|
||||||
|
assert!(ids.contains(&"e1".to_string()));
|
||||||
|
assert!(ids.contains(&"e3".to_string()));
|
||||||
|
fs::remove_file(db_path).unwrap();
|
||||||
|
}
|
||||||
|
// Ensures that an empty vector is returned when no entities match the filter.
|
||||||
|
#[test]
|
||||||
|
fn returns_empty_vec_for_no_matching_entities() {
|
||||||
|
let db_path = Path::new("test_load_no_matching.db");
|
||||||
|
setup_db(db_path);
|
||||||
|
let filter = Filter::new().with_bool("non_existent_attr", true);
|
||||||
|
let entities = run(db_path, &filter).unwrap();
|
||||||
|
assert!(entities.is_empty());
|
||||||
|
fs::remove_file(db_path).unwrap();
|
||||||
|
}
|
||||||
|
// Tests that all entities are returned when an empty filter is provided.
|
||||||
|
#[test]
|
||||||
|
fn handles_empty_filter() {
|
||||||
|
let db_path = Path::new("test_load_empty_filter.db");
|
||||||
|
setup_db(db_path);
|
||||||
|
let filter = Filter::new();
|
||||||
|
let entities = run(db_path, &filter).unwrap();
|
||||||
|
assert_eq!(entities.len(), 3);
|
||||||
|
fs::remove_file(db_path).unwrap();
|
||||||
|
}
|
||||||
|
// Checks that the function returns an appropriate error on a database connection failure.
|
||||||
|
#[test]
|
||||||
|
fn fails_gracefully_on_db_error() {
|
||||||
|
let invalid_path = Path::new("non_existing/path");
|
||||||
|
let filter = Filter::new();
|
||||||
|
let result = run(invalid_path, &filter);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
// Confirms that loaded entities include all their associated attributes.
|
||||||
|
#[test]
|
||||||
|
fn fully_loads_entities_with_attributes() {
|
||||||
|
let db_path = Path::new("test_load_fully.db");
|
||||||
|
setup_db(db_path);
|
||||||
|
let filter = Filter::new().with_bool("deleted", true);
|
||||||
|
let entities = run(db_path, &filter).unwrap();
|
||||||
|
assert_eq!(entities.len(), 1);
|
||||||
|
let entity = &entities[0];
|
||||||
|
assert_eq!(entity.id, "e3");
|
||||||
|
assert_eq!(entity.attributes.len(), 2);
|
||||||
|
assert!(entity.attributes.contains_key("active"));
|
||||||
|
assert!(entity.attributes.contains_key("deleted"));
|
||||||
|
fs::remove_file(db_path).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,20 +9,27 @@ mod tst;
|
|||||||
|
|
||||||
pub(crate) use crate::str::attribute::O as Attribute;
|
pub(crate) use crate::str::attribute::O as Attribute;
|
||||||
pub use crate::str::catalog::O as Catalog;
|
pub use crate::str::catalog::O as Catalog;
|
||||||
|
pub use crate::str::condition::E as Condition;
|
||||||
pub use crate::str::entity::O as Entity;
|
pub use crate::str::entity::O as Entity;
|
||||||
pub use crate::str::entity_state::EntityState;
|
pub use crate::str::entity_state::EntityState;
|
||||||
pub use crate::str::failed_to::FailedTo;
|
pub use crate::str::failed_to::FailedTo;
|
||||||
|
pub use crate::str::filter::O as Filter;
|
||||||
pub(crate) use crate::str::value::Value;
|
pub(crate) use crate::str::value::Value;
|
||||||
pub use crate::trt::eav::T as EAV;
|
pub use crate::trt::eav::T as EAV;
|
||||||
|
|
||||||
mod sqlite {
|
mod sqlite {
|
||||||
pub use crate::str::sqlite_failed_to::FailedTo;
|
pub use crate::str::sqlite_failed_to::FailedTo;
|
||||||
|
pub mod build {
|
||||||
|
pub use crate::fun::sqlite_build_params::run as params;
|
||||||
|
pub use crate::fun::sqlite_build_statement::run as statement;
|
||||||
|
}
|
||||||
pub mod init {
|
pub mod init {
|
||||||
pub use crate::fun::sqlite_init_db::run as db;
|
pub use crate::fun::sqlite_init_db::run as db;
|
||||||
}
|
}
|
||||||
pub mod load {
|
pub mod load {
|
||||||
pub use crate::fun::sqlite_load_attributes::run as attributes;
|
pub use crate::fun::sqlite_load_attributes::run as attributes;
|
||||||
pub use crate::fun::sqlite_load_by_class::run as by_class;
|
pub use crate::fun::sqlite_load_by_class::run as by_class;
|
||||||
|
pub use crate::fun::sqlite_load_by_filter::run as by_filter;
|
||||||
pub use crate::fun::sqlite_load_by_id::run as by_id;
|
pub use crate::fun::sqlite_load_by_id::run as by_id;
|
||||||
}
|
}
|
||||||
pub mod map {
|
pub mod map {
|
||||||
|
|||||||
@@ -213,6 +213,15 @@ impl Catalog {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -260,7 +269,6 @@ mod tests {
|
|||||||
assert_eq!(catalog.path, path);
|
assert_eq!(catalog.path, path);
|
||||||
assert!(catalog.items.is_empty());
|
assert!(catalog.items.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ## 'init()'
|
// ## 'init()'
|
||||||
#[test]
|
#[test]
|
||||||
fn init_should_create_db_file_if_not_exists() {
|
fn init_should_create_db_file_if_not_exists() {
|
||||||
@@ -280,7 +288,6 @@ mod tests {
|
|||||||
// Clean up the created file
|
// Clean up the created file
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn init_should_not_fail_if_db_file_exists() {
|
fn init_should_not_fail_if_db_file_exists() {
|
||||||
// Should not fail if the database file already exists.
|
// Should not fail if the database file already exists.
|
||||||
@@ -298,7 +305,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn init_should_return_error_for_invalid_path() {
|
fn init_should_return_error_for_invalid_path() {
|
||||||
// Should return an error for an invalid path or permissions issue.
|
// Should return an error for an invalid path or permissions issue.
|
||||||
@@ -312,7 +318,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_dir_all(invalid_path).unwrap();
|
std::fs::remove_dir_all(invalid_path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ## 'upsert()' & 'insert_many()'
|
// ## 'upsert()' & 'insert_many()'
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_should_add_single_entity_as_new() {
|
fn insert_should_add_single_entity_as_new() {
|
||||||
@@ -334,7 +339,6 @@ mod tests {
|
|||||||
assert_eq!(entity.value_of("price"), Some(&Value::from(100u64)));
|
assert_eq!(entity.value_of("price"), Some(&Value::from(100u64)));
|
||||||
assert_eq!(entity.value_of("in_stock"), Some(&Value::from(true)));
|
assert_eq!(entity.value_of("in_stock"), Some(&Value::from(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_should_overwrite_existing_entity() {
|
fn insert_should_overwrite_existing_entity() {
|
||||||
// 'upsert()': Should overwrite an existing entity with the same ID.
|
// 'upsert()': Should overwrite an existing entity with the same ID.
|
||||||
@@ -361,7 +365,6 @@ mod tests {
|
|||||||
assert_eq!(entity.value_of("in_stock"), Some(&Value::from(false)));
|
assert_eq!(entity.value_of("in_stock"), Some(&Value::from(false)));
|
||||||
assert_eq!(entity.state, EntityState::Updated);
|
assert_eq!(entity.state, EntityState::Updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_many_should_add_all_entities() {
|
fn insert_many_should_add_all_entities() {
|
||||||
// 'insert_many()': Should add all provided entities to the 'items' map.
|
// 'insert_many()': Should add all provided entities to the 'items' map.
|
||||||
@@ -389,7 +392,6 @@ mod tests {
|
|||||||
assert_eq!(entity2.state, EntityState::New);
|
assert_eq!(entity2.state, EntityState::New);
|
||||||
assert_eq!(entity2.value_of("name"), Some(&Value::from("Item 2")));
|
assert_eq!(entity2.value_of("name"), Some(&Value::from("Item 2")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ## 'get()'
|
// ## 'get()'
|
||||||
#[test]
|
#[test]
|
||||||
fn get_should_retrieve_and_convert_entity_by_id() {
|
fn get_should_retrieve_and_convert_entity_by_id() {
|
||||||
@@ -405,7 +407,6 @@ mod tests {
|
|||||||
let retrieved_item: Option<Item> = catalog.get::<Item>("item-123").unwrap();
|
let retrieved_item: Option<Item> = catalog.get::<Item>("item-123").unwrap();
|
||||||
assert_eq!(retrieved_item, Some(item));
|
assert_eq!(retrieved_item, Some(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_should_return_none_for_nonexistent_id() {
|
fn get_should_return_none_for_nonexistent_id() {
|
||||||
// Should return 'None' if the ID does not exist.
|
// Should return 'None' if the ID does not exist.
|
||||||
@@ -420,7 +421,6 @@ mod tests {
|
|||||||
let retrieved_item: Option<Item> = catalog.get("nonexistent-id").unwrap();
|
let retrieved_item: Option<Item> = catalog.get("nonexistent-id").unwrap();
|
||||||
assert!(retrieved_item.is_none());
|
assert!(retrieved_item.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ## 'get_by_class_and_attribute()'
|
// ## 'get_by_class_and_attribute()'
|
||||||
#[test]
|
#[test]
|
||||||
fn get_by_class_and_attribute_should_retrieve_correct_entity() {
|
fn get_by_class_and_attribute_should_retrieve_correct_entity() {
|
||||||
@@ -445,7 +445,6 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(retrieved_item, Some(item2));
|
assert_eq!(retrieved_item, Some(item2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_by_class_and_attribute_should_work_with_different_value_types() {
|
fn get_by_class_and_attribute_should_work_with_different_value_types() {
|
||||||
// Should work with different value types (String, u64, bool).
|
// Should work with different value types (String, u64, bool).
|
||||||
@@ -479,7 +478,6 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(retrieved_by_stock, Some(item1.clone()));
|
assert_eq!(retrieved_by_stock, Some(item1.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_by_class_and_attribute_should_return_none_if_no_match() {
|
fn get_by_class_and_attribute_should_return_none_if_no_match() {
|
||||||
// Should return 'None' if no entity matches the criteria.
|
// Should return 'None' if no entity matches the criteria.
|
||||||
@@ -502,7 +500,6 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(retrieved_item_2.is_none());
|
assert!(retrieved_item_2.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ## 'list_by_class()'
|
// ## 'list_by_class()'
|
||||||
#[test]
|
#[test]
|
||||||
fn list_by_class_should_return_all_entities_of_class() {
|
fn list_by_class_should_return_all_entities_of_class() {
|
||||||
@@ -530,7 +527,6 @@ mod tests {
|
|||||||
assert!(results.contains(&item1));
|
assert!(results.contains(&item1));
|
||||||
assert!(results.contains(&item2));
|
assert!(results.contains(&item2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_by_class_should_return_empty_iterator_if_no_match() {
|
fn list_by_class_should_return_empty_iterator_if_no_match() {
|
||||||
// Should return an empty iterator if no entities of that class exist.
|
// Should return an empty iterator if no entities of that class exist.
|
||||||
@@ -541,7 +537,6 @@ mod tests {
|
|||||||
.collect();
|
.collect();
|
||||||
assert!(results.is_empty());
|
assert!(results.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ## 'list_by_class_and_attribute()'
|
// ## 'list_by_class_and_attribute()'
|
||||||
#[test]
|
#[test]
|
||||||
fn list_by_class_and_attribute_should_return_all_matching_entities() {
|
fn list_by_class_and_attribute_should_return_all_matching_entities() {
|
||||||
@@ -577,7 +572,6 @@ mod tests {
|
|||||||
assert!(results.contains(&item3));
|
assert!(results.contains(&item3));
|
||||||
assert!(!results.contains(&item2));
|
assert!(!results.contains(&item2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_by_class_and_attribute_should_return_empty_iterator_if_no_match() {
|
fn list_by_class_and_attribute_should_return_empty_iterator_if_no_match() {
|
||||||
// Should return an empty iterator if no entities match.
|
// Should return an empty iterator if no entities match.
|
||||||
@@ -609,7 +603,6 @@ mod tests {
|
|||||||
.collect();
|
.collect();
|
||||||
assert!(results_3.is_empty());
|
assert!(results_3.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ## 'delete()'
|
// ## 'delete()'
|
||||||
#[test]
|
#[test]
|
||||||
fn delete_should_mark_entity_as_to_delete() {
|
fn delete_should_mark_entity_as_to_delete() {
|
||||||
@@ -627,7 +620,6 @@ mod tests {
|
|||||||
let entity = catalog.items.get(&item_id).unwrap();
|
let entity = catalog.items.get(&item_id).unwrap();
|
||||||
assert_eq!(entity.state, EntityState::ToDelete);
|
assert_eq!(entity.state, EntityState::ToDelete);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delete_should_have_no_effect_for_nonexistent_id() {
|
fn delete_should_have_no_effect_for_nonexistent_id() {
|
||||||
// Should have no effect if the entity ID does not exist.
|
// Should have no effect if the entity ID does not exist.
|
||||||
@@ -644,7 +636,6 @@ mod tests {
|
|||||||
catalog.delete("nonexistent-id");
|
catalog.delete("nonexistent-id");
|
||||||
assert_eq!(catalog.items, original_items);
|
assert_eq!(catalog.items, original_items);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ## 'persist()'
|
// ## 'persist()'
|
||||||
#[test]
|
#[test]
|
||||||
fn persist_should_insert_new_entities() {
|
fn persist_should_insert_new_entities() {
|
||||||
@@ -675,7 +666,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn persist_should_delete_to_delete_entities() {
|
fn persist_should_delete_to_delete_entities() {
|
||||||
// Should delete entities with 'EntityState::ToDelete' from the database.
|
// Should delete entities with 'EntityState::ToDelete' from the database.
|
||||||
@@ -708,7 +698,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn persist_should_update_updated_entities() {
|
fn persist_should_update_updated_entities() {
|
||||||
// Should update entities with 'EntityState::Updated' in the database.
|
// Should update entities with 'EntityState::Updated' in the database.
|
||||||
@@ -756,7 +745,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn persist_should_handle_mixed_entity_states() {
|
fn persist_should_handle_mixed_entity_states() {
|
||||||
// Should handle a mix of new, updated, and deleted entities in one operation.
|
// Should handle a mix of new, updated, and deleted entities in one operation.
|
||||||
@@ -835,7 +823,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn persist_should_return_error_on_db_failure() {
|
fn persist_should_return_error_on_db_failure() {
|
||||||
// Should return an error if the database connection fails or a query fails.
|
// Should return an error if the database connection fails or a query fails.
|
||||||
@@ -856,7 +843,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_dir_all(invalid_path).unwrap();
|
std::fs::remove_dir_all(invalid_path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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.
|
// After persisting, the in-memory state of entities should be considered.
|
||||||
@@ -964,7 +950,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ## 'load_by_id()'
|
// ## 'load_by_id()'
|
||||||
#[test]
|
#[test]
|
||||||
fn load_by_id_should_load_entity_from_db() {
|
fn load_by_id_should_load_entity_from_db() {
|
||||||
@@ -1011,7 +996,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_by_id_should_overwrite_in_memory_entity() {
|
fn load_by_id_should_overwrite_in_memory_entity() {
|
||||||
// Should overwrite an existing in-memory entity with the same ID.
|
// Should overwrite an existing in-memory entity with the same ID.
|
||||||
@@ -1067,7 +1051,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_by_id_should_do_nothing_if_not_found() {
|
fn load_by_id_should_do_nothing_if_not_found() {
|
||||||
// Should do nothing if the entity is not found in the database.
|
// Should do nothing if the entity is not found in the database.
|
||||||
@@ -1088,7 +1071,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_by_id_should_return_error_on_db_failure() {
|
fn load_by_id_should_return_error_on_db_failure() {
|
||||||
// Should return an error if the database operation fails.
|
// Should return an error if the database operation fails.
|
||||||
@@ -1103,7 +1085,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_dir_all(invalid_path).unwrap();
|
std::fs::remove_dir_all(invalid_path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ## 'load_by_class()'
|
// ## 'load_by_class()'
|
||||||
#[test]
|
#[test]
|
||||||
fn load_by_class_should_load_all_entities_of_class() {
|
fn load_by_class_should_load_all_entities_of_class() {
|
||||||
@@ -1153,7 +1134,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_by_class_should_overwrite_in_memory_entities() {
|
fn load_by_class_should_overwrite_in_memory_entities() {
|
||||||
// Should overwrite any existing in-memory entities with the same IDs.
|
// Should overwrite any existing in-memory entities with the same IDs.
|
||||||
@@ -1202,7 +1182,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_by_class_should_do_nothing_if_none_found() {
|
fn load_by_class_should_do_nothing_if_none_found() {
|
||||||
// Should do nothing if no entities of that class are found in the database.
|
// Should do nothing if no entities of that class are found in the database.
|
||||||
@@ -1237,7 +1216,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_by_class_should_return_error_on_db_failure() {
|
fn load_by_class_should_return_error_on_db_failure() {
|
||||||
// Should return an error if the database operation fails.
|
// Should return an error if the database operation fails.
|
||||||
@@ -1254,7 +1232,161 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_dir_all(invalid_path).unwrap();
|
std::fs::remove_dir_all(invalid_path).unwrap();
|
||||||
}
|
}
|
||||||
|
// ## 'load_by_filter()'
|
||||||
|
#[test]
|
||||||
|
fn load_by_filter_should_load_matching_entities() {
|
||||||
|
// Verifies that entities matching a boolean filter are correctly loaded into the catalog.
|
||||||
|
let db_path = "target/test_dbs/lbf_should_load_matching_entities.db";
|
||||||
|
let path = std::path::Path::new(db_path);
|
||||||
|
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||||
|
if path.exists() {
|
||||||
|
std::fs::remove_file(path).unwrap();
|
||||||
|
}
|
||||||
|
let mut catalog_setup = Catalog::new(db_path);
|
||||||
|
catalog_setup.init().unwrap();
|
||||||
|
let items = vec![
|
||||||
|
Item {
|
||||||
|
id: "item-1".to_string(),
|
||||||
|
name: "Item One".to_string(),
|
||||||
|
price: 100,
|
||||||
|
in_stock: true,
|
||||||
|
},
|
||||||
|
Item {
|
||||||
|
id: "item-2".to_string(),
|
||||||
|
name: "Item Two".to_string(),
|
||||||
|
price: 200,
|
||||||
|
in_stock: false,
|
||||||
|
},
|
||||||
|
Item {
|
||||||
|
id: "item-3".to_string(),
|
||||||
|
name: "Item Three".to_string(),
|
||||||
|
price: 300,
|
||||||
|
in_stock: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
catalog_setup.insert_many(items).unwrap();
|
||||||
|
catalog_setup.persist().unwrap();
|
||||||
|
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"));
|
||||||
|
std::fs::remove_file(path).unwrap();
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn load_by_filter_should_not_load_non_matching_entities() {
|
||||||
|
// Ensures that entities that do not match the filter are not loaded.
|
||||||
|
let db_path = "target/test_dbs/lbf_should_not_load_non_matching_entities.db";
|
||||||
|
let path = std::path::Path::new(db_path);
|
||||||
|
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||||
|
if path.exists() {
|
||||||
|
std::fs::remove_file(path).unwrap();
|
||||||
|
}
|
||||||
|
let mut catalog_setup = Catalog::new(db_path);
|
||||||
|
catalog_setup.init().unwrap();
|
||||||
|
let items = vec![
|
||||||
|
Item {
|
||||||
|
id: "item-1".to_string(),
|
||||||
|
name: "Item One".to_string(),
|
||||||
|
price: 100,
|
||||||
|
in_stock: true,
|
||||||
|
},
|
||||||
|
Item {
|
||||||
|
id: "item-2".to_string(),
|
||||||
|
name: "Item Two".to_string(),
|
||||||
|
price: 200,
|
||||||
|
in_stock: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
catalog_setup.insert_many(items).unwrap();
|
||||||
|
catalog_setup.persist().unwrap();
|
||||||
|
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"));
|
||||||
|
std::fs::remove_file(path).unwrap();
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn load_by_filter_with_empty_filter_should_load_all_entities() {
|
||||||
|
// Tests that using an empty filter loads all entities from the database.
|
||||||
|
let db_path = "target/test_dbs/lbf_with_empty_filter_should_load_all_entities.db";
|
||||||
|
let path = std::path::Path::new(db_path);
|
||||||
|
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||||
|
if path.exists() {
|
||||||
|
std::fs::remove_file(path).unwrap();
|
||||||
|
}
|
||||||
|
let mut catalog_setup = Catalog::new(db_path);
|
||||||
|
catalog_setup.init().unwrap();
|
||||||
|
let items = vec![
|
||||||
|
Item {
|
||||||
|
id: "item-1".to_string(),
|
||||||
|
name: "Item One".to_string(),
|
||||||
|
price: 100,
|
||||||
|
in_stock: true,
|
||||||
|
},
|
||||||
|
Item {
|
||||||
|
id: "item-2".to_string(),
|
||||||
|
name: "Item Two".to_string(),
|
||||||
|
price: 200,
|
||||||
|
in_stock: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
catalog_setup.insert_many(items).unwrap();
|
||||||
|
catalog_setup.persist().unwrap();
|
||||||
|
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);
|
||||||
|
std::fs::remove_file(path).unwrap();
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn load_by_filter_should_overwrite_existing_entities() {
|
||||||
|
// Checks that loading from the database overwrites any existing in-memory entities with the same ID.
|
||||||
|
let db_path = "target/test_dbs/lbf_should_overwrite_existing_entities.db";
|
||||||
|
let path = std::path::Path::new(db_path);
|
||||||
|
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||||
|
if path.exists() {
|
||||||
|
std::fs::remove_file(path).unwrap();
|
||||||
|
}
|
||||||
|
let mut catalog_setup = Catalog::new(db_path);
|
||||||
|
catalog_setup.init().unwrap();
|
||||||
|
let item_in_db = Item {
|
||||||
|
id: "item-1".to_string(),
|
||||||
|
name: "DB Version".to_string(),
|
||||||
|
price: 100,
|
||||||
|
in_stock: true,
|
||||||
|
};
|
||||||
|
catalog_setup.upsert(item_in_db.clone()).unwrap();
|
||||||
|
catalog_setup.persist().unwrap();
|
||||||
|
let mut catalog = Catalog::new(db_path);
|
||||||
|
let item_in_memory = Item {
|
||||||
|
id: "item-1".to_string(),
|
||||||
|
name: "Memory Version".to_string(),
|
||||||
|
price: 200,
|
||||||
|
in_stock: false,
|
||||||
|
};
|
||||||
|
catalog.upsert(item_in_memory).unwrap();
|
||||||
|
let filter = Filter::new().with_bool("in_stock", true);
|
||||||
|
assert!(catalog.load_by_filter(&filter).is_ok());
|
||||||
|
let loaded_item: Item = catalog.get("item-1").unwrap().unwrap();
|
||||||
|
assert_eq!(loaded_item, item_in_db);
|
||||||
|
std::fs::remove_file(path).unwrap();
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn load_by_filter_should_return_error_on_db_failure() {
|
||||||
|
// Confirms that the function returns an appropriate error if the database operation fails.
|
||||||
|
let invalid_path = "target/test_dbs/a_directory_for_lbf_fail";
|
||||||
|
std::fs::create_dir_all(invalid_path).unwrap();
|
||||||
|
let mut catalog = Catalog::new(invalid_path);
|
||||||
|
let filter = Filter::new();
|
||||||
|
let result = catalog.load_by_filter(&filter);
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(result.unwrap_err(), FailedTo::LoadFromDB);
|
||||||
|
std::fs::remove_dir_all(invalid_path).unwrap();
|
||||||
|
}
|
||||||
// ## Integration Tests
|
// ## Integration Tests
|
||||||
#[test]
|
#[test]
|
||||||
fn integration_test_init_insert_persist_load_get() {
|
fn integration_test_init_insert_persist_load_get() {
|
||||||
@@ -1285,7 +1417,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn integration_test_init_insert_many_persist_load_list() {
|
fn integration_test_init_insert_many_persist_load_list() {
|
||||||
// Scenario: 'init' -> 'insert_many' -> 'persist' -> new catalog -> 'load_by_class' -> 'list_by_class' -> verify all items are loaded.
|
// Scenario: 'init' -> 'insert_many' -> 'persist' -> new catalog -> 'load_by_class' -> 'list_by_class' -> verify all items are loaded.
|
||||||
@@ -1331,7 +1462,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn integration_test_insert_persist_load_delete_persist_load() {
|
fn integration_test_insert_persist_load_delete_persist_load() {
|
||||||
// Scenario: 'insert' -> 'persist' -> 'load_by_id' -> 'delete' -> 'persist' -> 'load_by_id' should now return nothing for the deleted ID.
|
// Scenario: 'insert' -> 'persist' -> 'load_by_id' -> 'delete' -> 'persist' -> 'load_by_id' should now return nothing for the deleted ID.
|
||||||
@@ -1367,7 +1497,6 @@ mod tests {
|
|||||||
// Clean up
|
// Clean up
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn integration_test_insert_get_by_class_and_attribute() {
|
fn integration_test_insert_get_by_class_and_attribute() {
|
||||||
// Scenario: 'insert' -> 'get_by_class_and_attribute' should return the correct item.
|
// Scenario: 'insert' -> 'get_by_class_and_attribute' should return the correct item.
|
||||||
@@ -1394,7 +1523,6 @@ mod tests {
|
|||||||
// Verify the correct item was retrieved
|
// Verify the correct item was retrieved
|
||||||
assert_eq!(retrieved_item, Some(item2));
|
assert_eq!(retrieved_item, Some(item2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn integration_test_insert_many_list_by_class_and_attribute() {
|
fn integration_test_insert_many_list_by_class_and_attribute() {
|
||||||
// Scenario: 'insert_many' -> 'list_by_class_and_attribute' should return all matching items.
|
// Scenario: 'insert_many' -> 'list_by_class_and_attribute' should return all matching items.
|
||||||
@@ -1432,7 +1560,6 @@ mod tests {
|
|||||||
assert_eq!(results.len(), 2);
|
assert_eq!(results.len(), 2);
|
||||||
assert_eq!(results, expected);
|
assert_eq!(results, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore] // This test can be flaky as it depends on thread scheduling.
|
#[ignore] // This test can be flaky as it depends on thread scheduling.
|
||||||
fn integration_test_concurrency() {
|
fn integration_test_concurrency() {
|
||||||
|
|||||||
4
01.workspace/heave/src/str/condition.rs
Normal file
4
01.workspace/heave/src/str/condition.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Hash)]
|
||||||
|
pub enum E {
|
||||||
|
Bool(bool),
|
||||||
|
}
|
||||||
22
01.workspace/heave/src/str/filter.rs
Normal file
22
01.workspace/heave/src/str/filter.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq, Clone)]
|
||||||
|
pub struct O {
|
||||||
|
conditions: Vec<(String, Condition)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Filter {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
conditions: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn with_bool(mut self, attribute_name: &str, value: bool) -> Self {
|
||||||
|
self.conditions
|
||||||
|
.push((attribute_name.to_string(), Condition::Bool(value)));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub(crate) fn conditions(&self) -> impl Iterator<Item = &(String, Condition)> {
|
||||||
|
self.conditions.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
pub mod attribute;
|
pub mod attribute;
|
||||||
pub mod catalog;
|
pub mod catalog;
|
||||||
|
pub mod condition;
|
||||||
pub mod entity;
|
pub mod entity;
|
||||||
pub mod entity_state;
|
pub mod entity_state;
|
||||||
pub mod failed_to;
|
pub mod failed_to;
|
||||||
|
pub mod filter;
|
||||||
pub mod sqlite_failed_to;
|
pub mod sqlite_failed_to;
|
||||||
pub mod value;
|
pub mod value;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Hash)]
|
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Hash)]
|
||||||
pub enum FailedTo {
|
pub enum FailedTo {
|
||||||
BeginTransaction,
|
BeginTransaction,
|
||||||
|
BuildStatement,
|
||||||
CommitTransaction,
|
CommitTransaction,
|
||||||
ExecuteBatch,
|
ExecuteBatch,
|
||||||
ExecuteQuery,
|
ExecuteQuery,
|
||||||
|
|||||||
Reference in New Issue
Block a user