diff --git a/src/message.rs b/src/message.rs index 77d9d70..10bb9d5 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1395,6 +1395,7 @@ impl DocDef { fn field_ids(&self) -> HashSet<&String> { self.fields.keys().collect::>() + //self.fields.keys().cloned().collect::>() } fn validate(&self, field_name: &str, value: Option) -> Result { @@ -2089,6 +2090,7 @@ impl Query { fn field_ids(&self) -> HashSet<&String> { self.data.keys().collect::>() + //self.data.keys().cloned().collect::>() } } @@ -2417,6 +2419,18 @@ impl Index { output } + fn pull(&self, calc: &Calculation) -> HashSet { + let mut output = HashSet::new(); + for (key, value) in self.data.iter() { + let mut op = calc.clone(); + op.add_value(key.clone()); + if op.calculate() == true.into() { + output = output.union(&value).cloned().collect(); + } + } + output + } + fn remove(&mut self, field: &Field, oid: &Oid) { match self.data.get_mut(field) { Some(oids) => { @@ -2453,6 +2467,18 @@ impl Indexes { Self { data: output } } + fn index_ids(&self) -> HashSet<&String> { + self.data.keys().collect::>() + } + + fn get_index(&self, field_id: &str) -> &Index { + self.data.get(field_id).unwrap() + } + + fn pull(&self, field_id: &str, calc: &Calculation) -> HashSet { + self.get_index(field_id).pull(calc) + } + fn add_to_index(&mut self, field_name: &str, field: Field, oid: Oid) { let index = match self.data.get_mut(field_name) { Some(data) => data, @@ -2770,29 +2796,64 @@ impl DocumentFile { fn run_query(&self, query: &Query) -> Result, MTTError> { let query_ids = query.field_ids(); let doc_ids = self.docdef.field_ids(); + let index_ids = self.indexes.index_ids(); if !doc_ids.is_superset(&query_ids) { let missed = query_ids.difference(&doc_ids).last().unwrap(); return Err(MTTError::DocumentFieldNotFound(missed.to_string())); } - let mut oids: HashSet = HashSet::new(); - 'docs: for (oid, doc) in self.docs.iter() { - for query_id in query_ids.iter() { - let doc_data = doc.get_field(query_id).unwrap(); - let mut operation = query.get(query_id).unwrap(); - match operation.add_value(doc_data.clone()) { - Ok(_) => {} - Err(err) => match err { - MTTError::DocumentFieldWrongDataType(got, expected) => { - return Err(MTTError::DocumentFieldWrongDataType(expected, got)) - } - _ => return Err(err), - }, - } - if operation.calculate() == false.into() { - continue 'docs; + let used_indexed = index_ids + .intersection(&query_ids) + .cloned() + .collect::>(); + let used_unindexed = query_ids + .difference(&index_ids) + .cloned() + .collect::>(); + let mut oids = HashSet::new(); + if used_indexed.is_empty() { + 'docs: for (oid, doc) in self.docs.iter() { + for query_id in query_ids.iter() { + let doc_data = doc.get_field(query_id).unwrap(); + let mut operation = query.get(query_id).unwrap(); + match operation.add_value(doc_data.clone()) { + Ok(_) => {} + Err(err) => match err { + MTTError::DocumentFieldWrongDataType(got, expected) => { + return Err(MTTError::DocumentFieldWrongDataType(expected, got)) + } + _ => return Err(err), + }, + } + if operation.calculate() == false.into() { + continue 'docs; + } } + oids.insert(oid.clone()); + } + } else { + let mut first_time = true; + for field_id in used_indexed.iter() { + let op = query.get(field_id).unwrap(); + let holder = self.indexes.pull(field_id, &op); + if first_time { + oids = holder; + } else { + oids = oids.intersection(&holder).cloned().collect(); + } + first_time = false; + } + for field_id in used_unindexed.iter() { + let mut holder: HashSet = HashSet::new(); + for oid in oids.iter() { + let doc = self.docs.get(oid).unwrap(); + let mut op = query.get(field_id).unwrap().clone(); + op.add_value(doc.get_field(field_id).unwrap()); + if op.calculate() == true.into() { + holder.insert(oid.clone()); + } + } + oids = oids.intersection(&holder).cloned().collect(); } - oids.insert(oid.clone()); } Ok(oids) } @@ -3322,6 +3383,87 @@ mod document_files { } } + #[test] + fn query_should_work_with_multiple_inexed_fields() { + let mut doc = + TestDocument::new([FieldType::StaticString, FieldType::StaticString].to_vec()); + let docdef = doc.get_docdef_mut(); + docdef.add_index("field0".to_string(), IndexType::Index); + docdef.add_index("field1".to_string(), IndexType::Index); + doc.start(); + let values = [ + ["a".into(), "a".into()].to_vec(), + ["a".into(), "b".into()].to_vec(), + ["b".into(), "a".into()].to_vec(), + ["b".into(), "b".into()].to_vec(), + ]; + for value in values.iter() { + doc.populate(value.clone()); + } + let mut query = Query::new(); + let mut calc = Calculation::new(Operand::Equal); + calc.add_value("a"); + query.add("field0".to_string(), calc); + let mut calc = Calculation::new(Operand::Equal); + calc.add_value("b"); + query.add("field1".to_string(), calc); + doc.send(query).unwrap(); + let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap(); + let action = result.get_action(); + match action { + MsgAction::Reply(data) => { + let afield: Field = "a".into(); + let bfield: Field = "b".into(); + assert_eq!(data.len(), 1, "should return one entry:\n{:?}", action); + for doc in data.iter() { + assert_eq!(doc.get_field("field0").unwrap(), afield); + assert_eq!(doc.get_field("field1").unwrap(), bfield); + } + } + _ => unreachable!("got {:?}: should have been a reply", action), + } + } + + #[test] + fn query_should_work_with_mixed_inexed_fields() { + let mut doc = + TestDocument::new([FieldType::StaticString, FieldType::StaticString].to_vec()); + let docdef = doc.get_docdef_mut(); + docdef.add_index("field0".to_string(), IndexType::Index); + doc.start(); + let values = [ + ["a".into(), "a".into()].to_vec(), + ["a".into(), "b".into()].to_vec(), + ["b".into(), "a".into()].to_vec(), + ["b".into(), "b".into()].to_vec(), + ]; + for value in values.iter() { + doc.populate(value.clone()); + } + let mut query = Query::new(); + let mut calc = Calculation::new(Operand::Equal); + calc.add_value("a"); + query.add("field0".to_string(), calc); + let mut calc = Calculation::new(Operand::Equal); + calc.add_value("b"); + query.add("field1".to_string(), calc); + doc.send(query).unwrap(); + let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap(); + let action = result.get_action(); + match action { + MsgAction::Reply(data) => { + let afield: Field = "a".into(); + let bfield: Field = "b".into(); + assert_eq!(data.len(), 1, "should return one entry:\n{:?}", action); + for doc in data.iter() { + assert_eq!(doc.get_field("field0").unwrap(), afield); + assert_eq!(doc.get_field("field1").unwrap(), bfield); + } + } + _ => unreachable!("got {:?}: should have been a reply", action), + } + } + #[test] fn errors_on_bad_field_name() { let (docdef, doc_name) = create_docdef(Vec::new());