diff --git a/Cargo.toml b/Cargo.toml index 299259e..781662f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,5 +20,6 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] +bitvec = "1.0.1" hashbrown = "0.14.3" venndb-macros = { version = "0.1.0", path = "venndb-macros" } diff --git a/src/lib.rs b/src/lib.rs index 3cf5a52..e3103b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,10 @@ pub use venndb_macros::VennDB; #[doc(hidden)] -pub mod internal { +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 hashbrown::HashMap; } diff --git a/venndb-macros/src/field.rs b/venndb-macros/src/field.rs index ab7eb1d..2324552 100644 --- a/venndb-macros/src/field.rs +++ b/venndb-macros/src/field.rs @@ -23,6 +23,7 @@ pub struct StructField<'a> { pub enum FieldInfo<'a> { Key(KeyField<'a>), + Filter(FilterField<'a>), } pub struct KeyField<'a> { @@ -48,6 +49,24 @@ impl<'a> KeyField<'a> { } } +pub struct FilterField<'a> { + pub name: &'a Ident, +} + +impl<'a> FilterField<'a> { + pub fn name(&'a self) -> &'a Ident { + self.name + } + + pub fn filter_name(&self) -> Ident { + format_ident!("filter_{}", self.name) + } + + pub fn filter_not_name(&self) -> Ident { + format_ident!("filter_not_{}", self.name) + } +} + impl<'a> StructField<'a> { /// Attempts to parse a field of a `#[derive(VennDB)]` struct, pulling out the /// fields required for code generation. @@ -63,6 +82,7 @@ impl<'a> StructField<'a> { name: self.name, ty: &self.field.ty, }), + FieldKind::Filter => FieldInfo::Filter(FilterField { name: self.name }), }) } } diff --git a/venndb-macros/src/generate_db.rs b/venndb-macros/src/generate_db.rs index e84f9c7..32fbefd 100644 --- a/venndb-macros/src/generate_db.rs +++ b/venndb-macros/src/generate_db.rs @@ -1,6 +1,6 @@ use crate::field::{FieldInfo, StructField}; use proc_macro2::TokenStream; -use quote::quote; +use quote::{format_ident, quote}; use syn::Ident; /// Generate the venndb logic @@ -16,7 +16,6 @@ pub fn generate_db( let db_struct_methods = generate_db_struct_methods(name, name_db, vis, &fields[..]); let db_query = generate_query_struct(name, name_db, vis, &fields[..]); - let db_query_methods = generate_query_struct_methods(name, name_db, vis, &fields[..]); quote! { #db_struct @@ -24,8 +23,6 @@ pub fn generate_db( #db_struct_methods #db_query - - #db_query_methods } } @@ -42,7 +39,15 @@ fn generate_db_struct( let field_name = field.map_name(); let ty: &syn::Type = field.ty(); quote! { - #field_name: ::venndb::internal::HashMap<#ty, usize>, + #field_name: ::venndb::__internal::HashMap<#ty, usize>, + } + } + FieldInfo::Filter(field) => { + let field_name = field.filter_name(); + let field_name_not = field.filter_not_name(); + quote! { + #field_name: ::venndb::__internal::BitVec, + #field_name_not: ::venndb::__internal::BitVec, } } }) @@ -54,6 +59,7 @@ fn generate_db_struct( ); quote! { #[doc=#db_doc] + #[derive(Debug, Default)] #vis struct #name_db { rows: Vec<#name>, #(#db_fields)* @@ -69,7 +75,7 @@ fn generate_db_struct_methods( ) -> TokenStream { let method_new = generate_db_struct_method_new(name, name_db, vis, fields); let method_with_capacity = generate_db_struct_method_with_capacity(name, name_db, vis, fields); - let key_methods = generate_db_struct_methods_key(name, name_db, vis, fields); + let field_methods = generate_db_struct_field_methods(name, name_db, vis, fields); let method_append = generate_db_struct_method_append(name, name_db, vis, fields); quote! { @@ -94,16 +100,10 @@ fn generate_db_struct_methods( self.rows.is_empty() } - #key_methods + #field_methods #method_append } - - impl Default for #name_db { - fn default() -> Self { - Self::new() - } - } } } @@ -124,7 +124,15 @@ pub fn generate_db_struct_method_new( FieldInfo::Key(field) => { let name = field.map_name(); quote! { - #name: ::venndb::internal::HashMap::new(), + #name: ::venndb::__internal::HashMap::new(), + } + } + FieldInfo::Filter(field) => { + let name = field.filter_name(); + let name_not = field.filter_not_name(); + quote! { + #name: ::venndb::__internal::BitVec::new(), + #name_not: ::venndb::__internal::BitVec::new(), } } }) @@ -141,23 +149,32 @@ pub fn generate_db_struct_method_new( } } -pub fn generate_db_struct_method_append( +pub fn generate_db_struct_method_with_capacity( name: &Ident, _name_db: &Ident, vis: &syn::Visibility, fields: &[FieldInfo], ) -> TokenStream { - let method_doc = format!("Append a new instance of [`{}`] to the database.", name); + let method_doc = format!( + "Construct a new empty database for storing instances of [`{}`] with a given capacity.", + name + ); - let db_field_inserts: Vec<_> = fields + let db_fields_initialisers_with_capacity: Vec<_> = fields .iter() .map(|info| match info { FieldInfo::Key(field) => { - let map_name = field.map_name(); - let field_name = field.name(); - + let name = field.map_name(); quote! { - self.#map_name.insert(data.#field_name.clone(), index); + #name: ::venndb::__internal::HashMap::with_capacity(capacity), + } + } + FieldInfo::Filter(field) => { + let name = field.filter_name(); + let name_not = field.filter_not_name(); + quote! { + #name: ::venndb::__internal::BitVec::with_capacity(capacity), + #name_not: ::venndb::__internal::BitVec::with_capacity(capacity), } } }) @@ -165,38 +182,41 @@ pub fn generate_db_struct_method_append( quote! { #[doc=#method_doc] - #vis fn append(&mut self, data: #name) { - let index = self.rows.len(); - - if index == self.rows.capacity() { - // TODO: double the bitvecs in size... + #vis fn with_capacity(capacity: usize) -> Self { + Self { + rows: Vec::new(), + #(#db_fields_initialisers_with_capacity)* } - - #(#db_field_inserts)* - - self.rows.push(data); } } } -pub fn generate_db_struct_method_with_capacity( +pub fn generate_db_struct_method_append( name: &Ident, _name_db: &Ident, vis: &syn::Visibility, fields: &[FieldInfo], ) -> TokenStream { - let method_doc = format!( - "Construct a new empty database for storing instances of [`{}`] with a given capacity.", - name - ); + let method_doc = format!("Append a new instance of [`{}`] to the database.", name); - let db_fields_initialisers_with_capacity: Vec<_> = fields + let db_field_inserts: Vec<_> = fields .iter() .map(|info| match info { FieldInfo::Key(field) => { - let name = field.map_name(); + let map_name = field.map_name(); + let field_name = field.name(); + quote! { - #name: ::venndb::internal::HashMap::with_capacity(capacity), + self.#map_name.insert(data.#field_name.clone(), index); + } + } + FieldInfo::Filter(field) => { + let name = field.name(); + let field_name = field.filter_name(); + let field_name_not = field.filter_not_name(); + quote! { + self.#field_name.push(data.#name); + self.#field_name_not.push(!data.#name); } } }) @@ -204,16 +224,17 @@ pub fn generate_db_struct_method_with_capacity( quote! { #[doc=#method_doc] - #vis fn with_capacity(capacity: usize) -> Self { - Self { - rows: Vec::new(), - #(#db_fields_initialisers_with_capacity)* - } + #vis fn append(&mut self, data: #name) { + let index = self.rows.len(); + + #(#db_field_inserts)* + + self.rows.push(data); } } } -pub fn generate_db_struct_methods_key( +pub fn generate_db_struct_field_methods( name: &Ident, _name_db: &Ident, vis: &syn::Visibility, @@ -221,12 +242,18 @@ pub fn generate_db_struct_methods_key( ) -> TokenStream { let db_key_methods: Vec<_> = fields .iter() - .map(|info| match info { + .filter_map(|info| match info { FieldInfo::Key(field) => { let map_name = field.map_name(); let ty = field.ty(); let method_name = field.method_name(); - quote! { + let doc = format!( + "Get an instance of [`{}`] by its key `{}`, if it exists in the database.", + name, + field.name() + ); + Some(quote! { + #[doc=#doc] #vis fn #method_name(&self, key: &Q) -> ::std::option::Option<&#name> where #ty: ::std::borrow::Borrow, @@ -234,8 +261,9 @@ pub fn generate_db_struct_methods_key( { self.#map_name.get(key).and_then(|index| self.rows.get(*index)) } - } + }) } + FieldInfo::Filter(_) => None, }) .collect(); @@ -245,19 +273,189 @@ pub fn generate_db_struct_methods_key( } fn generate_query_struct( - _name: &Ident, - _name_db: &Ident, - _vis: &syn::Visibility, - _fields: &[FieldInfo], + name: &Ident, + name_db: &Ident, + vis: &syn::Visibility, + fields: &[FieldInfo], ) -> TokenStream { - TokenStream::new() // TOOD + let name_query = format_ident!("{}Query", name_db); + + let query_fields: Vec<_> = fields + .iter() + .filter_map(|info| match info { + FieldInfo::Filter(field) => { + let name = field.name(); + Some(quote! { + #name: Option, + }) + } + FieldInfo::Key(_) => None, + }) + .collect(); + + if query_fields.is_empty() { + return TokenStream::new(); + } + + let query_field_initialisers: Vec<_> = fields + .iter() + .filter_map(|info| match info { + FieldInfo::Filter(field) => { + let name = field.name(); + Some(quote! { + #name: None, + }) + } + FieldInfo::Key(_) => None, + }) + .collect(); + + let query_impl = generate_query_struct_impl(name, name_db, &name_query, vis, fields); + + let query_doc = format!( + "A query object for filtering instances of [`{}`], within [`{}`], generated by `#[derive(VennDB)]`.", + name, name_db + ); + + quote! { + #[doc=#query_doc] + #[derive(Debug)] + #vis struct #name_query<'a> { + db: &'a #name_db, + #(#query_fields)* + } + + impl<'a> #name_query<'a> { + fn new(db: &'a #name_db) -> Self { + Self { + db, + #(#query_field_initialisers)* + } + } + } + + #query_impl + + impl #name_db { + #vis fn query(&self) -> #name_query { + #name_query::new(&self) + } + } + } } -fn generate_query_struct_methods( - _name: &Ident, +fn generate_query_struct_impl( + name: &Ident, _name_db: &Ident, - _vis: &syn::Visibility, - _fields: &[FieldInfo], + name_query: &Ident, + vis: &syn::Visibility, + fields: &[FieldInfo], ) -> TokenStream { - TokenStream::new() // TOOD + let filter_setters: Vec<_> = fields + .iter() + .filter_map(|info| match info { + FieldInfo::Filter(field) => { + let name = field.name(); + let doc = format!("Enable and set the `{}` filter.", name); + Some(quote! { + #[doc=#doc] + #vis fn #name(&mut self, value: bool) -> &mut Self { + self.#name = Some(value); + self + } + }) + } + FieldInfo::Key(_) => None, + }) + .collect(); + + let filters: Vec<_> = fields + .iter() + .filter_map(|info| match info { + FieldInfo::Filter(field) => { + let name = field.name(); + let filter_name: Ident = field.filter_name(); + let filter_not_name: Ident = field.filter_not_name(); + Some(quote! { + match self.#name { + Some(true) => filter &= &self.db.#filter_name, + Some(false) => filter &= &self.db.#filter_not_name, + None => (), + }; + }) + } + FieldInfo::Key(_) => None, + }) + .collect(); + + let name_query_result = format_ident!("{}Result", name_query); + + let name_query_result_doc = format!( + "Contains a reference to the found instances of [`{}`] if there is at least one found, queried using [`{}`], generated by `#[derive(VennDB)]`.", + name, name_query + ); + + let name_query_result_iter = format_ident!("{}Iter", name_query_result); + + let name_query_result_iter_doc = format!( + "An iterator over the found instances of [`{}`] queried using [`{}`], generated by `#[derive(VennDB)]`.", + name, name_query + ); + + quote! { + impl<'a> #name_query<'a> { + #(#filter_setters)* + + /// Execute the query on the database, returning an iterator over the results. + #vis fn exec(&self) -> Option<#name_query_result<'a>> { + let mut filter = ::venndb::__internal::bitvec![1; self.db.rows.len()]; + + #(#filters)* + + if filter.any() { + Some(#name_query_result { + rows: &self.db.rows, + v: filter, + }) + } else { + None + } + } + } + + #[doc=#name_query_result_doc] + #[derive(Debug)] + #vis struct #name_query_result<'a> { + rows: &'a [#name], + v: ::venndb::__internal::BitVec, + } + + impl<'a> #name_query_result<'a> { + #vis fn first(&self) -> &'a #name { + let index = self.v.iter_ones().next().expect("should contains at least one result"); + &self.rows[index] + } + + #vis fn iter(&self) -> #name_query_result_iter<'a, '_> { + #name_query_result_iter { + rows: self.rows, + iter_ones: self.v.iter_ones(), + } + } + } + + #[doc=#name_query_result_iter_doc] + #vis struct #name_query_result_iter<'a, 'b> { + rows: &'a [#name], + iter_ones: ::venndb::__internal::IterOnes<'b, usize, ::venndb::__internal::Lsb0>, + } + + impl<'a, 'b> Iterator for #name_query_result_iter<'a, 'b> { + type Item = &'a #name; + + fn next(&mut self) -> Option { + self.iter_ones.next().map(|index| &self.rows[index]) + } + } + } } diff --git a/venndb-macros/src/parse_attrs.rs b/venndb-macros/src/parse_attrs.rs index 1d7b306..1849704 100644 --- a/venndb-macros/src/parse_attrs.rs +++ b/venndb-macros/src/parse_attrs.rs @@ -1,3 +1,5 @@ +use quote::ToTokens; + use crate::errors::Errors; /// Attributes applied to a field of a `#![derive(VennDB)]` struct. @@ -8,12 +10,16 @@ pub struct FieldAttrs { pub enum FieldKind { Key, + Filter, } impl FieldAttrs { pub fn parse(errors: &Errors, field: &syn::Field) -> Self { let mut this = Self::default(); + let mut skipped = false; + let mut is_key = false; + for attr in &field.attrs { let ml = if let Some(ml) = venndb_attr_to_meta_list(errors, attr) { ml @@ -21,9 +27,6 @@ impl FieldAttrs { continue; }; - let mut skipped = false; - let mut is_key = false; - for meta in ml { let name = meta.path(); if name.is_ident("key") { @@ -40,18 +43,34 @@ impl FieldAttrs { ); } } + } - if skipped { - this.kind = None; - } else if is_key { - this.kind = Some(FieldKind::Key); - } + if skipped { + this.kind = None; + } else if is_key { + this.kind = Some(FieldKind::Key); + } else if is_bool(&field.ty) { + this.kind = Some(FieldKind::Filter); } this } } +fn is_bool(ty: &syn::Type) -> bool { + if let syn::Type::Path(syn::TypePath { path, .. }) = ty { + path.is_ident("bool") + } else { + if ty.to_token_stream().to_string().contains("bool") { + panic!( + "Expected bool, found {:?}", + ty.to_token_stream().to_string() + ); + } + false + } +} + /// Represents a `#[derive(VennDB)]` type's top-level attributes. #[derive(Default)] pub struct TypeAttrs { diff --git a/venndb-usage/src/main.rs b/venndb-usage/src/main.rs index 101e28b..fab3201 100644 --- a/venndb-usage/src/main.rs +++ b/venndb-usage/src/main.rs @@ -70,4 +70,48 @@ mod tests { assert_eq!(employee.id, 1); assert_eq!(employee.name, "Alice"); } + + #[test] + fn test_employee_query_filters() { + let mut db = EmployeeDB::default(); + + db.append(Employee { + id: 1, + name: "Alice".to_string(), + is_manager: true, + is_admin: false, + is_active: true, + department: Department::Engineering, + }); + db.append(Employee { + id: 2, + name: "Bob".to_string(), + is_manager: false, + is_admin: false, + is_active: true, + department: Department::Engineering, + }); + db.append(Employee { + id: 3, + name: "Charlie".to_string(), + is_manager: true, + is_admin: true, + is_active: true, + department: Department::Sales, + }); + + let mut query = db.query(); + let results: Vec<_> = query + .is_manager(true) + .is_admin(true) + .exec() + .unwrap() + .iter() + .collect(); + assert_eq!(results.len(), 1); + assert_eq!(results[0].id, 3); + + let mut query = db.query(); + assert!(query.is_active(false).exec().is_none()); + } }