feat: add load_by_filter to catalog with possibility to filter by bool attribute

This commit is contained in:
2025-10-18 08:31:51 +02:00
parent c865c72c01
commit 20a68aa018
10 changed files with 432 additions and 37 deletions

View File

@@ -1,6 +1,9 @@
pub mod sqlite_build_params;
pub mod sqlite_build_statement;
pub mod sqlite_init_db;
pub mod sqlite_load_attributes;
pub mod sqlite_load_by_class;
pub mod sqlite_load_by_filter;
pub mod sqlite_load_by_id;
pub mod sqlite_map_row_to_attribute;
pub mod sqlite_map_row_to_entity;

View 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))
);
}
}

View 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)"
);
}
}

View 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(&params[..], 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();
}
}

View File

@@ -9,20 +9,27 @@ mod tst;
pub(crate) use crate::str::attribute::O as Attribute;
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_state::EntityState;
pub use crate::str::failed_to::FailedTo;
pub use crate::str::filter::O as Filter;
pub(crate) use crate::str::value::Value;
pub use crate::trt::eav::T as EAV;
mod sqlite {
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 use crate::fun::sqlite_init_db::run as db;
}
pub mod load {
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_filter::run as by_filter;
pub use crate::fun::sqlite_load_by_id::run as by_id;
}
pub mod map {

View File

@@ -213,6 +213,15 @@ impl Catalog {
}
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)]
@@ -260,7 +269,6 @@ mod tests {
assert_eq!(catalog.path, path);
assert!(catalog.items.is_empty());
}
// ## 'init()'
#[test]
fn init_should_create_db_file_if_not_exists() {
@@ -280,7 +288,6 @@ mod tests {
// Clean up the created file
std::fs::remove_file(path).unwrap();
}
#[test]
fn init_should_not_fail_if_db_file_exists() {
// Should not fail if the database file already exists.
@@ -298,7 +305,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
fn init_should_return_error_for_invalid_path() {
// Should return an error for an invalid path or permissions issue.
@@ -312,7 +318,6 @@ mod tests {
// Clean up
std::fs::remove_dir_all(invalid_path).unwrap();
}
// ## 'upsert()' & 'insert_many()'
#[test]
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("in_stock"), Some(&Value::from(true)));
}
#[test]
fn insert_should_overwrite_existing_entity() {
// '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.state, EntityState::Updated);
}
#[test]
fn insert_many_should_add_all_entities() {
// '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.value_of("name"), Some(&Value::from("Item 2")));
}
// ## 'get()'
#[test]
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();
assert_eq!(retrieved_item, Some(item));
}
#[test]
fn get_should_return_none_for_nonexistent_id() {
// 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();
assert!(retrieved_item.is_none());
}
// ## 'get_by_class_and_attribute()'
#[test]
fn get_by_class_and_attribute_should_retrieve_correct_entity() {
@@ -445,7 +445,6 @@ mod tests {
.unwrap();
assert_eq!(retrieved_item, Some(item2));
}
#[test]
fn get_by_class_and_attribute_should_work_with_different_value_types() {
// Should work with different value types (String, u64, bool).
@@ -479,7 +478,6 @@ mod tests {
.unwrap();
assert_eq!(retrieved_by_stock, Some(item1.clone()));
}
#[test]
fn get_by_class_and_attribute_should_return_none_if_no_match() {
// Should return 'None' if no entity matches the criteria.
@@ -502,7 +500,6 @@ mod tests {
.unwrap();
assert!(retrieved_item_2.is_none());
}
// ## 'list_by_class()'
#[test]
fn list_by_class_should_return_all_entities_of_class() {
@@ -530,7 +527,6 @@ mod tests {
assert!(results.contains(&item1));
assert!(results.contains(&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.
@@ -541,7 +537,6 @@ mod tests {
.collect();
assert!(results.is_empty());
}
// ## 'list_by_class_and_attribute()'
#[test]
fn list_by_class_and_attribute_should_return_all_matching_entities() {
@@ -577,7 +572,6 @@ mod tests {
assert!(results.contains(&item3));
assert!(!results.contains(&item2));
}
#[test]
fn list_by_class_and_attribute_should_return_empty_iterator_if_no_match() {
// Should return an empty iterator if no entities match.
@@ -609,7 +603,6 @@ mod tests {
.collect();
assert!(results_3.is_empty());
}
// ## 'delete()'
#[test]
fn delete_should_mark_entity_as_to_delete() {
@@ -627,7 +620,6 @@ mod tests {
let entity = catalog.items.get(&item_id).unwrap();
assert_eq!(entity.state, EntityState::ToDelete);
}
#[test]
fn delete_should_have_no_effect_for_nonexistent_id() {
// Should have no effect if the entity ID does not exist.
@@ -644,7 +636,6 @@ mod tests {
catalog.delete("nonexistent-id");
assert_eq!(catalog.items, original_items);
}
// ## 'persist()'
#[test]
fn persist_should_insert_new_entities() {
@@ -675,7 +666,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
fn persist_should_delete_to_delete_entities() {
// Should delete entities with 'EntityState::ToDelete' from the database.
@@ -708,7 +698,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
fn persist_should_update_updated_entities() {
// Should update entities with 'EntityState::Updated' in the database.
@@ -756,7 +745,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
fn persist_should_handle_mixed_entity_states() {
// Should handle a mix of new, updated, and deleted entities in one operation.
@@ -835,7 +823,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
fn persist_should_return_error_on_db_failure() {
// Should return an error if the database connection fails or a query fails.
@@ -856,7 +843,6 @@ mod tests {
// Clean up
std::fs::remove_dir_all(invalid_path).unwrap();
}
#[test]
fn persist_should_update_in_memory_state() {
// After persisting, the in-memory state of entities should be considered.
@@ -964,7 +950,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
// ## 'load_by_id()'
#[test]
fn load_by_id_should_load_entity_from_db() {
@@ -1011,7 +996,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
fn load_by_id_should_overwrite_in_memory_entity() {
// Should overwrite an existing in-memory entity with the same ID.
@@ -1067,7 +1051,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
fn load_by_id_should_do_nothing_if_not_found() {
// Should do nothing if the entity is not found in the database.
@@ -1088,7 +1071,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
fn load_by_id_should_return_error_on_db_failure() {
// Should return an error if the database operation fails.
@@ -1103,7 +1085,6 @@ mod tests {
// Clean up
std::fs::remove_dir_all(invalid_path).unwrap();
}
// ## 'load_by_class()'
#[test]
fn load_by_class_should_load_all_entities_of_class() {
@@ -1153,7 +1134,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
fn load_by_class_should_overwrite_in_memory_entities() {
// Should overwrite any existing in-memory entities with the same IDs.
@@ -1202,7 +1182,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
fn load_by_class_should_do_nothing_if_none_found() {
// Should do nothing if no entities of that class are found in the database.
@@ -1237,7 +1216,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
fn load_by_class_should_return_error_on_db_failure() {
// Should return an error if the database operation fails.
@@ -1254,7 +1232,161 @@ mod tests {
// Clean up
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
#[test]
fn integration_test_init_insert_persist_load_get() {
@@ -1285,7 +1417,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
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.
@@ -1331,7 +1462,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
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.
@@ -1367,7 +1497,6 @@ mod tests {
// Clean up
std::fs::remove_file(path).unwrap();
}
#[test]
fn integration_test_insert_get_by_class_and_attribute() {
// Scenario: 'insert' -> 'get_by_class_and_attribute' should return the correct item.
@@ -1394,7 +1523,6 @@ mod tests {
// Verify the correct item was retrieved
assert_eq!(retrieved_item, Some(item2));
}
#[test]
fn integration_test_insert_many_list_by_class_and_attribute() {
// 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, expected);
}
#[test]
#[ignore] // This test can be flaky as it depends on thread scheduling.
fn integration_test_concurrency() {

View File

@@ -0,0 +1,4 @@
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Hash)]
pub enum E {
Bool(bool),
}

View 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()
}
}

View File

@@ -1,7 +1,9 @@
pub mod attribute;
pub mod catalog;
pub mod condition;
pub mod entity;
pub mod entity_state;
pub mod failed_to;
pub mod filter;
pub mod sqlite_failed_to;
pub mod value;

View File

@@ -1,6 +1,7 @@
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Hash)]
pub enum FailedTo {
BeginTransaction,
BuildStatement,
CommitTransaction,
ExecuteBatch,
ExecuteQuery,