diff --git a/venndb-macros/src/generate_db.rs b/venndb-macros/src/generate_db.rs index 77e23aa..ad8f98c 100644 --- a/venndb-macros/src/generate_db.rs +++ b/venndb-macros/src/generate_db.rs @@ -299,6 +299,10 @@ fn generate_db_struct_method_append( fields: &[FieldInfo], ) -> TokenStream { let method_doc = format!("Append a new instance of [`{}`] to the database.", name); + let method_iter_doc = format!( + "Extend the database with the given iterator of items that can be turned into [`{}`] instances.", + name + ); let db_field_insert_checks: Vec<_> = fields .iter() @@ -368,6 +372,8 @@ fn generate_db_struct_method_append( .collect(); let append_return_type = db_error.generate_fn_output(name_db, quote! { #name }, quote! { () }); + let extend_return_type = + db_error.generate_fn_output(name_db, quote! { (#name, I::IntoIter) }, quote! { () }); let append_kind_return_type = db_error.generate_fn_kind_output(name_db, quote! { () }); let append_internal_call = db_error.generate_fn_error_kind_usage( @@ -378,6 +384,14 @@ fn generate_db_struct_method_append( quote! { data }, ); + let extend_append_internal_call = db_error.generate_fn_error_kind_usage( + name_db, + quote! { + self.append_internal(&data, index) + }, + quote! { (data, iter) }, + ); + let append_return_output = db_error.generate_fn_return_value_ok(quote! { () }); quote! { @@ -390,6 +404,23 @@ fn generate_db_struct_method_append( #append_return_output } + #[doc=#method_iter_doc] + #vis fn extend(&mut self, iter: I) -> #extend_return_type + where + I: ::std::iter::IntoIterator, + Item: ::std::convert::Into<#name>, + { + let mut index = self.rows.len(); + let mut iter = iter.into_iter(); + for item in &mut iter { + let data = item.into(); + #extend_append_internal_call + self.rows.push(data); + index += 1; + } + #append_return_output + } + fn append_internal(&mut self, data: &#name, index: usize) -> #append_kind_return_type { #(#db_field_insert_checks)* #(#db_field_insert_commits)* @@ -595,6 +626,10 @@ fn generate_query_struct_impl( let filter_name: Ident = field.filter_name(); let filter_not_name: Ident = field.filter_not_name(); Some(quote! { + // Filter by the filter below. Only if it is defined as Some(_). + // Using negation if negation is desired, and + // the regular filter otherwise. + match self.#name { Some(true) => filter &= &self.db.#filter_name, Some(false) => filter &= &self.db.#filter_not_name, @@ -607,10 +642,14 @@ fn generate_query_struct_impl( let filter_map_name: Ident = field.filter_map_name(); let filter_vec_name: Ident = field.filter_vec_name(); 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 { match self.db.#filter_map_name.get(value) { Some(index) => filter &= &self.db.#filter_vec_name[*index], - None => filter.fill(false), + None => return None, }; } }) diff --git a/venndb-usage/src/main.rs b/venndb-usage/src/main.rs index 9c17408..e3dd3ae 100644 --- a/venndb-usage/src/main.rs +++ b/venndb-usage/src/main.rs @@ -115,6 +115,79 @@ mod tests { assert_eq!(employee.department, Department::Engineering); } + #[test] + fn test_extend() { + let mut db = EmployeeDB::default(); + assert_eq!(db.len(), 0); + assert!(db.get_by_id(&1).is_none()); + assert!(db.get_by_id(&2).is_none()); + assert!(db.is_empty()); + + db.extend(vec![ + L1Engineer { + id: 1, + name: "Alice".to_string(), + }, + L1Engineer { + id: 2, + name: "Bob".to_string(), + }, + ]) + .unwrap(); + assert_eq!(db.len(), 2); + + let employee: &Employee = db.get_by_id(&1).unwrap(); + assert_eq!(employee.id, 1); + assert_eq!(employee.name, "Alice"); + + let employee: &Employee = db.get_by_id(&2).unwrap(); + assert_eq!(employee.id, 2); + assert_eq!(employee.name, "Bob"); + } + + #[test] + fn test_extend_duplicate_key() { + let mut db = EmployeeDB::default(); + db.extend(vec![ + L1Engineer { + id: 1, + name: "Alice".to_string(), + }, + L1Engineer { + id: 2, + name: "Bob".to_string(), + }, + ]) + .unwrap(); + assert_eq!(db.len(), 2); + + let err = db + .extend(vec![ + L1Engineer { + id: 2, + name: "Charlie".to_string(), + }, + L1Engineer { + id: 3, + name: "David".to_string(), + }, + ]) + .unwrap_err(); + assert_eq!(EmployeeDBErrorKind::DuplicateKey, err.kind()); + + let (dup_employee, employee_iter) = err.into_input(); + assert_eq!(dup_employee.id, 2); + assert_eq!(dup_employee.name, "Charlie"); + + let employees: Vec<_> = employee_iter.collect(); + assert_eq!(employees.len(), 1); + assert_eq!(employees[0].id, 3); + + db.extend(employees).unwrap(); + assert_eq!(db.len(), 3); + assert_eq!(db.get_by_id(&3).unwrap().name, "David"); + } + #[test] fn test_employee_query_filters() { let mut db = EmployeeDB::default(); @@ -278,6 +351,35 @@ mod tests { assert_eq!(rows[1].id, 2); } + #[test] + fn test_from_rows_duplicate_key() { + let err = 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: 1, + name: "Bob".to_string(), + is_manager: false, + is_admin: false, + is_active: true, + department: Department::Engineering, + }, + ]) + .unwrap_err(); + assert_eq!(EmployeeDBErrorKind::DuplicateKey, err.kind()); + + let employees = err.into_input(); + assert_eq!(employees.len(), 2); + assert_eq!(employees[0].name, "Alice"); + assert_eq!(employees[1].name, "Bob"); + } + #[test] fn test_from_iter() { let db = EmployeeDB::from_iter([ @@ -309,6 +411,27 @@ mod tests { assert_eq!(results[1].id, 2); } + #[test] + fn test_from_iter_duplicate_key() { + let err = EmployeeDB::from_iter([ + L1Engineer { + id: 1, + name: "Alice".to_string(), + }, + L1Engineer { + id: 1, + name: "Bob".to_string(), + }, + ]) + .unwrap_err(); + assert_eq!(EmployeeDBErrorKind::DuplicateKey, err.kind()); + + let employees = err.into_input(); + assert_eq!(employees.len(), 2); + assert_eq!(employees[0].name, "Alice"); + assert_eq!(employees[1].name, "Bob"); + } + #[test] fn test_query_reset() { let mut db = EmployeeDB::default();