diff --git a/CHANGELOG.md b/CHANGELOG.md index f3d6c70..ded3731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,48 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +# 0.5.0 (2024-05-23) + +New Features: + +- [[`#15`](https://github.com/plabayo/venndb/issues/15)]: support multi dimensional filter options to be filtered in group + +Example: + +```rust +use venndb::{Any, VennDB}; + +#[derive(Debug, VennDB)] +pub struct Value { + #[venndb(filter)] + pub foo: String, + pub bar: u32, +} + +let db = ValueDB::from_iter([ + Value { + foo: "a".to_owned(), + bar: 8, + }, + Value { + foo: "b".to_owned(), + bar: 12, + }, + Value { + foo: "c".to_owned(), + bar: 16, + }, +].into_Iter()).unwrap(); + +let mut query = db.query(); +query.foo(MyString("a".to_owned())); +query.foo(MyString("c".to_owned())); +let values: Vec<_> = query.execute().unwrap().iter().collect(); +assert_eq!(values.len(), 2); +assert_eq!(values[0].bar, 8); +assert_eq!(values[0].bar, 16); +``` + # 0.4.0 (2024-04-19) Breaking Changes: diff --git a/Cargo.toml b/Cargo.toml index 0c987de..b475a85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://github.com/plabayo/venndb" keywords = ["database", "db", "memory", "bits"] categories = ["database"] authors = ["Glen De Cauwsemaecker "] -version = "0.4.0" +version = "0.5.0" rust-version = "1.75.0" [package.metadata.docs.rs] @@ -23,7 +23,7 @@ rustdoc-args = ["--cfg", "docsrs"] bitvec = "1.0.1" hashbrown = "0.14.3" rand = "0.8.5" -venndb-macros = { version = "0.4.0", path = "venndb-macros" } +venndb-macros = { version = "0.5.0", path = "venndb-macros" } [dev-dependencies] divan = "0.1.14" diff --git a/README.md b/README.md index 5c2b69a..49d6dd9 100644 --- a/README.md +++ b/README.md @@ -387,6 +387,47 @@ with a value of `foo` being more likely then other strings. As such the only correct answer when filtering for _any_ value, is to return rows that have _any_ value. +> ❓ How can I query on multiple variants of a "filter map" property? + +Just call the _query_ method multiple times. It will allow you to match +rows that have either of these values. + +Example + +```rust,ignore +use venndb::{Any, VennDB}; + +#[derive(Debug, VennDB)] +pub struct Value { + #[venndb(filter)] + pub foo: String, + pub bar: u32, +} + +let db = ValueDB::from_iter([ + Value { + foo: "a".to_owned(), + bar: 8, + }, + Value { + foo: "b".to_owned(), + bar: 12, + }, + Value { + foo: "c".to_owned(), + bar: 16, + }, +].into_Iter()).unwrap(); + +let mut query = db.query(); +query.foo(MyString("a".to_owned())); +query.foo(MyString("c".to_owned())); +let values: Vec<_> = query.execute().unwrap().iter().collect(); +assert_eq!(values.len(), 2); +assert_eq!(values[0].bar, 8); +assert_eq!(values[0].bar, 16); +``` + ## Example Here follows an example demonstrating all the features of `VennDB`. @@ -755,7 +796,7 @@ Query (e.g. `EmployeeInMemDBQuery`) | `EmployeeInMemDBQuery::reset(&mut self) -> &mut Self` | reset the query, bringing it back to the clean state it has on creation | | `EmployeeInMemDBQuery::execute(&self) -> Option>` | return the result of the query using the set filters. It will be `None` in case no rows matched the defined filters. Or put otherwise, the result will contain at least one row when `Some(_)` is returned. | | `EmployeeInMemDBQuery::is_manager(&mut self, value: bool) -> &mut Self` | a filter setter for a `bool` filter. One such method per `bool` filter (that isn't `skip`ped) will be available. E.g. if you have ` foo` filter then there will be a `EmployeeInMemDBQuery:foo` method. For _bool_ filters that are optional (`Option`) this method is also generated just the same. | -| `EmployeeInMemDBQuery::department(&mut self, value: impl ::std::convert::Into) -> &mut Self` | a filter (map) setter for a non-`bool` filter. One such method per non-`bool` filter will be available. You can also `skip` these, but that's of course a bit pointless. The type will be equal to the actual field type. And the name will once again be equal to the original field name. Filter maps that have a `Option` type have exactly the same signature. | +| `EmployeeInMemDBQuery::department(&mut self, value: impl ::std::convert::Into) -> &mut Self` | a filter (map) setter for a non-`bool` filter. One such method per non-`bool` filter will be available. You can also `skip` these, but that's of course a bit pointless. The type will be equal to the actual field type. And the name will once again be equal to the original field name. Filter maps that have a `Option` type have exactly the same signature. Duering query you can call this method multiple times in case you wish to allow multiple variants. | Query Result (e.g. `EmployeeInMemDBQueryResult`) diff --git a/venndb-macros/Cargo.toml b/venndb-macros/Cargo.toml index 189514f..4d88999 100644 --- a/venndb-macros/Cargo.toml +++ b/venndb-macros/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/plabayo/venndb" keywords = ["database", "db", "memory", "bits"] categories = ["database", "db"] authors = ["Glen De Cauwsemaecker "] -version = "0.4.0" +version = "0.5.0" rust-version = "1.75.0" [package.metadata.docs.rs] diff --git a/venndb-macros/src/generate_db.rs b/venndb-macros/src/generate_db.rs index ecc8b00..8caec9c 100644 --- a/venndb-macros/src/generate_db.rs +++ b/venndb-macros/src/generate_db.rs @@ -623,7 +623,7 @@ fn generate_query_struct( let name = field.name(); let ty = field.ty(); Some(quote! { - #name: Option<#ty>, + #name: Vec<#ty>, }) } FieldInfo::Key(_) => None, @@ -646,7 +646,7 @@ fn generate_query_struct( FieldInfo::FilterMap(field) => { let name = field.name(); Some(quote! { - #name: None, + #name: Vec::new(), }) } FieldInfo::Key(_) => None, @@ -724,7 +724,7 @@ fn generate_query_struct_impl( Some(quote! { #[doc=#doc] #vis fn #name(&mut self, value: impl::std::convert::Into<#ty>) -> &mut Self { - self.#name = Some(value.into()); + self.#name.push(value.into()); self } }) @@ -745,7 +745,7 @@ fn generate_query_struct_impl( FieldInfo::FilterMap(field) => { let name = field.name(); Some(quote! { - self.#name = None; + self.#name.clear(); }) } FieldInfo::Key(_) => None, @@ -775,7 +775,8 @@ fn generate_query_struct_impl( let name = field.name(); let filter_map_name: Ident = field.filter_map_name(); let filter_vec_name: Ident = field.filter_vec_name(); - let value_filter = match field.filter_any_name() { + // used if only one value is set, making it more efficient + let value_filter_one = match field.filter_any_name() { Some(filter_any_vec) => quote! { if ::venndb::Any::is_any(&value) { filter &= &self.db.#filter_any_vec; @@ -793,13 +794,43 @@ fn generate_query_struct_impl( }; }, }; + // used if multiple values are set, requires an extra alloc + let value_filter_multi = match field.filter_any_name() { + Some(filter_any_vec) => quote! { + if ::venndb::Any::is_any(&value) { + inter_filter |= &self.db.#filter_any_vec; + } else { + match self.db.#filter_map_name.get(value) { + Some(index) => inter_filter |= &self.db.#filter_vec_name[*index], + None => inter_filter |= &self.db.#filter_any_vec, + }; + } + }, + None => quote! { + match self.db.#filter_map_name.get(value) { + Some(index) => inter_filter |= &self.db.#filter_vec_name[*index], + None => return None, + }; + }, + }; + // apply the filter Some(quote! { // Filter by the filterm ap below, only if it is defined as Some(_). // If there is no filter matched to the given value then the search is over, // and we early return None. - - if let Some(value) = &self.#name { - #value_filter + match &self.#name.len() { + 0 => (), + 1 => { + let value = &self.#name[self.#name.len() - 1]; + #value_filter_one + } + _ => { + let mut inter_filter = ::venndb::__internal::BitVec::repeat(false, self.db.rows.len()); + for value in &self.#name { + #value_filter_multi + } + filter &= inter_filter; + } } }) } diff --git a/venndb-usage/src/main.rs b/venndb-usage/src/main.rs index af5d1ea..f9094ee 100644 --- a/venndb-usage/src/main.rs +++ b/venndb-usage/src/main.rs @@ -1351,3 +1351,88 @@ mod tests_v0_4 { ); } } + +#[cfg(test)] +mod tests_v0_5 { + use super::*; + + #[test] + fn test_employee_db_multi_filter_map_value() { + let db = EmployeeDB::from_rows(vec![ + Employee { + id: 1, + name: "Alice".to_string(), + is_manager: true, + is_admin: false, + is_active: true, + department: Department::Engineering, + }, + Employee { + id: 2, + name: "Bob".to_string(), + is_manager: false, + is_admin: false, + is_active: true, + department: Department::HR, + }, + Employee { + id: 3, + name: "Charlie".to_string(), + is_manager: true, + is_admin: true, + is_active: true, + department: Department::Sales, + }, + ]) + .unwrap(); + + let mut query = db.query(); + query.department(Department::Engineering); + query.department(Department::HR); + + let results = query.execute().unwrap().iter().collect::>(); + assert_eq!(results.len(), 2); + assert_eq!(results[0].id, 1); + assert_eq!(results[1].id, 2); + } + + #[test] + fn test_employee_db_multi_filter_map_value_with_any() { + let db = EmployeeDB::from_rows(vec![ + Employee { + id: 1, + name: "Alice".to_string(), + is_manager: true, + is_admin: false, + is_active: true, + department: Department::Engineering, + }, + Employee { + id: 2, + name: "Bob".to_string(), + is_manager: false, + is_admin: false, + is_active: true, + department: Department::HR, + }, + Employee { + id: 3, + name: "Charlie".to_string(), + is_manager: true, + is_admin: true, + is_active: true, + department: Department::Any, + }, + ]) + .unwrap(); + + let mut query = db.query(); + query.department(Department::Engineering); + query.department(Department::Any); + + let results = query.execute().unwrap().iter().collect::>(); + assert_eq!(results.len(), 2); + assert_eq!(results[0].id, 1); + assert_eq!(results[1].id, 3); + } +} diff --git a/venndb-usage/tests/compiles/derive_struct_all_the_things.rs b/venndb-usage/tests/compiles/derive_struct_all_the_things.rs index e207d04..2ae1578 100644 --- a/venndb-usage/tests/compiles/derive_struct_all_the_things.rs +++ b/venndb-usage/tests/compiles/derive_struct_all_the_things.rs @@ -61,6 +61,7 @@ fn main() { query .is_manager(true) .is_admin(true) - .department(Department::Engineering); + .department(Department::Engineering) + .department(Department::Sales); assert!(query.execute().is_none()); }