From ceecdd3912c5d7be6b83b60c3a79e61c9ccae363 Mon Sep 17 00:00:00 2001 From: davidemazzocchi Date: Sun, 19 Oct 2025 11:56:01 +0200 Subject: [PATCH] feat: handle comparison "starts with" for text condition - rewrite statement generation to handle errors - align "exactly match" with case insensitiveness of starts with --- .../heave/src/fun/sqlite_build_params.rs | 43 +++++++++-- .../heave/src/fun/sqlite_build_statement.rs | 5 +- 01.workspace/heave/src/str/catalog.rs | 74 ++++++++++++++++++- 3 files changed, 113 insertions(+), 9 deletions(-) diff --git a/01.workspace/heave/src/fun/sqlite_build_params.rs b/01.workspace/heave/src/fun/sqlite_build_params.rs index fda3f1f..60db287 100644 --- a/01.workspace/heave/src/fun/sqlite_build_params.rs +++ b/01.workspace/heave/src/fun/sqlite_build_params.rs @@ -4,12 +4,43 @@ use rusqlite::*; pub fn run<'a>(filter: &'a Filter) -> Result>, FailedTo> { let mut params: Vec> = Vec::new(); for condition in filter.conditions() { - let (_, _, condition) = condition; - match condition { - Condition::Bool(value) => params.push(Box::new(value)), - Condition::SignedInt(value) => params.push(Box::new(value)), - Condition::UnsignedInt(value) => params.push(Box::new(value)), - Condition::Text(value) => params.push(Box::new(value)), + let (_, comparison, condition) = condition; + match (comparison, condition) { + // BOOL + (Comparison::Equal, Condition::Bool(value)) => params.push(Box::new(value)), + // SIGNED INT + (Comparison::Equal, Condition::SignedInt(value)) => params.push(Box::new(value)), + (Comparison::Greater, Condition::SignedInt(value)) => params.push(Box::new(value)), + (Comparison::Lesser, Condition::SignedInt(value)) => params.push(Box::new(value)), + (Comparison::GreaterOrEqual, Condition::SignedInt(value)) => { + params.push(Box::new(value)) + } + (Comparison::LesserOrEqual, Condition::SignedInt(value)) => { + params.push(Box::new(value)) + } + // UNSIGNED INT + (Comparison::Equal, Condition::UnsignedInt(value)) => params.push(Box::new(value)), + (Comparison::Greater, Condition::UnsignedInt(value)) => params.push(Box::new(value)), + (Comparison::Lesser, Condition::UnsignedInt(value)) => params.push(Box::new(value)), + (Comparison::GreaterOrEqual, Condition::UnsignedInt(value)) => { + params.push(Box::new(value)) + } + (Comparison::LesserOrEqual, Condition::UnsignedInt(value)) => { + params.push(Box::new(value)) + } + // TEXT + (Comparison::IsExactly, Condition::Text(value)) => params.push(Box::new(value)), + (Comparison::StartsWith, Condition::Text(value)) => { + params.push(Box::new(format!("{}%", value))) + } + (Comparison::EndsWith, Condition::Text(value)) => { + params.push(Box::new(format!("%{}", value))) + } + (Comparison::Contains, Condition::Text(value)) => { + params.push(Box::new(format!("%{}%", value))) + } + // ERROR + _ => return Err(FailedTo::ComposeFilter), } } Ok(params) diff --git a/01.workspace/heave/src/fun/sqlite_build_statement.rs b/01.workspace/heave/src/fun/sqlite_build_statement.rs index edc7b4c..e3e812c 100644 --- a/01.workspace/heave/src/fun/sqlite_build_statement.rs +++ b/01.workspace/heave/src/fun/sqlite_build_statement.rs @@ -61,7 +61,10 @@ pub fn run(filter: &Filter) -> Result { (_, Condition::UnsignedInt(_)) => return Err(FailedTo::ComposeFilter), // TEXT (Comparison::IsExactly, Condition::Text(_)) => { - compose_fragment(name, "value_text", "=", i + 1) + compose_fragment(name, "value_text", "LIKE", i + 1) + } + (Comparison::StartsWith, Condition::Text(_)) => { + compose_fragment(name, "value_text", "LIKE", i + 1) } _ => todo!(), }; diff --git a/01.workspace/heave/src/str/catalog.rs b/01.workspace/heave/src/str/catalog.rs index 855a05a..9154885 100644 --- a/01.workspace/heave/src/str/catalog.rs +++ b/01.workspace/heave/src/str/catalog.rs @@ -2321,11 +2321,12 @@ mod tests { let filter = Filter::new().with_text("name", Comparison::IsExactly, "Item"); assert!(catalog.load_by_filter(&filter).is_ok()); assert!(catalog.items.is_empty()); - // Case sensitive match + // Case insensitive match let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::IsExactly, "item one"); assert!(catalog.load_by_filter(&filter).is_ok()); - assert!(catalog.items.is_empty()); + assert_eq!(catalog.items.len(), 1); + assert!(catalog.items.contains_key("item-1")); // No match let mut catalog = Catalog::new(db_path); let filter = Filter::new().with_text("name", Comparison::IsExactly, "Item Four"); @@ -2333,4 +2334,73 @@ mod tests { assert!(catalog.items.is_empty()); std::fs::remove_file(path).unwrap(); } + #[test] + fn load_by_filter_should_load_text_starts_with_comparisons() { + let db_path = "target/test_dbs/lbf_text_starts_with_comparisons.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, + sell_trend: 0, + in_stock: true, + }, + Item { + id: "item-2".to_string(), + name: "Item Two".to_string(), + price: 200, + sell_trend: 0, + in_stock: false, + }, + Item { + id: "item-3".to_string(), + name: "Another Item".to_string(), + price: 300, + sell_trend: 0, + in_stock: true, + }, + ]; + catalog_setup.insert_many(items).unwrap(); + catalog_setup.persist().unwrap(); + // Starts with "Item" + let mut catalog = Catalog::new(db_path); + let filter = Filter::new().with_text("name", Comparison::StartsWith, "Item"); + 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-2")); + // Starts with "I" + let mut catalog = Catalog::new(db_path); + let filter = Filter::new().with_text("name", Comparison::StartsWith, "I"); + 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-2")); + // No match + let mut catalog = Catalog::new(db_path); + let filter = Filter::new().with_text("name", Comparison::StartsWith, "Z"); + assert!(catalog.load_by_filter(&filter).is_ok()); + assert!(catalog.items.is_empty()); + // Case insensitive + let mut catalog = Catalog::new(db_path); + let filter = Filter::new().with_text("name", Comparison::StartsWith, "item"); + 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-2")); + // Full string match + let mut catalog = Catalog::new(db_path); + let filter = Filter::new().with_text("name", Comparison::StartsWith, "Item One"); + assert!(catalog.load_by_filter(&filter).is_ok()); + assert_eq!(catalog.items.len(), 1); + assert!(catalog.items.contains_key("item-1")); + std::fs::remove_file(path).unwrap(); + } }