From 7f1ce3d871f63986d8f134a5915a9463f5bde66a Mon Sep 17 00:00:00 2001 From: Jeff Baskin Date: Sat, 13 Sep 2025 12:45:20 -0400 Subject: [PATCH] added indexes to the mix. --- src/message.rs | 216 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) diff --git a/src/message.rs b/src/message.rs index 286f63d..5d07c65 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1556,6 +1556,35 @@ enum Operand { Equal, } +impl Operand { + fn check(&self, x: &Field, y: &Field) -> bool { + match self { + Self::Equal => x == y, + } + } +} + +#[cfg(test)] +mod operands { + use super::*; + + #[test] + fn equals_true() { + let data: Field = Uuid::new_v4().into(); + assert!(Operand::Equal.check(&data, &data)); + } + + #[test] + fn equals_false() { + let x: Field = Uuid::new_v4().into(); + let mut y: Field = Uuid::new_v4().into(); + while x == y { + y = Uuid::new_v4().into(); + } + assert!(!Operand::Equal.check(&x, &y)); + } +} + #[derive(Clone, Debug)] struct Specifier { field_name: String, @@ -1574,6 +1603,14 @@ impl Specifier { value: value.into(), } } + + fn which_field(&self) -> String { + self.field_name.clone() + } + + fn check(&self, field: &Field) -> bool { + self.operation.check(field, &self.value) + } } #[derive(Clone, Debug)] @@ -1774,6 +1811,141 @@ impl Oid { } } +struct Index { + data: HashMap>, +} + +impl Index { + fn new() -> Self { + Self { + data: HashMap::new(), + } + } + + fn add(&mut self, field: Field, oid: Oid) { + let oids = match self.data.get_mut(&field) { + Some(data) => data, + None => { + let mut data = HashSet::new(); + self.data.insert(field.clone(), data); + self.data.get_mut(&field).unwrap() + } + }; + oids.insert(oid); + } + + fn get(&self, spec: &Specifier) -> Vec { + let mut output = Vec::new(); + for (field, oids) in self.data.iter() { + if spec.check(field) { + for oid in oids.iter() { + output.push(oid.clone()); + } + } + } + output + } + + fn remove(&mut self, field: &Field, oid: &Oid) { + match self.data.get_mut(field) { + Some(oids) => { + oids.remove(oid); + if oids.len() == 0 { + self.data.remove(field); + } + } + None => {} + }; + } +} + +#[cfg(test)] +mod indexes { + use super::*; + + fn get_fields(count: usize) -> Vec { + let mut output = Vec::new(); + while output.len() < count { + let field: Field = Uuid::new_v4().into(); + if !output.contains(&field) { + output.push(field); + } + } + output + } + + fn get_oids(count: usize) -> Vec { + let mut output = Vec::new(); + while output.len() < count { + let oid = Oid::new(); + if !output.contains(&oid) { + output.push(oid); + } + } + output + } + + #[test] + fn add_to_index() { + let mut index = Index::new(); + let count = 3; + let fields = get_fields(count); + let oids = get_oids(count); + for i in 0..count { + index.add(fields[i].clone(), oids[i].clone()); + } + for i in 0..count { + let spec = Specifier::new("stuff".to_string(), Operand::Equal, fields[i].clone()); + let result = index.get(&spec); + assert_eq!(result.len(), 1); + assert_eq!(result[0], oids[i]); + } + } + + #[test] + fn index_can_handle_multiple_entries() { + let mut index = Index::new(); + let count = 3; + let fields = get_fields(1); + let oids = get_oids(count); + for i in 0..count { + index.add(fields[0].clone(), oids[i].clone()); + } + let spec = Specifier::new("unimportant".to_string(), Operand::Equal, fields[0].clone()); + let result = index.get(&spec); + assert_eq!(result.len(), 3); + for oid in oids { + assert!(result.contains(&oid)); + } + } + + #[test] + fn can_remove_oid() { + let mut index = Index::new(); + let count = 3; + let pos = 1; + let fields = get_fields(1); + let oids = get_oids(count); + for i in 0..count { + index.add(fields[0].clone(), oids[i].clone()); + } + index.remove(&fields[0], &oids[pos]); + let spec = Specifier::new("x".to_string(), Operand::Equal, fields[0].clone()); + let result = index.get(&spec); + assert!(!result.contains(&oids[pos]), "should have removed oid"); + } + + #[test] + fn are_empty_indexes_removed() { + let mut index = Index::new(); + let field: Field = Uuid::new_v4().into(); + let oid = Oid::new(); + index.add(field.clone(), oid.clone()); + index.remove(&field, &oid); + assert_eq!(index.data.len(), 0); + } +} + struct DocumentFile { docdef: DocDef, docs: HashMap, @@ -2871,6 +3043,50 @@ mod document_files { _ => unreachable!("got {:?}: should have gotten a reply", action), } } + + // #[test] + fn unique_available_after_bad_change() { + let mut ids: Vec = Vec::new(); + while ids.len() < 3 { + let id = Uuid::new_v4(); + if !ids.contains(&id) { + ids.push(id); + } + } + let (mut docdef, doc_name) = + create_docdef([FieldType::Uuid, FieldType::StaticString].to_vec()); + docdef.set_unique("field0"); + let (queue, rx) = test_doc(doc_name.as_str(), docdef, standard_routes()); + let field1 = "fred"; + for index in 0..2 { + let mut addition = Addition::new(); + addition.add_field("field0".to_string(), ids[index].clone()); + addition.add_field("field1".to_string(), field1); + let msg = Message::new(doc_name.clone(), addition.clone()); + queue.send(msg).unwrap(); + rx.recv_timeout(TIMEOUT).unwrap(); + } + let mut update = Update::new(); + let query = update.get_query_mut(); + query.add_specifier("field1".to_string(), Operand::Equal, field1); + let values = update.get_values_mut(); + values.add_field("field0".to_string(), ids[2].clone()); + let msg = Message::new(doc_name.clone(), update); + queue.send(msg).unwrap(); + let result = rx.recv_timeout(TIMEOUT).unwrap(); + let action = result.get_action(); + match action { + MsgAction::Error(err) => match err { + MTTError::FieldDuplicate(key, field) => { + let expected: Field = ids[2].into(); + assert_eq!(key, "field0"); + assert_eq!(field, &expected); + } + _ => unreachable!("got {:?}: should have gotten an missing field", err), + }, + _ => unreachable!("got {:?}: should have gotten an error", action), + } + } } #[cfg(test)]