feat: add class and subclass to filter struct
This commit is contained in:
@@ -50,5 +50,11 @@ pub fn run<'a>(filter: &'a Filter) -> Result<Vec<Box<dyn ToSql + 'a>>, FailedTo>
|
|||||||
_ => return Err(FailedTo::ComposeFilter),
|
_ => return Err(FailedTo::ComposeFilter),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(value) = filter.class() {
|
||||||
|
params.push(Box::new(value));
|
||||||
|
}
|
||||||
|
if let Some(value) = filter.subclass() {
|
||||||
|
params.push(Box::new(value));
|
||||||
|
}
|
||||||
Ok(params)
|
Ok(params)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const INNER_JOIN_FRAGMENT: &str = r#"
|
|||||||
AND attribute_{index}.id = '{attribute_id}'
|
AND attribute_{index}.id = '{attribute_id}'
|
||||||
AND attribute_{index}.{field} {op} ?{index}
|
AND attribute_{index}.{field} {op} ?{index}
|
||||||
"#;
|
"#;
|
||||||
|
const WHERE: &str = r#" WHERE 1=1"#;
|
||||||
|
|
||||||
fn compose_fragment(name: &str, field: &str, op: &str, index: usize) -> String {
|
fn compose_fragment(name: &str, field: &str, op: &str, index: usize) -> String {
|
||||||
INNER_JOIN_FRAGMENT
|
INNER_JOIN_FRAGMENT
|
||||||
@@ -16,76 +17,98 @@ fn compose_fragment(name: &str, field: &str, op: &str, index: usize) -> String {
|
|||||||
.replace("{index}", &index.to_string())
|
.replace("{index}", &index.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_condition(
|
||||||
|
i: usize,
|
||||||
|
name: &str,
|
||||||
|
comparison: &Comparison,
|
||||||
|
condition: &Condition,
|
||||||
|
) -> Result<String, FailedTo> {
|
||||||
|
let fragment = match (comparison, condition) {
|
||||||
|
// BOOL
|
||||||
|
(Comparison::Equal, Condition::Bool(_)) => compose_fragment(name, "value_bool", "=", i + 1),
|
||||||
|
(_, Condition::Bool(_)) => return Err(FailedTo::ComposeFilter),
|
||||||
|
// SIGNED INT
|
||||||
|
(Comparison::Equal, Condition::SignedInt(_)) => {
|
||||||
|
compose_fragment(name, "value_int", "=", i + 1)
|
||||||
|
}
|
||||||
|
(Comparison::Greater, Condition::SignedInt(_)) => {
|
||||||
|
compose_fragment(name, "value_int", ">", i + 1)
|
||||||
|
}
|
||||||
|
(Comparison::Lesser, Condition::SignedInt(_)) => {
|
||||||
|
compose_fragment(name, "value_int", "<", i + 1)
|
||||||
|
}
|
||||||
|
(Comparison::GreaterOrEqual, Condition::SignedInt(_)) => {
|
||||||
|
compose_fragment(name, "value_int", ">=", i + 1)
|
||||||
|
}
|
||||||
|
(Comparison::LesserOrEqual, Condition::SignedInt(_)) => {
|
||||||
|
compose_fragment(name, "value_int", "<=", i + 1)
|
||||||
|
}
|
||||||
|
(_, Condition::SignedInt(_)) => return Err(FailedTo::ComposeFilter),
|
||||||
|
// UNSIGNED INT
|
||||||
|
(Comparison::Equal, Condition::UnsignedInt(_)) => {
|
||||||
|
compose_fragment(name, "value_uint", "=", i + 1)
|
||||||
|
}
|
||||||
|
(Comparison::Greater, Condition::UnsignedInt(_)) => {
|
||||||
|
compose_fragment(name, "value_uint", ">", i + 1)
|
||||||
|
}
|
||||||
|
(Comparison::Lesser, Condition::UnsignedInt(_)) => {
|
||||||
|
compose_fragment(name, "value_uint", "<", i + 1)
|
||||||
|
}
|
||||||
|
(Comparison::GreaterOrEqual, Condition::UnsignedInt(_)) => {
|
||||||
|
compose_fragment(name, "value_uint", ">=", i + 1)
|
||||||
|
}
|
||||||
|
(Comparison::LesserOrEqual, Condition::UnsignedInt(_)) => {
|
||||||
|
compose_fragment(name, "value_uint", "<=", i + 1)
|
||||||
|
}
|
||||||
|
(_, Condition::UnsignedInt(_)) => return Err(FailedTo::ComposeFilter),
|
||||||
|
// REAL
|
||||||
|
(Comparison::Equal, Condition::Real(_)) => compose_fragment(name, "value_real", "=", i + 1),
|
||||||
|
(Comparison::Greater, Condition::Real(_)) => {
|
||||||
|
compose_fragment(name, "value_real", ">", i + 1)
|
||||||
|
}
|
||||||
|
(Comparison::Lesser, Condition::Real(_)) => {
|
||||||
|
compose_fragment(name, "value_real", "<", i + 1)
|
||||||
|
}
|
||||||
|
(Comparison::GreaterOrEqual, Condition::Real(_)) => {
|
||||||
|
compose_fragment(name, "value_real", ">=", i + 1)
|
||||||
|
}
|
||||||
|
(Comparison::LesserOrEqual, Condition::Real(_)) => {
|
||||||
|
compose_fragment(name, "value_real", "<=", i + 1)
|
||||||
|
}
|
||||||
|
(_, Condition::Real(_)) => return Err(FailedTo::ComposeFilter),
|
||||||
|
// TEXT
|
||||||
|
(Comparison::IsExactly, Condition::Text(_)) => {
|
||||||
|
compose_fragment(name, "value_text", "LIKE", i + 1)
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Comparison::StartsWith | Comparison::EndsWith | Comparison::Contains,
|
||||||
|
Condition::Text(_),
|
||||||
|
) => compose_fragment(name, "value_text", "LIKE", i + 1),
|
||||||
|
(_, Condition::Text(_)) => return Err(FailedTo::ComposeFilter),
|
||||||
|
};
|
||||||
|
Ok(fragment)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(filter: &Filter) -> Result<String, FailedTo> {
|
pub fn run(filter: &Filter) -> Result<String, FailedTo> {
|
||||||
|
// base statement
|
||||||
let mut statement = String::from(BASE_SELECT);
|
let mut statement = String::from(BASE_SELECT);
|
||||||
|
let mut idx = 0;
|
||||||
|
// for each condition add an inner join fragment
|
||||||
for (i, (name, comparison, condition)) in filter.conditions().enumerate() {
|
for (i, (name, comparison, condition)) in filter.conditions().enumerate() {
|
||||||
let fragment = match (comparison, condition) {
|
idx = i;
|
||||||
// BOOL
|
let fragment = from_condition(i, name, comparison, condition)?;
|
||||||
(Comparison::Equal, Condition::Bool(_)) => {
|
statement.push_str(&fragment);
|
||||||
compose_fragment(name, "value_bool", "=", i + 1)
|
}
|
||||||
}
|
// add a neutral where condition
|
||||||
(_, Condition::Bool(_)) => return Err(FailedTo::ComposeFilter),
|
statement.push_str(WHERE);
|
||||||
// SIGNED INT
|
if filter.class().is_some() {
|
||||||
(Comparison::Equal, Condition::SignedInt(_)) => {
|
idx += 1;
|
||||||
compose_fragment(name, "value_int", "=", i + 1)
|
let fragment = format!(" AND entity.class = ?{}", idx);
|
||||||
}
|
statement.push_str(&fragment);
|
||||||
(Comparison::Greater, Condition::SignedInt(_)) => {
|
}
|
||||||
compose_fragment(name, "value_int", ">", i + 1)
|
if filter.subclass().is_some() {
|
||||||
}
|
idx += 1;
|
||||||
(Comparison::Lesser, Condition::SignedInt(_)) => {
|
let fragment = format!(" AND entity.subclass = ?{}", idx);
|
||||||
compose_fragment(name, "value_int", "<", i + 1)
|
|
||||||
}
|
|
||||||
(Comparison::GreaterOrEqual, Condition::SignedInt(_)) => {
|
|
||||||
compose_fragment(name, "value_int", ">=", i + 1)
|
|
||||||
}
|
|
||||||
(Comparison::LesserOrEqual, Condition::SignedInt(_)) => {
|
|
||||||
compose_fragment(name, "value_int", "<=", i + 1)
|
|
||||||
}
|
|
||||||
(_, Condition::SignedInt(_)) => return Err(FailedTo::ComposeFilter),
|
|
||||||
// UNSIGNED INT
|
|
||||||
(Comparison::Equal, Condition::UnsignedInt(_)) => {
|
|
||||||
compose_fragment(name, "value_uint", "=", i + 1)
|
|
||||||
}
|
|
||||||
(Comparison::Greater, Condition::UnsignedInt(_)) => {
|
|
||||||
compose_fragment(name, "value_uint", ">", i + 1)
|
|
||||||
}
|
|
||||||
(Comparison::Lesser, Condition::UnsignedInt(_)) => {
|
|
||||||
compose_fragment(name, "value_uint", "<", i + 1)
|
|
||||||
}
|
|
||||||
(Comparison::GreaterOrEqual, Condition::UnsignedInt(_)) => {
|
|
||||||
compose_fragment(name, "value_uint", ">=", i + 1)
|
|
||||||
}
|
|
||||||
(Comparison::LesserOrEqual, Condition::UnsignedInt(_)) => {
|
|
||||||
compose_fragment(name, "value_uint", "<=", i + 1)
|
|
||||||
}
|
|
||||||
(_, Condition::UnsignedInt(_)) => return Err(FailedTo::ComposeFilter),
|
|
||||||
// REAL
|
|
||||||
(Comparison::Equal, Condition::Real(_)) => {
|
|
||||||
compose_fragment(name, "value_real", "=", i + 1)
|
|
||||||
}
|
|
||||||
(Comparison::Greater, Condition::Real(_)) => {
|
|
||||||
compose_fragment(name, "value_real", ">", i + 1)
|
|
||||||
}
|
|
||||||
(Comparison::Lesser, Condition::Real(_)) => {
|
|
||||||
compose_fragment(name, "value_real", "<", i + 1)
|
|
||||||
}
|
|
||||||
(Comparison::GreaterOrEqual, Condition::Real(_)) => {
|
|
||||||
compose_fragment(name, "value_real", ">=", i + 1)
|
|
||||||
}
|
|
||||||
(Comparison::LesserOrEqual, Condition::Real(_)) => {
|
|
||||||
compose_fragment(name, "value_real", "<=", i + 1)
|
|
||||||
}
|
|
||||||
(_, Condition::Real(_)) => return Err(FailedTo::ComposeFilter),
|
|
||||||
// TEXT
|
|
||||||
(Comparison::IsExactly, Condition::Text(_)) => {
|
|
||||||
compose_fragment(name, "value_text", "LIKE", i + 1)
|
|
||||||
}
|
|
||||||
(
|
|
||||||
Comparison::StartsWith | Comparison::EndsWith | Comparison::Contains,
|
|
||||||
Condition::Text(_),
|
|
||||||
) => compose_fragment(name, "value_text", "LIKE", i + 1),
|
|
||||||
(_, Condition::Text(_)) => return Err(FailedTo::ComposeFilter),
|
|
||||||
};
|
|
||||||
statement.push_str(&fragment);
|
statement.push_str(&fragment);
|
||||||
}
|
}
|
||||||
Ok(statement)
|
Ok(statement)
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ const DELETE_ENTITY_STATEMENT: &str = r#"
|
|||||||
"#;
|
"#;
|
||||||
|
|
||||||
const INSERT_ENTITY_STATEMENT: &str = r#"
|
const INSERT_ENTITY_STATEMENT: &str = r#"
|
||||||
INSERT INTO entity (id, class, ref_date)
|
INSERT INTO entity (id, class, subclass, ref_date)
|
||||||
VALUES (?1, ?2, ?3);
|
VALUES (?1, ?2, ?3, ?4);
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
const INSERT_ATTRIBUTE_STATEMENT_TEMPLATE: &str = r#"
|
const INSERT_ATTRIBUTE_STATEMENT_TEMPLATE: &str = r#"
|
||||||
@@ -51,7 +51,7 @@ fn delete_entity(entity: &Entity, transaction: &rusqlite::Transaction) -> Result
|
|||||||
|
|
||||||
fn write_entity(entity: &Entity, transaction: &rusqlite::Transaction) -> Result<(), FailedTo> {
|
fn write_entity(entity: &Entity, transaction: &rusqlite::Transaction) -> Result<(), FailedTo> {
|
||||||
let entity_id = [&entity.id];
|
let entity_id = [&entity.id];
|
||||||
let entity_values = (&entity.id, &entity.class, entity.ref_date);
|
let entity_values = (&entity.id, &entity.class, &entity.subclass, entity.ref_date);
|
||||||
transaction
|
transaction
|
||||||
.execute(DELETE_ENTITY_STATEMENT, entity_id)
|
.execute(DELETE_ENTITY_STATEMENT, entity_id)
|
||||||
.map_err(|_| sqlite::FailedTo::ExecuteStatement)?;
|
.map_err(|_| sqlite::FailedTo::ExecuteStatement)?;
|
||||||
|
|||||||
@@ -369,6 +369,7 @@ mod tests {
|
|||||||
fn from(value: Item) -> Entity {
|
fn from(value: Item) -> Entity {
|
||||||
let mut entity = Entity::new::<Item>()
|
let mut entity = Entity::new::<Item>()
|
||||||
.with_id(&value.id)
|
.with_id(&value.id)
|
||||||
|
.with_subclass("subitem")
|
||||||
.with_attribute("name", value.name)
|
.with_attribute("name", value.name)
|
||||||
.with_attribute("price", value.price)
|
.with_attribute("price", value.price)
|
||||||
.with_attribute("discount", value.discount)
|
.with_attribute("discount", value.discount)
|
||||||
@@ -554,6 +555,7 @@ mod tests {
|
|||||||
let mut catalog = Catalog::new("dummy.db");
|
let mut catalog = Catalog::new("dummy.db");
|
||||||
let item = Item {
|
let item = Item {
|
||||||
id: "item-123".to_string(),
|
id: "item-123".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Test Item".to_string(),
|
name: "Test Item".to_string(),
|
||||||
price: 100,
|
price: 100,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -595,6 +597,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let item2 = Item {
|
let item2 = Item {
|
||||||
id: "item-2".to_string(),
|
id: "item-2".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Unique Item".to_string(),
|
name: "Unique Item".to_string(),
|
||||||
price: 200,
|
price: 200,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -614,6 +617,7 @@ mod tests {
|
|||||||
let mut catalog = Catalog::new("dummy.db");
|
let mut catalog = Catalog::new("dummy.db");
|
||||||
let item1 = Item {
|
let item1 = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item One".to_string(),
|
name: "Item One".to_string(),
|
||||||
price: 100,
|
price: 100,
|
||||||
sell_trend: 10,
|
sell_trend: 10,
|
||||||
@@ -622,6 +626,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let item2 = Item {
|
let item2 = Item {
|
||||||
id: "item-2".to_string(),
|
id: "item-2".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item Two".to_string(),
|
name: "Item Two".to_string(),
|
||||||
price: 250,
|
price: 250,
|
||||||
sell_trend: 20,
|
sell_trend: 20,
|
||||||
@@ -681,6 +686,7 @@ mod tests {
|
|||||||
let mut catalog = Catalog::new("dummy.db");
|
let mut catalog = Catalog::new("dummy.db");
|
||||||
let item1 = Item {
|
let item1 = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item One".to_string(),
|
name: "Item One".to_string(),
|
||||||
price: 100,
|
price: 100,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -689,6 +695,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let item2 = Item {
|
let item2 = Item {
|
||||||
id: "item-2".to_string(),
|
id: "item-2".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item Two".to_string(),
|
name: "Item Two".to_string(),
|
||||||
price: 200,
|
price: 200,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -722,6 +729,7 @@ mod tests {
|
|||||||
let mut catalog = Catalog::new("dummy.db");
|
let mut catalog = Catalog::new("dummy.db");
|
||||||
let item1 = Item {
|
let item1 = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item One".to_string(),
|
name: "Item One".to_string(),
|
||||||
price: 100,
|
price: 100,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -730,6 +738,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let item2 = Item {
|
let item2 = Item {
|
||||||
id: "item-2".to_string(),
|
id: "item-2".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item Two".to_string(),
|
name: "Item Two".to_string(),
|
||||||
price: 200,
|
price: 200,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -738,6 +747,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let item3 = Item {
|
let item3 = Item {
|
||||||
id: "item-3".to_string(),
|
id: "item-3".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item Three".to_string(),
|
name: "Item Three".to_string(),
|
||||||
price: 300,
|
price: 300,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -841,6 +851,7 @@ mod tests {
|
|||||||
catalog1.init().unwrap();
|
catalog1.init().unwrap();
|
||||||
let item1 = Item {
|
let item1 = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Test Item".to_string(),
|
name: "Test Item".to_string(),
|
||||||
price: 123,
|
price: 123,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -906,6 +917,7 @@ mod tests {
|
|||||||
catalog1.init().unwrap();
|
catalog1.init().unwrap();
|
||||||
let original_item = Item {
|
let original_item = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Original Name".to_string(),
|
name: "Original Name".to_string(),
|
||||||
price: 100,
|
price: 100,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -920,6 +932,7 @@ mod tests {
|
|||||||
// 3. Upsert updated data for the same item. This should mark it as 'Updated'.
|
// 3. Upsert updated data for the same item. This should mark it as 'Updated'.
|
||||||
let updated_item = Item {
|
let updated_item = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Updated Name".to_string(),
|
name: "Updated Name".to_string(),
|
||||||
price: 200,
|
price: 200,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -957,6 +970,7 @@ mod tests {
|
|||||||
catalog_setup.init().unwrap();
|
catalog_setup.init().unwrap();
|
||||||
let item_to_update_original = Item {
|
let item_to_update_original = Item {
|
||||||
id: "update-me".to_string(),
|
id: "update-me".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Original".to_string(),
|
name: "Original".to_string(),
|
||||||
price: 10,
|
price: 10,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -965,6 +979,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let item_to_delete = Item {
|
let item_to_delete = Item {
|
||||||
id: "delete-me".to_string(),
|
id: "delete-me".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Delete Me".to_string(),
|
name: "Delete Me".to_string(),
|
||||||
price: 20,
|
price: 20,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -973,6 +988,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let item_to_keep = Item {
|
let item_to_keep = Item {
|
||||||
id: "keep-me".to_string(),
|
id: "keep-me".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Keep Me".to_string(),
|
name: "Keep Me".to_string(),
|
||||||
price: 30,
|
price: 30,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -989,6 +1005,7 @@ mod tests {
|
|||||||
// A new item to be inserted.
|
// A new item to be inserted.
|
||||||
let item_to_add = Item {
|
let item_to_add = Item {
|
||||||
id: "add-me".to_string(),
|
id: "add-me".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Add Me".to_string(),
|
name: "Add Me".to_string(),
|
||||||
price: 40,
|
price: 40,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -999,6 +1016,7 @@ mod tests {
|
|||||||
// An updated version of an existing item.
|
// An updated version of an existing item.
|
||||||
let item_to_update_new = Item {
|
let item_to_update_new = Item {
|
||||||
id: "update-me".to_string(),
|
id: "update-me".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Updated".to_string(),
|
name: "Updated".to_string(),
|
||||||
price: 11,
|
price: 11,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -1194,6 +1212,7 @@ mod tests {
|
|||||||
catalog1.init().unwrap();
|
catalog1.init().unwrap();
|
||||||
let item_to_persist = Item {
|
let item_to_persist = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Test Item".to_string(),
|
name: "Test Item".to_string(),
|
||||||
price: 123,
|
price: 123,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -1245,6 +1264,7 @@ mod tests {
|
|||||||
catalog1.init().unwrap();
|
catalog1.init().unwrap();
|
||||||
let item_in_db = Item {
|
let item_in_db = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item from DB".to_string(),
|
name: "Item from DB".to_string(),
|
||||||
price: 100,
|
price: 100,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -1257,6 +1277,7 @@ mod tests {
|
|||||||
let mut catalog2 = Catalog::new(db_path);
|
let mut catalog2 = Catalog::new(db_path);
|
||||||
let item_in_memory = Item {
|
let item_in_memory = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "In-memory version".to_string(),
|
name: "In-memory version".to_string(),
|
||||||
price: 200,
|
price: 200,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -1343,6 +1364,7 @@ mod tests {
|
|||||||
catalog1.init().unwrap();
|
catalog1.init().unwrap();
|
||||||
let item1 = Item {
|
let item1 = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item One".to_string(),
|
name: "Item One".to_string(),
|
||||||
price: 100,
|
price: 100,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -1351,6 +1373,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let item2 = Item {
|
let item2 = Item {
|
||||||
id: "item-2".to_string(),
|
id: "item-2".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item Two".to_string(),
|
name: "Item Two".to_string(),
|
||||||
price: 200,
|
price: 200,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -1395,6 +1418,7 @@ mod tests {
|
|||||||
catalog1.init().unwrap();
|
catalog1.init().unwrap();
|
||||||
let item_in_db = Item {
|
let item_in_db = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "DB Version".to_string(),
|
name: "DB Version".to_string(),
|
||||||
price: 100,
|
price: 100,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -1407,6 +1431,7 @@ mod tests {
|
|||||||
let mut catalog2 = Catalog::new(db_path);
|
let mut catalog2 = Catalog::new(db_path);
|
||||||
let item_in_memory = Item {
|
let item_in_memory = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Memory Version".to_string(),
|
name: "Memory Version".to_string(),
|
||||||
price: 200,
|
price: 200,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -1452,6 +1477,7 @@ mod tests {
|
|||||||
// 3. Add an item to memory and try loading again from the empty DB.
|
// 3. Add an item to memory and try loading again from the empty DB.
|
||||||
let item_in_memory = Item {
|
let item_in_memory = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "In-memory only".to_string(),
|
name: "In-memory only".to_string(),
|
||||||
price: 100,
|
price: 100,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -1622,6 +1648,7 @@ mod tests {
|
|||||||
catalog_setup.init().unwrap();
|
catalog_setup.init().unwrap();
|
||||||
let item_in_db = Item {
|
let item_in_db = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "DB Version".to_string(),
|
name: "DB Version".to_string(),
|
||||||
price: 100,
|
price: 100,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -1633,6 +1660,7 @@ mod tests {
|
|||||||
let mut catalog = Catalog::new(db_path);
|
let mut catalog = Catalog::new(db_path);
|
||||||
let item_in_memory = Item {
|
let item_in_memory = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Memory Version".to_string(),
|
name: "Memory Version".to_string(),
|
||||||
price: 200,
|
price: 200,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -2191,6 +2219,7 @@ mod tests {
|
|||||||
catalog1.init().unwrap();
|
catalog1.init().unwrap();
|
||||||
let item_to_insert = Item {
|
let item_to_insert = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Integration Test Item".to_string(),
|
name: "Integration Test Item".to_string(),
|
||||||
price: 999,
|
price: 999,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -2223,6 +2252,7 @@ mod tests {
|
|||||||
let items_to_insert = vec![
|
let items_to_insert = vec![
|
||||||
Item {
|
Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item One".to_string(),
|
name: "Item One".to_string(),
|
||||||
price: 100,
|
price: 100,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -2231,6 +2261,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
Item {
|
Item {
|
||||||
id: "item-2".to_string(),
|
id: "item-2".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item Two".to_string(),
|
name: "Item Two".to_string(),
|
||||||
price: 200,
|
price: 200,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -2309,6 +2340,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let item2 = Item {
|
let item2 = Item {
|
||||||
id: "item-2".to_string(),
|
id: "item-2".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Second Item".to_string(),
|
name: "Second Item".to_string(),
|
||||||
price: 200,
|
price: 200,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -2331,6 +2363,7 @@ mod tests {
|
|||||||
let mut catalog = Catalog::new("dummy.db");
|
let mut catalog = Catalog::new("dummy.db");
|
||||||
let item1 = Item {
|
let item1 = Item {
|
||||||
id: "item-1".to_string(),
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item One".to_string(),
|
name: "Item One".to_string(),
|
||||||
price: 100,
|
price: 100,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -2339,6 +2372,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let item2 = Item {
|
let item2 = Item {
|
||||||
id: "item-2".to_string(),
|
id: "item-2".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item Two".to_string(),
|
name: "Item Two".to_string(),
|
||||||
price: 200,
|
price: 200,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -2347,6 +2381,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let item3 = Item {
|
let item3 = Item {
|
||||||
id: "item-3".to_string(),
|
id: "item-3".to_string(),
|
||||||
|
subclass: Some("subitem".to_string()),
|
||||||
name: "Item Three".to_string(),
|
name: "Item Three".to_string(),
|
||||||
price: 150,
|
price: 150,
|
||||||
sell_trend: 0,
|
sell_trend: 0,
|
||||||
@@ -3149,10 +3184,8 @@ mod tests {
|
|||||||
assert!(catalog.load_by_filter(&filter).is_ok());
|
assert!(catalog.load_by_filter(&filter).is_ok());
|
||||||
assert_eq!(catalog.items.len(), 1);
|
assert_eq!(catalog.items.len(), 1);
|
||||||
assert!(catalog.items.contains_key("item-1"));
|
assert!(catalog.items.contains_key("item-1"));
|
||||||
|
|
||||||
std::fs::remove_file(path).unwrap();
|
std::fs::remove_file(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_by_class_and_subclass_should_return_matching_entities() {
|
fn list_by_class_and_subclass_should_return_matching_entities() {
|
||||||
let mut catalog = Catalog::new("dummy.db");
|
let mut catalog = Catalog::new("dummy.db");
|
||||||
@@ -3174,18 +3207,15 @@ mod tests {
|
|||||||
let _ = catalog.upsert(item1.clone());
|
let _ = catalog.upsert(item1.clone());
|
||||||
let _ = catalog.upsert(item2.clone());
|
let _ = catalog.upsert(item2.clone());
|
||||||
let _ = catalog.upsert(item3.clone());
|
let _ = catalog.upsert(item3.clone());
|
||||||
|
|
||||||
let results: Vec<Item> = catalog
|
let results: Vec<Item> = catalog
|
||||||
.list_by_class_and_subclass("electronics")
|
.list_by_class_and_subclass("electronics")
|
||||||
.map(|item| item.unwrap())
|
.map(|item| item.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
assert_eq!(results.len(), 2);
|
assert_eq!(results.len(), 2);
|
||||||
assert!(results.contains(&item1));
|
assert!(results.contains(&item1));
|
||||||
assert!(results.contains(&item3));
|
assert!(results.contains(&item3));
|
||||||
assert!(!results.contains(&item2));
|
assert!(!results.contains(&item2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_by_class_and_subclass_should_return_empty_if_no_match() {
|
fn list_by_class_and_subclass_should_return_empty_if_no_match() {
|
||||||
let mut catalog = Catalog::new("dummy.db");
|
let mut catalog = Catalog::new("dummy.db");
|
||||||
@@ -3195,12 +3225,102 @@ mod tests {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let _ = catalog.upsert(item1.clone());
|
let _ = catalog.upsert(item1.clone());
|
||||||
|
|
||||||
let results: Vec<Item> = catalog
|
let results: Vec<Item> = catalog
|
||||||
.list_by_class_and_subclass("books")
|
.list_by_class_and_subclass("books")
|
||||||
.map(|item| item.unwrap())
|
.map(|item| item.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
assert!(results.is_empty());
|
assert!(results.is_empty());
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn load_by_filter_with_class_and_subclass_clauses() {
|
||||||
|
// Verifies that class and subclass clauses in a filter correctly load entities.
|
||||||
|
let db_path = "target/test_dbs/lbf_with_class_subclass_clauses.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();
|
||||||
|
// Define a second struct with a different class
|
||||||
|
#[derive(Debug, Default, PartialEq, Clone)]
|
||||||
|
struct AnotherItem {
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
impl EAV for AnotherItem {
|
||||||
|
fn class() -> &'static str {
|
||||||
|
"another_item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<AnotherItem> for Entity {
|
||||||
|
fn from(value: AnotherItem) -> Entity {
|
||||||
|
Entity::new::<AnotherItem>().with_id(&value.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Entity> for AnotherItem {
|
||||||
|
fn from(entity: Entity) -> Self {
|
||||||
|
Self {
|
||||||
|
id: entity.id.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let item1 = Item {
|
||||||
|
id: "item-1".to_string(),
|
||||||
|
subclass: Some("sub-a".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let item2 = Item {
|
||||||
|
id: "item-2".to_string(),
|
||||||
|
subclass: Some("sub-b".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let item3 = Item {
|
||||||
|
id: "item-3".to_string(),
|
||||||
|
subclass: Some("sub-a".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let item4 = Item {
|
||||||
|
id: "item-4".to_string(),
|
||||||
|
subclass: None, // -> "subitem"
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let another_item = AnotherItem {
|
||||||
|
id: "another-1".to_string(),
|
||||||
|
};
|
||||||
|
catalog_setup.upsert(item1).unwrap();
|
||||||
|
catalog_setup.upsert(item2).unwrap();
|
||||||
|
catalog_setup.upsert(item3).unwrap();
|
||||||
|
catalog_setup.upsert(item4).unwrap();
|
||||||
|
catalog_setup.upsert(another_item).unwrap();
|
||||||
|
catalog_setup.persist().unwrap();
|
||||||
|
// Test 1: Filter by class "item"
|
||||||
|
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"));
|
||||||
|
// 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"));
|
||||||
|
// 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"));
|
||||||
|
// 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"));
|
||||||
|
std::fs::remove_file(path).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ use crate::*;
|
|||||||
/// all specified criteria.
|
/// all specified criteria.
|
||||||
#[derive(Debug, Default, PartialEq, Clone)]
|
#[derive(Debug, Default, PartialEq, Clone)]
|
||||||
pub struct O<'a> {
|
pub struct O<'a> {
|
||||||
|
class: Option<String>,
|
||||||
|
subclass: Option<String>,
|
||||||
conditions: Vec<(String, Comparison, Condition<'a>)>,
|
conditions: Vec<(String, Comparison, Condition<'a>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,9 +16,21 @@ impl<'a> Filter<'a> {
|
|||||||
/// Creates a new, empty `Filter`.
|
/// Creates a new, empty `Filter`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
class: None,
|
||||||
|
subclass: None,
|
||||||
conditions: Vec::new(),
|
conditions: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Adds a class condition to the filter.
|
||||||
|
pub fn with_class(mut self, value: &str) -> Self {
|
||||||
|
self.class = Some(value.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Adds a subclass condition to the filter.
|
||||||
|
pub fn with_subclass(mut self, value: &str) -> Self {
|
||||||
|
self.subclass = Some(value.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
/// Adds a boolean condition to the filter.
|
/// Adds a boolean condition to the filter.
|
||||||
///
|
///
|
||||||
/// This is a shorthand for `with_bool(name, Comparison::Equal, value)`.
|
/// This is a shorthand for `with_bool(name, Comparison::Equal, value)`.
|
||||||
@@ -85,4 +99,10 @@ impl<'a> Filter<'a> {
|
|||||||
pub(crate) fn conditions(&self) -> impl Iterator<Item = &(String, Comparison, Condition<'a>)> {
|
pub(crate) fn conditions(&self) -> impl Iterator<Item = &(String, Comparison, Condition<'a>)> {
|
||||||
self.conditions.iter()
|
self.conditions.iter()
|
||||||
}
|
}
|
||||||
|
pub(crate) fn class(&self) -> &Option<String> {
|
||||||
|
&self.class
|
||||||
|
}
|
||||||
|
pub(crate) fn subclass(&self) -> &Option<String> {
|
||||||
|
&self.subclass
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user