diff --git a/Cargo.toml b/Cargo.toml index 781662f..d02e4ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,5 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] bitvec = "1.0.1" hashbrown = "0.14.3" +rand = "0.8.5" venndb-macros = { version = "0.1.0", path = "venndb-macros" } diff --git a/src/lib.rs b/src/lib.rs index 3e54052..93cce10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,13 @@ pub mod __internal { pub use bitvec::{bitvec, order::Lsb0, slice::IterOnes, vec::BitVec}; pub use hashbrown::HashMap; + /// Generate a random `usize`. + pub fn rand_usize() -> usize { + use rand::Rng; + + rand::thread_rng().gen() + } + pub mod hash_map { //! Internal types related to hash map. diff --git a/venndb-macros/src/generate_db.rs b/venndb-macros/src/generate_db.rs index a9ad18f..4e1ab8c 100644 --- a/venndb-macros/src/generate_db.rs +++ b/venndb-macros/src/generate_db.rs @@ -75,6 +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 method_from_rows = generate_db_struct_method_from_rows(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); @@ -84,6 +85,8 @@ fn generate_db_struct_methods( #method_with_capacity + #method_from_rows + /// Return the number of rows in the database. #vis fn len(&self) -> usize { self.rows.len() @@ -103,6 +106,11 @@ fn generate_db_struct_methods( #field_methods #method_append + + /// Consumes the database and returns the rows. + #vis fn into_rows(self) -> Vec<#name> { + self.rows + } } } } @@ -191,6 +199,30 @@ pub fn generate_db_struct_method_with_capacity( } } +pub fn generate_db_struct_method_from_rows( + name: &Ident, + _name_db: &Ident, + vis: &syn::Visibility, + _fields: &[FieldInfo], +) -> TokenStream { + let method_doc = format!( + "Construct a new database from the given set of [`{}`] rows.", + name + ); + + quote! { + #[doc=#method_doc] + #vis fn from_rows(rows: Vec<#name>) -> Self { + let mut db = Self::with_capacity(rows.len()); + for (index, row) in rows.iter().enumerate() { + db.append_internal(row, index); + } + db.rows = rows; + db + } + } +} + pub fn generate_db_struct_method_append( name: &Ident, _name_db: &Ident, @@ -248,11 +280,15 @@ pub fn generate_db_struct_method_append( #vis fn append(&mut self, data: #name) { let index = self.rows.len(); - #(#db_field_insert_checks)* - #(#db_field_insert_commits)* + self.append_internal(&data, index); self.rows.push(data); } + + fn append_internal(&mut self, data: &#name, index: usize) { + #(#db_field_insert_checks)* + #(#db_field_insert_commits)* + } } } @@ -391,6 +427,19 @@ fn generate_query_struct_impl( }) .collect(); + let filter_resetters: Vec<_> = fields + .iter() + .filter_map(|info| match info { + FieldInfo::Filter(field) => { + let name = field.name(); + Some(quote! { + self.#name = None; + }) + } + FieldInfo::Key(_) => None, + }) + .collect(); + let filters: Vec<_> = fields .iter() .filter_map(|info| match info { @@ -428,6 +477,14 @@ fn generate_query_struct_impl( impl<'a> #name_query<'a> { #(#filter_setters)* + /// Reset the query to its initial values. + #vis fn reset(&mut self) -> &mut Self { + #(#filter_resetters)* + self + } + + // TODO: support a filter on the result based on a predicate + /// 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()]; @@ -458,6 +515,12 @@ fn generate_query_struct_impl( &self.rows[index] } + #vis fn any(&self) -> &'a #name { + let n = ::venndb::__internal::rand_usize() % self.v.count_ones(); + let index = self.v.iter_ones().nth(n).unwrap(); + &self.rows[index] + } + #vis fn iter(&self) -> #name_query_result_iter<'a, '_> { #name_query_result_iter { rows: self.rows, diff --git a/venndb-usage/src/main.rs b/venndb-usage/src/main.rs index f4f9705..f9774ae 100644 --- a/venndb-usage/src/main.rs +++ b/venndb-usage/src/main.rs @@ -143,4 +143,121 @@ mod tests { // TODO: add test to ensure that no other keys // have already been inserted! + + #[test] + fn test_into_from_rows() { + let 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::Engineering, + }, + ]; + + let db = EmployeeDB::from_rows(rows); + + assert_eq!(db.len(), 2); + assert_eq!(db.capacity(), 2); + + let mut query = db.query(); + query.is_manager(true); + let results: Vec<_> = query.execute().unwrap().iter().collect(); + assert_eq!(results.len(), 1); + assert_eq!(results[0].id, 1); + + let rows = db.into_rows(); + assert_eq!(rows.len(), 2); + assert_eq!(rows[0].id, 1); + assert_eq!(rows[1].id, 2); + } + + #[test] + fn test_query_reset() { + 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(); + query.is_manager(true); + let results: Vec<_> = query.execute().unwrap().iter().collect(); + assert_eq!(results.len(), 2); + assert_eq!(results[0].id, 1); + assert_eq!(results[1].id, 3); + + query.reset(); + let results: Vec<_> = query.execute().unwrap().iter().collect(); + assert_eq!(results.len(), 3); + assert_eq!(results[0].id, 1); + assert_eq!(results[1].id, 2); + assert_eq!(results[2].id, 3); + } + + #[test] + fn test_query_result_any() { + 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(); + query.is_active(true); + let result = query.execute().unwrap().any(); + assert!(result.id == 1 || result.id == 2 || result.id == 3); + } }