use std::collections::{HashMap}; use std::iter::Extend; use crate::yopa::model::{ObjectTemplate, ID}; use thiserror::Error; use std::hash::Hash; /// Data model structs and enums pub mod model { use serde::{Serialize, Deserialize}; use std::borrow::Cow; /// Common identifier type #[allow(non_camel_case_types)] pub type ID = uuid::Uuid; /// Object template #[derive(Debug,Clone,Serialize,Deserialize)] pub struct ObjectTemplate { /// PK pub id : ID, /// Template name, unique within the database pub name: String, /// Parent object template ID pub parent_tpl_id: Option, } /// Relation between templates #[derive(Debug,Clone,Serialize,Deserialize)] pub struct RelationTemplate { /// PK pub id: ID, /// Object template ID pub object_tpl_id: ID, /// Relation name, unique within the parent object pub name: String, /// Relation is optional pub optional: bool, /// Relation can be multiple pub multiple: bool, /// Related object template ID pub related_tpl_id: ID, } /// Property definition #[derive(Debug,Clone,Serialize,Deserialize)] pub struct PropertyTemplate { /// PK pub id: ID, /// Object or Reference template ID pub parent_tpl_id: ID, /// Property name, unique within the parent object or reference pub name: String, /// Property is optional pub optional: bool, /// Property can be multiple pub multiple: bool, /// Property data type pub data_type: DataType, /// Default value, used for newly created objects pub default: Option, } /// Value data type #[derive(Debug,Clone,Serialize,Deserialize)] pub enum DataType { /// Text String, /// Integer Integer, /// Floating point number Decimal, /// Boolean yes/no Boolean, } /// Value of a particular type #[derive(Debug,Clone,Serialize,Deserialize)] pub enum TypedValue { /// Text String(Cow<'static, str>), /// Integer Integer(i64), /// Floating point number Decimal(f64), /// Boolean yes/no Boolean(bool), } } /// Data value structs pub mod data { use serde::{Serialize, Deserialize}; use crate::yopa::model::{ID, TypedValue}; /// Instance of an object #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Object { /// PK pub id : ID, /// Object template ID pub object_tpl_id: ID, } /// Relation between two objects #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Relation { /// PK pub id : ID, /// Source object ID pub object_id : ID, /// Relation template ID pub rel_tpl_id: ID, /// Related object ID pub related_id : ID, } /// Value attached to an object #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Value { /// PK pub id : ID, /// Owning object ID pub object_id : ID, /// Property template ID pub prop_tpl_id: ID, /// Property value pub value : TypedValue, } } #[derive(Debug, Default)] pub struct InMemoryStorage { tpl_objects: HashMap, tpl_relations: HashMap, tpl_properties: HashMap, data_objects: HashMap, data_relations: HashMap, data_properties: HashMap, } fn next_id() -> ID { uuid::Uuid::new_v4() } pub fn zero_id() -> ID { uuid::Uuid::nil() } #[derive(Debug,Error)] pub enum StorageError { #[error("Referenced {0} does not exist")] NotExist(&'static str), #[error("Schema constraint violation: {0}")] ConstraintViolation(&'static str), } impl InMemoryStorage { pub fn new() -> Self { Self::default() } pub fn insert_object_template(&mut self, mut tpl : model::ObjectTemplate) -> Result { if let Some(pid) = tpl.parent_tpl_id { if !self.tpl_objects.contains_key(&pid) { return Err(StorageError::NotExist("parent object template")); } } if self.tpl_objects.iter().find(|(_, t)| t.name == tpl.name).is_some() { return Err(StorageError::ConstraintViolation("object template with this name already exists")); } let id = next_id(); tpl.id = id; self.tpl_objects.insert(id, tpl); Ok(id) } pub fn insert_relation_template(&mut self, mut rel: model::RelationTemplate) -> Result { if !self.tpl_objects.contains_key(&rel.object_tpl_id) { return Err(StorageError::NotExist("origin object template")); } if !self.tpl_objects.contains_key(&rel.related_tpl_id) { return Err(StorageError::NotExist("related object template")); } if self.tpl_relations.iter().find(|(_, t)| t.name == rel.name && t.object_tpl_id == rel.object_tpl_id).is_some() { return Err(StorageError::ConstraintViolation("relation with this name and parent already exists")); } let id = next_id(); rel.id = id; self.tpl_relations.insert(id, rel); Ok(id) } pub fn insert_property_template(&mut self, mut prop: model::PropertyTemplate) -> Result { if !self.tpl_objects.contains_key(&prop.parent_tpl_id) { // Maybe it's attached to a relation? if !self.tpl_relations.contains_key(&prop.parent_tpl_id) { return Err(StorageError::NotExist("object or reference template")); } } if self.tpl_properties.iter().find(|(_, t)| t.parent_tpl_id == prop.parent_tpl_id && t.name == prop.name).is_some() { return Err(StorageError::ConstraintViolation("property with this name and parent already exists")); } let id = next_id(); prop.id = id; self.tpl_properties.insert(id, prop); Ok(id) } pub fn delete_object_template(&mut self, id : ID) -> Result { return if let Some(t) = self.tpl_objects.remove(&id) { // Remove relation templates let removed_relation_ids = map_drain_filter(&mut self.tpl_relations, |_k, v| v.object_tpl_id == id || v.related_tpl_id == id) .keys(); // Remove related property templates let removed_prop_ids = map_drain_filter(&mut self.tpl_properties, |_k, v| v.parent_tpl_id == id || removed_relation_ids.contains(&v.parent_tpl_id)) .keys(); // Remove objects let _ = map_drain_filter(&mut self.data_objects, |_k, v| v.object_tpl_id == id); // Remove property values let _ = map_drain_filter(&mut self.data_properties, |_k, v| removed_prop_ids.contains(&v.prop_tpl_id)); // Remove relations let _ = map_drain_filter(&mut self.data_relations, |_k, v| removed_relation_ids.contains(&v.rel_tpl_id)); // Related object remain untouched, so there can be a problem with orphans. This is up to the application to deal with. Ok(t) } else { Err(StorageError::NotExist("object template")) } } pub fn delete_relation_template(&mut self, id : ID) -> Result { return if let Some(t) = self.tpl_relations.remove(&id) { // Remove relations let _ = map_drain_filter(&mut self.data_relations, |_k, v| v.rel_tpl_id == id).keys(); // Remove related property templates let removed_prop_tpl_ids = map_drain_filter(&mut self.tpl_properties, |_k, v| v.parent_tpl_id == id).keys(); let _ = map_drain_filter(&mut self.data_properties, |_k, v| removed_prop_tpl_ids.contains(&v.prop_tpl_id)); // Related object remain untouched, so there can be a problem with orphans. This is up to the application to deal with. Ok(t) } else { Err(StorageError::NotExist("relation template")) } } pub fn delete_property_template(&mut self, id : ID) -> Result { return if let Some(t) = self.tpl_properties.remove(&id) { // Remove relations let _ = map_drain_filter(&mut self.data_properties, |_k, v| v.prop_tpl_id == id); Ok(t) } else { Err(StorageError::NotExist("property template")) } } } fn map_drain_filter(map : &mut HashMap, filter : impl Fn(&K, &V) -> bool) -> Vec<(K, V)> { let mut removed = vec![]; let mut retain = vec![]; for (k, v) in map.drain() { if filter(&k, &v) { removed.push((k, v)); } else { retain.push((k, v)); } } map.extend(retain); removed } trait KVVecToKeysOrValues { fn keys(self) -> Vec; fn values(self) -> Vec; } impl KVVecToKeysOrValues for Vec<(K,V)> { fn keys(self) -> Vec { self.into_iter().map(|(k, _v)| k).collect() } fn values(self) -> Vec { self.into_iter().map(|(_k, v)| v).collect() } }