diff --git a/src/lib.rs b/src/lib.rs index 93cce10..793f800 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ pub mod __internal { //! Hidden thirdparty dependencies for venndb, //! not to be relied upon directly, as they may change at any time. - pub use bitvec::{bitvec, order::Lsb0, slice::IterOnes, vec::BitVec}; + pub use bitvec::{order::Lsb0, slice::IterOnes, vec::BitVec}; pub use hashbrown::HashMap; /// Generate a random `usize`. diff --git a/venndb-macros/src/field.rs b/venndb-macros/src/field.rs index 2324552..fcc1107 100644 --- a/venndb-macros/src/field.rs +++ b/venndb-macros/src/field.rs @@ -24,6 +24,7 @@ pub struct StructField<'a> { pub enum FieldInfo<'a> { Key(KeyField<'a>), Filter(FilterField<'a>), + FilterMap(FilterMapField<'a>), } pub struct KeyField<'a> { @@ -83,6 +84,33 @@ impl<'a> StructField<'a> { ty: &self.field.ty, }), FieldKind::Filter => FieldInfo::Filter(FilterField { name: self.name }), + FieldKind::FilterMap => FieldInfo::FilterMap(FilterMapField { + name: self.name, + ty: &self.field.ty, + }), }) } } + +pub struct FilterMapField<'a> { + pub name: &'a Ident, + pub ty: &'a syn::Type, +} + +impl<'a> FilterMapField<'a> { + pub fn name(&'a self) -> &'a Ident { + self.name + } + + pub fn ty(&'a self) -> &'a syn::Type { + self.ty + } + + pub fn filter_map_name(&self) -> Ident { + format_ident!("filter_map_{}", self.name) + } + + pub fn filter_vec_name(&self) -> Ident { + format_ident!("filter_vec_{}", self.name) + } +} diff --git a/venndb-macros/src/generate_db.rs b/venndb-macros/src/generate_db.rs index 8048d5d..5b4572a 100644 --- a/venndb-macros/src/generate_db.rs +++ b/venndb-macros/src/generate_db.rs @@ -56,6 +56,15 @@ fn generate_db_struct( #field_name_not: ::venndb::__internal::BitVec, } } + FieldInfo::FilterMap(field) => { + let filter_map_name = field.filter_map_name(); + let filter_vec_name = field.filter_vec_name(); + let ty: &syn::Type = field.ty(); + quote! { + #filter_map_name: ::venndb::__internal::HashMap<#ty, usize>, + #filter_vec_name: ::std::vec::Vec<::venndb::__internal::BitVec>, + } + } }) .collect(); @@ -122,7 +131,7 @@ fn generate_db_struct_methods( #method_append /// Consumes the database and returns the rows. - #vis fn into_rows(self) -> Vec<#name> { + #vis fn into_rows(self) -> ::std::vec::Vec<#name> { self.rows } } @@ -157,6 +166,14 @@ fn generate_db_struct_method_new( #name_not: ::venndb::__internal::BitVec::new(), } } + FieldInfo::FilterMap(field) => { + let filter_map_name = field.filter_map_name(); + let filter_vec_name = field.filter_vec_name(); + quote! { + #filter_map_name: ::venndb::__internal::HashMap::new(), + #filter_vec_name: ::std::vec::Vec::new(), + } + } }) .collect(); @@ -199,6 +216,14 @@ fn generate_db_struct_method_with_capacity( #name_not: ::venndb::__internal::BitVec::with_capacity(capacity), } } + FieldInfo::FilterMap(field) => { + let filter_map_name = field.filter_map_name(); + let filter_vec_name = field.filter_vec_name(); + quote! { + #filter_map_name: ::venndb::__internal::HashMap::with_capacity(capacity), + #filter_vec_name: ::std::vec::Vec::with_capacity(capacity), + } + } }) .collect(); @@ -225,7 +250,8 @@ fn generate_db_struct_method_from_rows( name ); - let return_type = db_error.generate_fn_output(name_db, quote! { Vec<#name> }, quote! { Self }); + let return_type = + db_error.generate_fn_output(name_db, quote! { ::std::vec::Vec<#name> }, quote! { Self }); let append_internal_call = db_error.generate_fn_error_kind_usage( name_db, quote! { @@ -239,7 +265,7 @@ fn generate_db_struct_method_from_rows( quote! { #[doc=#method_doc] - #vis fn from_rows(rows: Vec<#name>) -> #return_type { + #vis fn from_rows(rows: ::std::vec::Vec<#name>) -> #return_type { let mut db = Self::with_capacity(rows.len()); for (index, row) in rows.iter().enumerate() { #append_internal_call @@ -278,6 +304,7 @@ fn generate_db_struct_method_append( }) } FieldInfo::Filter(_) => None, + FieldInfo::FilterMap(_) => None, }) .collect(); @@ -301,6 +328,27 @@ fn generate_db_struct_method_append( self.#field_name_not.push(!data.#name); } } + FieldInfo::FilterMap(field) => { + let name = field.name(); + let filter_map_name = field.filter_map_name(); + let filter_vec_name = field.filter_vec_name(); + let filter_index = format_ident!("{}_index", filter_vec_name); + quote! { + let #filter_index = match self.#filter_map_name.entry(data.#name.clone()) { + ::venndb::__internal::hash_map::Entry::Occupied(entry) => *entry.get(), + ::venndb::__internal::hash_map::Entry::Vacant(entry) => { + let index = self.#filter_vec_name.len(); + entry.insert(index); + let bv = ::venndb::__internal::BitVec::repeat(false, self.rows.len()); + self.#filter_vec_name.push(bv); + index + } + }; + for (i, row) in self.#filter_vec_name.iter_mut().enumerate() { + row.push(i == #filter_index); + } + } + } }) .collect(); @@ -364,6 +412,7 @@ fn generate_db_struct_field_methods( }) } FieldInfo::Filter(_) => None, + FieldInfo::FilterMap(_) => None, }) .collect(); @@ -389,6 +438,13 @@ fn generate_query_struct( #name: Option, }) } + FieldInfo::FilterMap(field) => { + let name = field.name(); + let ty = field.ty(); + Some(quote! { + #name: Option<#ty>, + }) + } FieldInfo::Key(_) => None, }) .collect(); @@ -406,6 +462,12 @@ fn generate_query_struct( #name: None, }) } + FieldInfo::FilterMap(field) => { + let name = field.name(); + Some(quote! { + #name: None, + }) + } FieldInfo::Key(_) => None, }) .collect(); @@ -471,6 +533,21 @@ fn generate_query_struct_impl( } }) } + FieldInfo::FilterMap(field) => { + let name = field.name(); + let ty = field.ty(); + let doc = format!( + "Enable and set the `{}` filter-map with the given option.", + name + ); + Some(quote! { + #[doc=#doc] + #vis fn #name(&mut self, value: #ty) -> &mut Self { + self.#name = Some(value); + self + } + }) + } FieldInfo::Key(_) => None, }) .collect(); @@ -484,6 +561,12 @@ fn generate_query_struct_impl( self.#name = None; }) } + FieldInfo::FilterMap(field) => { + let name = field.name(); + Some(quote! { + self.#name = None; + }) + } FieldInfo::Key(_) => None, }) .collect(); @@ -503,6 +586,19 @@ fn generate_query_struct_impl( }; }) } + FieldInfo::FilterMap(field) => { + let name = field.name(); + let filter_map_name: Ident = field.filter_map_name(); + let filter_vec_name: Ident = field.filter_vec_name(); + Some(quote! { + if let Some(value) = &self.#name { + match self.db.#filter_map_name.get(value) { + Some(index) => filter &= &self.db.#filter_vec_name[*index], + None => filter.fill(false), + }; + } + }) + } FieldInfo::Key(_) => None, }) .collect(); @@ -550,7 +646,7 @@ fn generate_query_struct_impl( /// Execute the query on the database, returning an iterator over the results. #vis fn execute(&self) -> Option<#name_query_result<'a>> { - let mut filter = ::venndb::__internal::bitvec![1; self.db.rows.len()]; + let mut filter = ::venndb::__internal::BitVec::repeat(true, self.db.rows.len()); #(#filters)* @@ -575,7 +671,7 @@ fn generate_query_struct_impl( #[derive(Debug)] enum #name_query_result_kind { Bits(::venndb::__internal::BitVec), - Indices(Vec), + Indices(::std::vec::Vec), } impl<'a> #name_query_result<'a> { @@ -619,7 +715,7 @@ fn generate_query_struct_impl( where F: Fn(&#name) -> bool, { - let indices: Vec = match &self.references { + let indices: ::std::vec::Vec = match &self.references { #name_query_result_kind::Bits(v) => v.iter_ones().filter(|index| predicate(&self.rows[*index])).collect(), #name_query_result_kind::Indices(i) => i.iter().filter(|&index| predicate(&self.rows[*index])).map(|index| *index).collect(), }; diff --git a/venndb-macros/src/parse_attrs.rs b/venndb-macros/src/parse_attrs.rs index 1849704..9e5af75 100644 --- a/venndb-macros/src/parse_attrs.rs +++ b/venndb-macros/src/parse_attrs.rs @@ -11,6 +11,7 @@ pub struct FieldAttrs { pub enum FieldKind { Key, Filter, + FilterMap, } impl FieldAttrs { @@ -19,28 +20,54 @@ impl FieldAttrs { let mut skipped = false; let mut is_key = false; + let mut is_filter = false; for attr in &field.attrs { - let ml = if let Some(ml) = venndb_attr_to_meta_list(errors, attr) { - ml + let ml: Vec<_> = if let Some(ml) = venndb_attr_to_meta_list(errors, attr) { + ml.into_iter().collect() } else { continue; }; - for meta in ml { - let name = meta.path(); - if name.is_ident("key") { - is_key = true; - } else if name.is_ident("skip") { - skipped = true; - } else { - errors.err( - &meta, - concat!( - "Invalid field-level `venndb` attribute\n", - "Expected one of: `key`", - ), - ); + if ml.iter().any(|meta| meta.path().is_ident("skip")) { + // check first to avoid any other invalid combinations + skipped = true; + } else { + for meta in ml { + let name = meta.path(); + if name.is_ident("key") { + if is_filter { + errors.err( + &meta, + concat!( + "Invalid field-level `venndb` attribute\n", + "Cannot have both `key` and `filter`", + ), + ); + } else { + is_key = true; + } + } else if name.is_ident("filter") { + if is_key { + errors.err( + &meta, + concat!( + "Invalid field-level `venndb` attribute\n", + "Cannot have both `key` and `filter`", + ), + ); + } else { + is_filter = true; + } + } else { + errors.err( + &meta, + concat!( + "Invalid field-level `venndb` attribute\n", + "Expected one of: `key`", + ), + ); + } } } } @@ -51,6 +78,9 @@ impl FieldAttrs { this.kind = Some(FieldKind::Key); } else if is_bool(&field.ty) { this.kind = Some(FieldKind::Filter); + } else if is_filter { + // bool filters are to be seen as regular filters, even when made explicitly so! + this.kind = Some(FieldKind::FilterMap); } this diff --git a/venndb-usage/src/main.rs b/venndb-usage/src/main.rs index 9e1b663..4a8977e 100644 --- a/venndb-usage/src/main.rs +++ b/venndb-usage/src/main.rs @@ -11,10 +11,11 @@ pub struct Employee { is_manager: bool, is_admin: bool, is_active: bool, + #[venndb(filter)] department: Department, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum Department { Engineering, Sales, 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 805e5ad..2222872 100644 --- a/venndb-usage/tests/compiles/derive_struct_all_the_things.rs +++ b/venndb-usage/tests/compiles/derive_struct_all_the_things.rs @@ -6,14 +6,16 @@ struct Employee { #[venndb(key)] id: u32, name: String, + #[venndb(filter)] // explicit bool filter == regular bool is_manager: bool, is_admin: bool, #[venndb(skip)] is_active: bool, + #[venndb(filter)] department: Department, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum Department { Engineering, Sales, @@ -30,7 +32,8 @@ fn main() { is_admin: false, is_active: true, department: Department::Engineering, - }).unwrap(); + }) + .unwrap(); let employee_ref = db.get_by_id(&1).unwrap(); assert_eq!(employee_ref.id, 1); diff --git a/venndb-usage/tests/compiles/derive_struct_with_explicit_filters.rs b/venndb-usage/tests/compiles/derive_struct_with_explicit_filters.rs new file mode 100644 index 0000000..d6daa71 --- /dev/null +++ b/venndb-usage/tests/compiles/derive_struct_with_explicit_filters.rs @@ -0,0 +1,27 @@ +use venndb::VennDB; + +#[derive(Debug, VennDB)] +struct Employee { + #[venndb(key)] + id: u32, + name: String, + #[venndb(filter)] + is_manager: bool, + #[venndb(filter)] + is_admin: bool, + #[venndb(filter)] + is_active: bool, + department: Department, +} + +#[derive(Debug)] +pub enum Department { + Engineering, + Sales, + Marketing, + HR, +} + +fn main() { + let _ = EmployeeDB::new(); +} diff --git a/venndb-usage/tests/compiles/derive_struct_with_filter_map.rs b/venndb-usage/tests/compiles/derive_struct_with_filter_map.rs new file mode 100644 index 0000000..e2064b3 --- /dev/null +++ b/venndb-usage/tests/compiles/derive_struct_with_filter_map.rs @@ -0,0 +1,24 @@ +use venndb::VennDB; + +#[derive(Debug, VennDB)] +struct Employee { + id: u32, + name: String, + is_manager: bool, + is_admin: bool, + is_active: bool, + #[venndb(filter)] + department: Department, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub enum Department { + Engineering, + Sales, + Marketing, + HR, +} + +fn main() { + let _ = EmployeeDB::new(); +} diff --git a/venndb-usage/tests/compiles/filter_and_key_and_skip.rs b/venndb-usage/tests/compiles/filter_and_key_and_skip.rs new file mode 100644 index 0000000..3271e4d --- /dev/null +++ b/venndb-usage/tests/compiles/filter_and_key_and_skip.rs @@ -0,0 +1,12 @@ +use venndb::VennDB; + +#[derive(Debug, VennDB)] +struct Employee { + id: u32, + is_manager: bool, + is_active: bool, + #[venndb(filter, key, skip)] + country: String, +} + +fn main() {} diff --git a/venndb-usage/tests/fails/filter_and_key.rs b/venndb-usage/tests/fails/filter_and_key.rs new file mode 100644 index 0000000..ec94e6e --- /dev/null +++ b/venndb-usage/tests/fails/filter_and_key.rs @@ -0,0 +1,12 @@ +use venndb::VennDB; + +#[derive(Debug, VennDB)] +struct Employee { + id: u32, + is_manager: bool, + is_active: bool, + #[venndb(filter, key)] + country: String, +} + +fn main() {} diff --git a/venndb-usage/tests/fails/filter_and_key.stderr b/venndb-usage/tests/fails/filter_and_key.stderr new file mode 100644 index 0000000..b700940 --- /dev/null +++ b/venndb-usage/tests/fails/filter_and_key.stderr @@ -0,0 +1,6 @@ +error: Invalid field-level `venndb` attribute + Cannot have both `key` and `filter` + --> tests/fails/filter_and_key.rs:8:22 + | +8 | #[venndb(filter, key)] + | ^^^