2026-03-21 11:44:49 -04:00
|
|
|
use super::Session;
|
|
|
|
|
use super::{DocFeature, InternalRecord, InternalRecords, Oid};
|
2026-01-09 11:39:14 -05:00
|
|
|
use crate::{
|
2026-02-16 09:53:04 -05:00
|
|
|
action::{Action, CalcValue, Calculation, MsgAction, Query, Records, Reply, Update},
|
2026-01-14 12:03:40 -05:00
|
|
|
document::{
|
|
|
|
|
definition::{DocDef, DocFuncType},
|
|
|
|
|
field::Field,
|
|
|
|
|
},
|
2026-03-26 13:12:17 -04:00
|
|
|
message::{Message, MessageAction},
|
2026-02-12 22:49:19 -05:00
|
|
|
mtterror::{ErrorID, MTTError},
|
2026-02-27 08:36:22 -05:00
|
|
|
name::{NameID, NameType},
|
2026-01-30 13:58:55 -05:00
|
|
|
queue::{
|
|
|
|
|
data_director::{Include, Path, RegMsg, Register, RouteID},
|
|
|
|
|
router::Queue,
|
|
|
|
|
},
|
2026-01-09 11:39:14 -05:00
|
|
|
};
|
|
|
|
|
use std::{
|
|
|
|
|
collections::{HashMap, HashSet},
|
|
|
|
|
sync::mpsc::{channel, Receiver},
|
|
|
|
|
thread::spawn,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub struct CreateDoc {
|
|
|
|
|
queue: Queue,
|
|
|
|
|
rx: Receiver<Message>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CreateDoc {
|
|
|
|
|
fn new(queue: Queue, rx: Receiver<Message>) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
queue: queue,
|
|
|
|
|
rx: rx,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn start(mut queue: Queue) {
|
|
|
|
|
let (tx, rx) = channel();
|
|
|
|
|
let routes = [Path::new(
|
|
|
|
|
Include::All,
|
|
|
|
|
Include::All,
|
|
|
|
|
Include::Just(Action::Create),
|
|
|
|
|
)]
|
|
|
|
|
.to_vec();
|
|
|
|
|
let id = queue.add_sender(tx);
|
|
|
|
|
for route in routes.iter() {
|
|
|
|
|
let regmsg = Register::new(id.clone(), RegMsg::AddRoute(route.clone()));
|
2026-02-18 10:09:55 -05:00
|
|
|
queue.send(Message::new(regmsg));
|
2026-01-09 11:39:14 -05:00
|
|
|
rx.recv().unwrap();
|
|
|
|
|
}
|
|
|
|
|
let doc = CreateDoc::new(queue, rx);
|
|
|
|
|
spawn(move || {
|
|
|
|
|
doc.listen();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn listen(&self) {
|
|
|
|
|
loop {
|
|
|
|
|
let msg = self.rx.recv().unwrap();
|
|
|
|
|
DocumentFile::start(self.queue.clone(), msg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub enum IndexType {
|
|
|
|
|
Index,
|
|
|
|
|
Unique,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl IndexType {
|
|
|
|
|
fn create_index(&self) -> Index {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Index => Index::new(),
|
|
|
|
|
Self::Unique => Index::new_unique(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
struct Index {
|
|
|
|
|
data: HashMap<Field, HashSet<Oid>>,
|
|
|
|
|
unique: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Index {
|
|
|
|
|
fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
data: HashMap::new(),
|
|
|
|
|
unique: false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn new_unique() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
data: HashMap::new(),
|
|
|
|
|
unique: true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn internal_add(&mut self, field: &Field, oid: Oid) {
|
|
|
|
|
let storage = match self.data.get_mut(field) {
|
|
|
|
|
Some(data) => data,
|
|
|
|
|
None => {
|
|
|
|
|
let data = HashSet::new();
|
|
|
|
|
self.data.insert(field.clone(), data);
|
|
|
|
|
self.data.get_mut(field).unwrap()
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
storage.insert(oid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn add(&mut self, field: Field, oid: Oid) -> Result<(), MTTError> {
|
|
|
|
|
let oids = match self.data.get_mut(&field) {
|
|
|
|
|
Some(data) => data,
|
|
|
|
|
None => {
|
|
|
|
|
self.data.insert(field.clone(), HashSet::new());
|
|
|
|
|
self.data.get_mut(&field).unwrap()
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if self.unique && oids.len() > 0 {
|
2026-03-03 13:00:26 -05:00
|
|
|
let err = MTTError::new(ErrorID::IndexEntryAlreadyExists(field.clone()));
|
2026-02-12 22:49:19 -05:00
|
|
|
return Err(err);
|
2026-01-09 11:39:14 -05:00
|
|
|
} else {
|
|
|
|
|
oids.insert(oid);
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn pull(&self, calc: &Calculation) -> Result<HashSet<Oid>, MTTError> {
|
|
|
|
|
let mut output = HashSet::new();
|
|
|
|
|
for (key, value) in self.data.iter() {
|
|
|
|
|
match calc.calculate(key) {
|
|
|
|
|
Field::Boolean(data) => {
|
|
|
|
|
if data {
|
|
|
|
|
output = output.union(&value).cloned().collect();
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-12 22:49:19 -05:00
|
|
|
_ => {
|
2026-02-25 10:16:50 -05:00
|
|
|
let err = MTTError::new(ErrorID::FieldInvalidType);
|
2026-02-12 22:49:19 -05:00
|
|
|
return Err(err);
|
|
|
|
|
}
|
2026-01-09 11:39:14 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(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 => {}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn validate(&self, field: &Field) -> Result<(), MTTError> {
|
|
|
|
|
if self.unique {
|
|
|
|
|
match self.data.get(field) {
|
2026-02-12 22:49:19 -05:00
|
|
|
Some(_) => {
|
2026-03-03 13:00:26 -05:00
|
|
|
let err = MTTError::new(ErrorID::IndexEntryAlreadyExists(field.clone()));
|
2026-02-12 22:49:19 -05:00
|
|
|
return Err(err);
|
|
|
|
|
}
|
2026-01-09 11:39:14 -05:00
|
|
|
None => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Indexes {
|
2026-02-27 08:36:22 -05:00
|
|
|
data: HashMap<NameID, Index>,
|
2026-01-09 11:39:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Indexes {
|
2026-02-27 08:36:22 -05:00
|
|
|
fn new(settings: &HashMap<NameID, IndexType>) -> Self {
|
2026-01-09 11:39:14 -05:00
|
|
|
let mut output = HashMap::new();
|
|
|
|
|
for (key, value) in settings.iter() {
|
|
|
|
|
output.insert(key.clone(), value.create_index());
|
|
|
|
|
}
|
|
|
|
|
Self { data: output }
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 08:36:22 -05:00
|
|
|
fn index_ids(&self) -> HashSet<&NameID> {
|
|
|
|
|
self.data.keys().collect::<HashSet<&NameID>>()
|
2026-01-09 11:39:14 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-27 08:36:22 -05:00
|
|
|
fn pull(&self, field_id: &NameID, calc: &Calculation) -> Result<HashSet<Oid>, MTTError> {
|
2026-01-09 11:39:14 -05:00
|
|
|
self.data.get(field_id).unwrap().pull(calc)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 08:36:22 -05:00
|
|
|
fn add_to_index(
|
|
|
|
|
&mut self,
|
|
|
|
|
field_name: &NameID,
|
|
|
|
|
field: Field,
|
|
|
|
|
oid: Oid,
|
|
|
|
|
) -> Result<(), MTTError> {
|
2026-01-09 11:39:14 -05:00
|
|
|
let index = match self.data.get_mut(field_name) {
|
|
|
|
|
Some(data) => data,
|
|
|
|
|
None => return Ok(()),
|
|
|
|
|
};
|
|
|
|
|
index.add(field, oid)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 08:36:22 -05:00
|
|
|
fn validate(&self, field_name: &NameID, value: &Field) -> Result<(), MTTError> {
|
2026-01-09 11:39:14 -05:00
|
|
|
match self.data.get(field_name) {
|
|
|
|
|
Some(index) => match index.validate(value) {
|
|
|
|
|
Ok(_) => {}
|
|
|
|
|
Err(err) => return Err(err),
|
|
|
|
|
},
|
|
|
|
|
None => {}
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 08:36:22 -05:00
|
|
|
fn iter_mut(&mut self) -> impl Iterator<Item = (&NameID, &mut Index)> {
|
2026-01-09 11:39:14 -05:00
|
|
|
self.data.iter_mut()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod indexes {
|
|
|
|
|
use super::*;
|
2026-02-08 12:57:29 -05:00
|
|
|
use crate::action::{FieldType, Operand};
|
2026-04-23 11:31:52 -04:00
|
|
|
use uuid::Uuid;
|
2026-01-09 11:39:14 -05:00
|
|
|
|
|
|
|
|
fn get_fields(count: usize) -> Vec<Field> {
|
|
|
|
|
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<Oid> {
|
|
|
|
|
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()).unwrap();
|
|
|
|
|
}
|
|
|
|
|
for i in 0..count {
|
|
|
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
|
|
|
calc.add_value(fields[i].clone()).unwrap();
|
|
|
|
|
calc.add_value(CalcValue::Existing(FieldType::Uuid))
|
|
|
|
|
.unwrap();
|
|
|
|
|
let result = index.pull(&calc).unwrap();
|
|
|
|
|
assert_eq!(result.len(), 1);
|
|
|
|
|
assert_eq!(result.iter().last().unwrap(), &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()).unwrap();
|
|
|
|
|
}
|
|
|
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
|
|
|
calc.add_value(fields[0].clone()).unwrap();
|
|
|
|
|
calc.add_value(CalcValue::Existing(FieldType::Uuid))
|
|
|
|
|
.unwrap();
|
|
|
|
|
let result = index.pull(&calc).unwrap();
|
|
|
|
|
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()).unwrap();
|
|
|
|
|
}
|
|
|
|
|
index.remove(&fields[0], &oids[pos]);
|
|
|
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
|
|
|
calc.add_value(fields[0].clone()).unwrap();
|
|
|
|
|
calc.add_value(CalcValue::Existing(FieldType::Uuid))
|
|
|
|
|
.unwrap();
|
|
|
|
|
let result = index.pull(&calc).unwrap();
|
|
|
|
|
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()).unwrap();
|
|
|
|
|
index.remove(&field, &oid);
|
|
|
|
|
assert_eq!(index.data.len(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn do_unique_indexes_error_on_duplicates() {
|
|
|
|
|
let mut index = Index::new_unique();
|
|
|
|
|
let field: Field = "fred".into();
|
|
|
|
|
let oids = get_oids(2);
|
|
|
|
|
index.add(field.clone(), oids[0].clone()).unwrap();
|
|
|
|
|
match index.add(field.clone(), oids[0].clone()) {
|
|
|
|
|
Ok(_) => unreachable!("should have been an error"),
|
2026-02-12 22:49:19 -05:00
|
|
|
Err(err) => {
|
2026-02-25 10:16:50 -05:00
|
|
|
let err_id = err.get_error_ids().back().unwrap();
|
2026-02-12 22:49:19 -05:00
|
|
|
match err_id {
|
2026-03-03 13:00:26 -05:00
|
|
|
ErrorID::IndexEntryAlreadyExists(data) => assert_eq!(data, &field),
|
2026-02-12 22:49:19 -05:00
|
|
|
_ => unreachable!("got {:?}: should have been duplicate field", err),
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-09 11:39:14 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn index_returns_validate() {
|
|
|
|
|
let mut index = Index::new();
|
|
|
|
|
let field: Field = "stuff".into();
|
|
|
|
|
let oid = Oid::new();
|
|
|
|
|
index.add(field.clone(), oid).unwrap();
|
|
|
|
|
match index.validate(&field) {
|
|
|
|
|
Ok(_) => {}
|
|
|
|
|
Err(err) => unreachable!("got {:?}: should have returned without issue", err),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn unique_return_duplicate_error() {
|
|
|
|
|
let mut index = Index::new_unique();
|
|
|
|
|
let field: Field = "fred".into();
|
|
|
|
|
let oid = Oid::new();
|
|
|
|
|
index.add(field.clone(), oid).unwrap();
|
|
|
|
|
match index.validate(&field) {
|
|
|
|
|
Ok(_) => unreachable!("should have gotten a duplication error"),
|
2026-02-25 10:16:50 -05:00
|
|
|
Err(err) => match err.get_error_ids().back().unwrap() {
|
2026-03-03 13:00:26 -05:00
|
|
|
ErrorID::IndexEntryAlreadyExists(data) => assert_eq!(data, &field),
|
2026-01-09 11:39:14 -05:00
|
|
|
_ => unreachable!("got {:?}: should have been duplicate field", err),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DocumentFile {
|
|
|
|
|
docdef: DocDef,
|
|
|
|
|
docs: InternalRecords,
|
|
|
|
|
indexes: Indexes,
|
2026-02-27 08:36:22 -05:00
|
|
|
name_id: NameID,
|
2026-01-09 11:39:14 -05:00
|
|
|
queue: Queue,
|
|
|
|
|
routes: HashMap<RouteID, DocFuncType>,
|
|
|
|
|
rx: Receiver<Message>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DocumentFile {
|
|
|
|
|
fn new(
|
|
|
|
|
queue: Queue,
|
|
|
|
|
rx: Receiver<Message>,
|
|
|
|
|
docdef: DocDef,
|
|
|
|
|
routes: HashMap<RouteID, DocFuncType>,
|
2026-02-27 08:36:22 -05:00
|
|
|
name_id: NameID,
|
2026-01-09 11:39:14 -05:00
|
|
|
) -> Self {
|
|
|
|
|
let indexes = Indexes::new(docdef.get_indexes());
|
|
|
|
|
Self {
|
|
|
|
|
docdef: docdef.clone(),
|
|
|
|
|
docs: InternalRecords::new(),
|
|
|
|
|
indexes: indexes,
|
|
|
|
|
name_id: name_id,
|
|
|
|
|
queue: queue,
|
|
|
|
|
routes: routes,
|
|
|
|
|
rx: rx,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn start(mut queue: Queue, msg: Message) {
|
|
|
|
|
let (tx, rx) = channel();
|
|
|
|
|
let action = msg.get_action();
|
|
|
|
|
let docdef = match action {
|
|
|
|
|
MsgAction::Create(data) => data.clone(),
|
|
|
|
|
_ => unreachable!("got {:?}: should have been a create message", action),
|
|
|
|
|
};
|
|
|
|
|
let names = docdef.get_document_names();
|
|
|
|
|
let id = queue.add_sender(tx);
|
2026-03-10 09:57:16 -04:00
|
|
|
let reg_msg = Register::new(id.clone(), RegMsg::AddDocName(names.clone()));
|
2026-03-26 12:18:38 -04:00
|
|
|
queue.send(msg.set_action(reg_msg));
|
2026-01-09 11:39:14 -05:00
|
|
|
let name_result = rx.recv().unwrap();
|
|
|
|
|
let name_id = match name_result.get_action() {
|
|
|
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
|
|
|
RegMsg::DocumentNameID(data) => data,
|
|
|
|
|
RegMsg::Error(err) => {
|
|
|
|
|
queue.remove_sender(&id);
|
2026-03-26 12:18:38 -04:00
|
|
|
queue.send(msg.set_action(err.clone()));
|
2026-01-09 11:39:14 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!("should only return a name id or an error"),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("should only return a name id or an error"),
|
|
|
|
|
};
|
|
|
|
|
let mut route_action: HashMap<RouteID, DocFuncType> = HashMap::new();
|
|
|
|
|
for path_action in docdef.iter_routes() {
|
2026-03-26 12:18:38 -04:00
|
|
|
let reg_req = Register::new(id.clone(), RegMsg::AddRoute(path_action.path()));
|
|
|
|
|
queue.send(msg.set_action(reg_req));
|
2026-01-09 11:39:14 -05:00
|
|
|
let result = rx.recv().unwrap();
|
|
|
|
|
let route_id = match result.get_action() {
|
|
|
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
|
|
|
RegMsg::RouteID(data) => data,
|
|
|
|
|
RegMsg::Error(err) => {
|
|
|
|
|
queue.remove_sender(&id);
|
2026-03-26 12:18:38 -04:00
|
|
|
queue.send(msg.set_action(err.clone()));
|
2026-01-09 11:39:14 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!("should only return a route id or an error"),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("should only return a route id or an error"),
|
|
|
|
|
};
|
|
|
|
|
route_action.insert(route_id.clone(), path_action.doc_function());
|
|
|
|
|
}
|
2026-02-14 13:45:49 -05:00
|
|
|
let mut doc = DocumentFile::new(
|
|
|
|
|
queue.clone(),
|
|
|
|
|
rx,
|
|
|
|
|
docdef.clone(),
|
|
|
|
|
route_action,
|
|
|
|
|
name_id.clone(),
|
|
|
|
|
);
|
2026-01-09 11:39:14 -05:00
|
|
|
spawn(move || {
|
|
|
|
|
doc.listen();
|
|
|
|
|
});
|
2026-03-26 12:18:38 -04:00
|
|
|
let reply = msg.set_action(MsgAction::DocumentCreated);
|
2026-01-30 14:55:42 -05:00
|
|
|
queue.send(reply.clone());
|
2026-01-09 11:39:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn listen(&mut self) {
|
2026-03-21 11:44:49 -04:00
|
|
|
let sess_name = Session::doc_names()[0].clone();
|
2026-01-09 11:39:14 -05:00
|
|
|
loop {
|
|
|
|
|
let msg = self.rx.recv().unwrap();
|
2026-03-21 11:44:49 -04:00
|
|
|
if !self.docdef.has_feature(&DocFeature::System) {
|
|
|
|
|
self.queue.send(Message::new(Query::new(sess_name.clone())));
|
|
|
|
|
}
|
2026-01-09 11:39:14 -05:00
|
|
|
let route = msg.get_route();
|
|
|
|
|
for (route_id, doc_func) in self.routes.clone().iter() {
|
|
|
|
|
if route == route_id.into() {
|
|
|
|
|
match doc_func {
|
|
|
|
|
DocFuncType::Add => self.add_document(&msg),
|
|
|
|
|
DocFuncType::Delete => self.delete(&msg),
|
|
|
|
|
DocFuncType::Query => self.query(&msg),
|
2026-02-14 13:45:49 -05:00
|
|
|
DocFuncType::Show => self.queue.send(
|
2026-03-26 12:18:38 -04:00
|
|
|
msg.set_action(Reply::new(self.docdef.get_document_names()[0].clone())),
|
2026-02-14 13:45:49 -05:00
|
|
|
),
|
2026-01-09 11:39:14 -05:00
|
|
|
DocFuncType::Update => self.update(&msg),
|
|
|
|
|
DocFuncType::ExistingQuery(action) => self.existing_query(&msg, action),
|
|
|
|
|
DocFuncType::Trigger(action) => self.trigger(&msg, action),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn validate<NT>(&self, field_name: NT, value: &Field) -> Result<Field, MTTError>
|
|
|
|
|
where
|
|
|
|
|
NT: Into<NameType>,
|
|
|
|
|
{
|
2026-02-27 08:36:22 -05:00
|
|
|
let id_holder = field_name.into();
|
|
|
|
|
let field_id = match self.docdef.get_field_id(id_holder.clone()) {
|
2026-01-09 11:39:14 -05:00
|
|
|
Ok(data) => data,
|
|
|
|
|
Err(err) => return Err(err),
|
|
|
|
|
};
|
2026-02-27 08:36:22 -05:00
|
|
|
let output = match self.docdef.validate(id_holder.clone(), value) {
|
2026-01-09 11:39:14 -05:00
|
|
|
Ok(data) => data,
|
|
|
|
|
Err(err) => return Err(err),
|
|
|
|
|
};
|
|
|
|
|
match self.indexes.validate(&field_id, &output) {
|
|
|
|
|
Ok(_) => {}
|
|
|
|
|
Err(err) => return Err(err),
|
|
|
|
|
}
|
|
|
|
|
Ok(output)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn add_document(&mut self, msg: &Message) {
|
|
|
|
|
let addition = match msg.get_action() {
|
|
|
|
|
MsgAction::Addition(data) => data,
|
|
|
|
|
_ => return,
|
|
|
|
|
};
|
|
|
|
|
let mut holder = InternalRecord::new();
|
2026-02-27 08:36:22 -05:00
|
|
|
|
2026-02-27 09:33:27 -05:00
|
|
|
let mut field_ids = self.docdef.get_field_ids();
|
|
|
|
|
for (name, value) in addition.iter() {
|
|
|
|
|
let field_id = match self.docdef.get_field_id(name) {
|
2026-01-09 11:39:14 -05:00
|
|
|
Ok(id) => id,
|
2026-02-27 08:36:22 -05:00
|
|
|
Err(mut err) => {
|
2026-02-27 09:33:27 -05:00
|
|
|
err.add_parent(ErrorID::Field(name.clone()));
|
2026-02-27 08:36:22 -05:00
|
|
|
err.add_parent(ErrorID::Document(msg.doc_name().clone()));
|
2026-03-26 12:18:38 -04:00
|
|
|
let reply = msg.set_action(err);
|
2026-01-30 14:55:42 -05:00
|
|
|
self.queue.send(reply);
|
2026-01-09 11:39:14 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-02-27 09:33:27 -05:00
|
|
|
let corrected = match self.validate(field_id.clone(), &value.get(&Field::None)) {
|
|
|
|
|
Ok(data) => data,
|
|
|
|
|
Err(mut err) => {
|
|
|
|
|
err.add_parent(ErrorID::Field(name.clone().into()));
|
|
|
|
|
err.add_parent(ErrorID::Document(msg.doc_name().clone()));
|
2026-03-26 12:18:38 -04:00
|
|
|
let reply = msg.set_action(err);
|
2026-02-27 09:33:27 -05:00
|
|
|
self.queue.send(reply);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-01-09 11:39:14 -05:00
|
|
|
};
|
2026-02-27 09:33:27 -05:00
|
|
|
holder.insert(field_id.clone(), corrected.clone());
|
|
|
|
|
field_ids.remove(&field_id);
|
|
|
|
|
}
|
|
|
|
|
for field_id in field_ids.iter() {
|
|
|
|
|
let corrected = match self.validate(field_id, &Field::None) {
|
2026-01-09 11:39:14 -05:00
|
|
|
Ok(data) => data,
|
2026-02-27 08:36:22 -05:00
|
|
|
Err(mut err) => {
|
|
|
|
|
err.add_parent(ErrorID::Field(field_id.clone().into()));
|
|
|
|
|
err.add_parent(ErrorID::Document(msg.doc_name().clone()));
|
2026-03-26 12:18:38 -04:00
|
|
|
let reply = msg.set_action(err);
|
2026-01-30 14:55:42 -05:00
|
|
|
self.queue.send(reply);
|
2026-01-09 11:39:14 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
holder.insert(field_id.clone(), corrected.clone());
|
|
|
|
|
}
|
2026-02-13 15:04:16 -05:00
|
|
|
let mut records = Records::new(
|
|
|
|
|
self.docdef.get_document_names().clone(),
|
|
|
|
|
self.docdef.get_field_names().clone(),
|
|
|
|
|
);
|
2026-01-09 11:39:14 -05:00
|
|
|
if !holder.is_empty() {
|
|
|
|
|
let mut oid = Oid::new();
|
|
|
|
|
while self.docs.contains_key(&oid) {
|
|
|
|
|
oid = Oid::new();
|
|
|
|
|
}
|
|
|
|
|
for (field_id, oids) in self.indexes.iter_mut() {
|
|
|
|
|
let value = holder.get(field_id).unwrap();
|
|
|
|
|
oids.internal_add(value, oid.clone());
|
|
|
|
|
}
|
|
|
|
|
self.docs.insert(oid.clone(), holder.clone());
|
|
|
|
|
records.insert(oid, holder);
|
|
|
|
|
}
|
2026-03-26 12:18:38 -04:00
|
|
|
self.queue.send(msg.set_action(records.clone()));
|
2026-01-09 11:39:14 -05:00
|
|
|
self.queue
|
2026-03-26 12:18:38 -04:00
|
|
|
.send(msg.set_action(MsgAction::OnAddition(records)));
|
2026-01-09 11:39:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn delete(&mut self, msg: &Message) {
|
|
|
|
|
let delete = match msg.get_action() {
|
|
|
|
|
MsgAction::Delete(data) => data,
|
|
|
|
|
_ => unreachable!("should always be delete action"),
|
|
|
|
|
};
|
|
|
|
|
let records = match self.run_query(delete.get_query()) {
|
|
|
|
|
Ok(data) => data,
|
2026-03-05 08:06:38 -05:00
|
|
|
Err(mut err) => {
|
|
|
|
|
err.add_parent(ErrorID::Document(msg.doc_name().into()));
|
2026-03-26 12:18:38 -04:00
|
|
|
let reply = msg.set_action(err);
|
2026-01-30 14:55:42 -05:00
|
|
|
self.queue.send(reply);
|
2026-01-09 11:39:14 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
for (oid, record) in records.iter() {
|
|
|
|
|
for (field_id, index) in self.indexes.iter_mut() {
|
|
|
|
|
index.remove(record.get(field_id).unwrap(), oid);
|
|
|
|
|
}
|
|
|
|
|
self.docs.remove(oid);
|
|
|
|
|
}
|
2026-02-13 15:04:16 -05:00
|
|
|
let rec = Records::with_data(
|
|
|
|
|
self.docdef.get_document_names().clone(),
|
|
|
|
|
self.docdef.get_field_names().clone(),
|
|
|
|
|
records,
|
|
|
|
|
);
|
2026-03-26 12:18:38 -04:00
|
|
|
self.queue.send(msg.set_action(rec.clone()));
|
|
|
|
|
self.queue.send(msg.set_action(MsgAction::OnDelete(rec)));
|
2026-01-09 11:39:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn run_query(&self, query: &Query) -> Result<InternalRecords, MTTError> {
|
|
|
|
|
let indexed_ids = self.indexes.index_ids();
|
2026-02-27 08:36:22 -05:00
|
|
|
let mut indexed: HashMap<NameID, Calculation> = HashMap::new();
|
|
|
|
|
let mut unindexed: HashMap<NameID, Calculation> = HashMap::new();
|
2026-01-09 11:39:14 -05:00
|
|
|
for (field, data) in query.iter() {
|
2026-03-01 09:39:01 -05:00
|
|
|
let id = match self.docdef.get_field_id(field.clone()) {
|
|
|
|
|
Ok(fid) => {
|
|
|
|
|
let expected_type = self.docdef.get_field_type(field.clone()).unwrap();
|
|
|
|
|
if &data.get_type() != expected_type {
|
2026-03-01 13:33:03 -05:00
|
|
|
let mut err =
|
|
|
|
|
MTTError::new(ErrorID::FieldTypeExpected(expected_type.clone()));
|
2026-03-01 09:39:01 -05:00
|
|
|
err.add_parent(ErrorID::Field(field.clone()));
|
|
|
|
|
return Err(err);
|
|
|
|
|
}
|
|
|
|
|
fid
|
|
|
|
|
}
|
2026-03-01 08:07:05 -05:00
|
|
|
Err(mut err) => {
|
|
|
|
|
err.add_parent(ErrorID::Field(field.clone()));
|
|
|
|
|
return Err(err);
|
|
|
|
|
}
|
2026-01-09 11:39:14 -05:00
|
|
|
};
|
|
|
|
|
if indexed_ids.contains(&id) {
|
|
|
|
|
indexed.insert(id, data.clone());
|
|
|
|
|
} else {
|
|
|
|
|
unindexed.insert(id, data.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let mut oids: HashSet<Oid> = self.docs.keys().cloned().collect();
|
|
|
|
|
for (field_id, calculation) in indexed.iter() {
|
|
|
|
|
let holder = match self.indexes.pull(field_id, calculation) {
|
|
|
|
|
Ok(data) => data,
|
|
|
|
|
Err(err) => return Err(err),
|
|
|
|
|
};
|
|
|
|
|
oids = oids.intersection(&holder).cloned().collect();
|
|
|
|
|
}
|
|
|
|
|
let mut records = InternalRecords::new();
|
|
|
|
|
for oid in oids.iter() {
|
|
|
|
|
records.insert(oid.clone(), self.docs.get(oid).unwrap().clone());
|
|
|
|
|
}
|
|
|
|
|
let holder = oids.clone();
|
|
|
|
|
for (oid, record) in records.iter() {
|
|
|
|
|
for (field_id, calc) in unindexed.iter() {
|
|
|
|
|
match calc.calculate(record.get(field_id).unwrap()) {
|
|
|
|
|
Field::Boolean(data) => {
|
|
|
|
|
if !data {
|
|
|
|
|
oids.remove(oid);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
oids.remove(oid);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let removals = holder.difference(&oids);
|
|
|
|
|
for oid in removals {
|
|
|
|
|
records.remove(oid);
|
|
|
|
|
}
|
|
|
|
|
Ok(records)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn query(&self, msg: &Message) {
|
|
|
|
|
let query = match msg.get_action() {
|
|
|
|
|
MsgAction::Query(data) => data,
|
|
|
|
|
_ => unreachable!("should receive a query"),
|
|
|
|
|
};
|
|
|
|
|
let records = match self.run_query(query) {
|
|
|
|
|
Ok(data) => data,
|
2026-03-02 08:47:40 -05:00
|
|
|
Err(mut err) => {
|
|
|
|
|
err.add_parent(ErrorID::Document(msg.doc_name().into()));
|
2026-03-26 12:18:38 -04:00
|
|
|
let reply = msg.set_action(err);
|
2026-01-30 14:55:42 -05:00
|
|
|
self.queue.send(reply);
|
2026-01-09 11:39:14 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-02-13 15:04:16 -05:00
|
|
|
let recs = Records::with_data(
|
|
|
|
|
self.docdef.get_document_names().clone(),
|
|
|
|
|
self.docdef.get_field_names().clone(),
|
|
|
|
|
records,
|
|
|
|
|
);
|
2026-03-26 12:18:38 -04:00
|
|
|
self.queue.send(msg.set_action(recs.clone()));
|
|
|
|
|
self.queue.send(msg.set_action(MsgAction::OnQuery(recs)));
|
2026-01-09 11:39:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn run_update(
|
|
|
|
|
&mut self,
|
|
|
|
|
original: &InternalRecords,
|
|
|
|
|
update: &Update,
|
|
|
|
|
msg: &Message,
|
|
|
|
|
) -> Result<Records, MTTError> {
|
2026-02-27 08:36:22 -05:00
|
|
|
let mut changes: HashMap<NameID, &CalcValue> = HashMap::new();
|
2026-01-09 11:39:14 -05:00
|
|
|
for (key, value) in update.get_values().iter() {
|
|
|
|
|
let field_id = match self.docdef.get_field_id(key) {
|
2026-03-02 08:47:40 -05:00
|
|
|
Ok(id) => {
|
|
|
|
|
let expected_type = self.docdef.get_field_type(key.clone()).unwrap();
|
|
|
|
|
if &value.get_type() != expected_type {
|
|
|
|
|
let mut err =
|
|
|
|
|
MTTError::new(ErrorID::FieldTypeExpected(expected_type.clone()));
|
|
|
|
|
err.add_parent(ErrorID::Field(key.clone()));
|
|
|
|
|
return Err(err);
|
|
|
|
|
}
|
|
|
|
|
id
|
|
|
|
|
}
|
|
|
|
|
Err(mut err) => {
|
|
|
|
|
err.add_parent(ErrorID::Field(key.clone()));
|
|
|
|
|
return Err(err);
|
|
|
|
|
}
|
2026-01-09 11:39:14 -05:00
|
|
|
};
|
|
|
|
|
changes.insert(field_id, value);
|
|
|
|
|
}
|
|
|
|
|
let mut indexes = Indexes::new(self.docdef.get_indexes());
|
|
|
|
|
let mut updates = InternalRecords::new();
|
|
|
|
|
for (oid, record) in original.iter() {
|
|
|
|
|
let mut holder = record.clone();
|
|
|
|
|
for (field_id, value) in changes.iter() {
|
|
|
|
|
let field = value.get(holder.get(field_id).unwrap());
|
|
|
|
|
let correction = match self.validate(field_id, &field) {
|
|
|
|
|
Ok(data) => data,
|
|
|
|
|
Err(err) => return Err(err),
|
|
|
|
|
};
|
|
|
|
|
holder.insert(field_id.clone(), correction.clone());
|
|
|
|
|
match indexes.add_to_index(&field_id, correction, oid.clone()) {
|
|
|
|
|
Ok(_) => {}
|
|
|
|
|
Err(err) => return Err(err),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
updates.insert(oid.clone(), holder);
|
|
|
|
|
}
|
|
|
|
|
for (oid, new_rec) in updates.iter() {
|
|
|
|
|
let old_rec = original.get(oid).unwrap();
|
|
|
|
|
for (field_id, index) in self.indexes.iter_mut() {
|
|
|
|
|
index.remove(old_rec.get(field_id).unwrap(), oid);
|
|
|
|
|
index
|
|
|
|
|
.add(new_rec.get(field_id).unwrap().clone(), oid.clone())
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
self.docs.insert(oid.clone(), new_rec.clone());
|
|
|
|
|
}
|
2026-02-13 15:04:16 -05:00
|
|
|
let recs = Records::with_data(
|
|
|
|
|
self.docdef.get_document_names().clone(),
|
|
|
|
|
self.docdef.get_field_names().clone(),
|
|
|
|
|
updates,
|
|
|
|
|
);
|
2026-01-09 11:39:14 -05:00
|
|
|
self.queue
|
2026-03-26 12:18:38 -04:00
|
|
|
.send(msg.set_action(MsgAction::OnUpdate(recs.clone())));
|
2026-01-09 11:39:14 -05:00
|
|
|
Ok(recs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn update(&mut self, msg: &Message) {
|
|
|
|
|
let update = match msg.get_action() {
|
|
|
|
|
MsgAction::Update(data) => data,
|
|
|
|
|
_ => unreachable!("should receive a update"),
|
|
|
|
|
};
|
|
|
|
|
let original = match self.run_query(update.get_query()) {
|
|
|
|
|
Ok(result) => result,
|
2026-03-02 08:47:40 -05:00
|
|
|
Err(mut err) => {
|
|
|
|
|
err.add_parent(ErrorID::Document(msg.doc_name().into()));
|
2026-03-26 12:18:38 -04:00
|
|
|
let reply = msg.set_action(err);
|
2026-01-30 14:55:42 -05:00
|
|
|
self.queue.send(reply);
|
2026-01-09 11:39:14 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let data = match self.run_update(&original, update, msg) {
|
|
|
|
|
Ok(output) => output,
|
2026-03-02 08:47:40 -05:00
|
|
|
Err(mut err) => {
|
|
|
|
|
err.add_parent(ErrorID::Document(msg.doc_name().into()));
|
2026-03-26 12:18:38 -04:00
|
|
|
let reply = msg.set_action(err);
|
2026-01-30 14:55:42 -05:00
|
|
|
self.queue.send(reply);
|
2026-01-09 11:39:14 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-03-26 12:18:38 -04:00
|
|
|
self.queue.send(msg.set_action(data));
|
2026-01-09 11:39:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn existing_query(&mut self, msg: &Message, action: &MsgAction) {
|
|
|
|
|
let recs = match msg.get_action() {
|
|
|
|
|
MsgAction::OnQuery(data) => data,
|
|
|
|
|
_ => unreachable!("should only receive on messages"),
|
|
|
|
|
};
|
|
|
|
|
match action {
|
|
|
|
|
MsgAction::Update(change) => self
|
|
|
|
|
.run_update(recs.get_internal_records(), change, msg)
|
|
|
|
|
.unwrap(),
|
|
|
|
|
_ => panic!("should not get here"),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn trigger(&self, msg: &Message, action: &MsgAction) {
|
2026-03-26 12:18:38 -04:00
|
|
|
self.queue.send(msg.set_action(action.clone()));
|
2026-01-09 11:39:14 -05:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-21 11:44:49 -04:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod internal_features {
|
|
|
|
|
use super::*;
|
|
|
|
|
use crate::{Name, TestMoreThanText};
|
|
|
|
|
use std::sync::mpsc::RecvTimeoutError;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn do_system_documents_ignores_session() {
|
|
|
|
|
let sess_name = Session::doc_names()[0].clone();
|
|
|
|
|
let mut test_env = TestMoreThanText::new();
|
|
|
|
|
let mut mtt = test_env.get_morethantext();
|
2026-03-25 10:13:16 -04:00
|
|
|
let client = mtt.client();
|
2026-03-21 11:44:49 -04:00
|
|
|
let name = Name::english("something");
|
|
|
|
|
let docdef = DocDef::system(name.clone());
|
2026-03-25 10:13:16 -04:00
|
|
|
client.create_document(docdef);
|
2026-03-21 11:44:49 -04:00
|
|
|
let path = Path::new(
|
|
|
|
|
Include::All,
|
|
|
|
|
Include::Just(sess_name.clone().into()),
|
|
|
|
|
Include::Just(Action::Query),
|
|
|
|
|
);
|
|
|
|
|
test_env.register_channel(vec![path]);
|
2026-03-25 10:13:16 -04:00
|
|
|
client.records(Query::new(name)).unwrap();
|
2026-03-21 11:44:49 -04:00
|
|
|
match test_env.recv() {
|
|
|
|
|
Ok(msg) => unreachable!("got {:?} should have timed out", msg),
|
|
|
|
|
Err(err) => match err {
|
|
|
|
|
RecvTimeoutError::Timeout => {}
|
|
|
|
|
_ => unreachable!("got {:?}, should have timed out", err),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|