From 77e1cf023b2e203496a8170dc4dd6ee2a4fc7dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Fri, 5 Feb 2021 21:45:03 +0100 Subject: [PATCH] data inserting --- Cargo.lock | 16 ++ Cargo.toml | 1 + src/main.rs | 71 ++++++-- src/yopa.rs | 474 ++++++++++++++++++++++++++++++++++++++++------------ 4 files changed, 446 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7b937d..f1cf747 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "getrandom" version = "0.2.2" @@ -23,6 +29,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "itertools" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.7" @@ -213,6 +228,7 @@ name = "yopa" version = "0.1.0" dependencies = [ "anyhow", + "itertools", "log", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index bb80fa3..e157a0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ serde = { version = "1.0.120", features = ["derive"] } #parking_lot = "0.11.1" anyhow = "1.0.38" thiserror = "1.0.23" +itertools = "0.10.0" diff --git a/src/main.rs b/src/main.rs index f15eb64..e057e7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,8 @@ use log::LevelFilter; mod yopa; use yopa::model; use yopa::model::DataType; +use crate::yopa::insert::{InsertObj, InsertValue, InsertRel}; +use crate::yopa::data::TypedValue; mod cool; @@ -21,15 +23,15 @@ fn _main() -> anyhow::Result<()> { let mut store = yopa::InMemoryStorage::new(); - let recipe_tid = store.insert_object_template(model::ObjectTemplate { + let Recipe = store.define_object(model::ObjectModel { id: Default::default(), name: "recipe".to_string(), parent_tpl_id: None })?; - store.insert_property_template(model::PropertyTemplate { + let RecipeTitle = store.define_property(model::PropertyModel { id: Default::default(), - parent_tpl_id: recipe_tid, + parent_tpl_id: Recipe, name: "title".to_string(), optional: false, multiple: false, @@ -37,9 +39,9 @@ fn _main() -> anyhow::Result<()> { default: None })?; - store.insert_property_template(model::PropertyTemplate { + let PrepHours = store.define_property(model::PropertyModel { id: Default::default(), - parent_tpl_id: recipe_tid, + parent_tpl_id: Recipe, name: "prep_hours".to_string(), optional: true, multiple: false, @@ -47,24 +49,34 @@ fn _main() -> anyhow::Result<()> { default: None })?; - let book_tid = store.insert_object_template(model::ObjectTemplate { + let Book = store.define_object(model::ObjectModel { id: Default::default(), name: "book".to_string(), parent_tpl_id: None })?; - let br_rid = store.insert_relation_template(model::RelationTemplate { + let BookName = store.define_property(model::PropertyModel { id: Default::default(), - object_tpl_id: recipe_tid, - name: "book reference".to_string(), + parent_tpl_id: Book, + name: "name".to_string(), optional: false, multiple: false, - related_tpl_id: book_tid + data_type: DataType::String, + default: None })?; - store.insert_property_template(model::PropertyTemplate { + let BookToRecipe = store.define_relation(model::RelationModel { id: Default::default(), - parent_tpl_id: br_rid, + object_tpl_id: Recipe, + name: "book reference".to_string(), + optional: true, + multiple: true, + related_tpl_id: Book + })?; + + let BookToRecipePage = store.define_property(model::PropertyModel { + id: Default::default(), + parent_tpl_id: BookToRecipe, name: "page".to_string(), optional: true, multiple: false, @@ -74,5 +86,40 @@ fn _main() -> anyhow::Result<()> { debug!("{:#?}", store); + let MyBook1 = store.insert_object(InsertObj { + model_id: Book, + values: vec![ + InsertValue { + model_id: BookName, + value: TypedValue::String("Recipe Book 1".into()), + } + ], + relations: vec![], + })?; + + store.insert_object(InsertObj { + model_id: Recipe, + values: vec![ + InsertValue { + model_id: RecipeTitle, + value: TypedValue::String("Pancakes".into()), + } + ], + relations: vec![ + InsertRel { + model_id: BookToRecipe, + related_id: MyBook1, + values: vec![ + InsertValue { + model_id: BookToRecipePage, + value: TypedValue::Integer(123), + } + ] + } + ], + }); + + debug!("{:#?}", store); + Ok(()) } diff --git a/src/yopa.rs b/src/yopa.rs index 4d0c402..256fd28 100644 --- a/src/yopa.rs +++ b/src/yopa.rs @@ -1,21 +1,37 @@ use std::collections::{HashMap}; -use crate::yopa::model::{ObjectTemplate, ID}; use thiserror::Error; use crate::cool::{map_drain_filter, KVVecToKeysOrValues}; +use model::{ObjectModel}; +use insert::InsertObj; +use itertools::Itertools; +use std::borrow::Cow; +use model::Describe; +use insert::InsertValue; +use data::TypedValue; + +/// Common identifier type +#[allow(non_camel_case_types)] +pub type ID = uuid::Uuid; /// 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; + use super::ID; + use super::data::TypedValue; + use std::fmt::{Display, Formatter}; + use std::fmt; + + /// Get a description of a struct + pub trait Describe { + /// Short but informative description for error messages + fn describe(&self) -> String; + } /// Object template #[derive(Debug,Clone,Serialize,Deserialize)] - pub struct ObjectTemplate { + pub struct ObjectModel { /// PK pub id : ID, /// Template name, unique within the database @@ -26,7 +42,7 @@ pub mod model { /// Relation between templates #[derive(Debug,Clone,Serialize,Deserialize)] - pub struct RelationTemplate { + pub struct RelationModel { /// PK pub id: ID, /// Object template ID @@ -43,7 +59,7 @@ pub mod model { /// Property definition #[derive(Debug,Clone,Serialize,Deserialize)] - pub struct PropertyTemplate { + pub struct PropertyModel { /// PK pub id: ID, /// Object or Reference template ID @@ -61,7 +77,7 @@ pub mod model { } /// Value data type - #[derive(Debug,Clone,Serialize,Deserialize)] + #[derive(Debug,Clone,Copy,Serialize,Deserialize,Eq,PartialEq)] pub enum DataType { /// Text String, @@ -73,8 +89,36 @@ pub mod model { Boolean, } + impl Display for ObjectModel { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "object \"{}\" ({})", self.name, self.id) + } + } + + impl Display for RelationModel { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "relation \"{}\" ({})", self.name, self.id) + } + } + + impl Display for PropertyModel { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "property \"{}\" ({})", self.name, self.id) + } + } +} + +/// Data value structs +pub mod data { + use serde::{Serialize, Deserialize}; + use super::ID; + use std::borrow::Cow; + use super::StorageError; + use std::num::ParseIntError; + use crate::yopa::model::DataType; + /// Value of a particular type - #[derive(Debug,Clone,Serialize,Deserialize)] + #[derive(Debug,Clone,Serialize,Deserialize,PartialEq)] pub enum TypedValue { /// Text String(Cow<'static, str>), @@ -85,12 +129,142 @@ pub mod model { /// Boolean yes/no Boolean(bool), } -} -/// Data value structs -pub mod data { - use serde::{Serialize, Deserialize}; - use crate::yopa::model::{ID, TypedValue}; + impl TypedValue { + /// Try ot cast to another type. On error, the original value is returned as Err. + pub fn cast_to(self, ty : DataType) -> Result { + match (self, ty) { + // to string + (s @ TypedValue::String(_), DataType::String) => Ok(s), + (TypedValue::Integer(i), DataType::String) => Ok(TypedValue::String(i.to_string().into())), + (TypedValue::Decimal(f), DataType::String) => Ok(TypedValue::String(f.to_string().into())), + (TypedValue::Boolean(b), DataType::String) => Ok(TypedValue::String(Cow::Borrowed(if b { "1" } else { "0" }))), + // to int + (TypedValue::String(s), DataType::Integer) => { + match s.parse::() { + Ok(i) => Ok(TypedValue::Integer(i)), + Err(_) => Err(TypedValue::String(s)) + } + }, + (s @ TypedValue::Integer(_), DataType::Integer) => Ok(s), + (TypedValue::Decimal(f), DataType::Integer) => Ok(TypedValue::Integer(f.round() as i64)), + (TypedValue::Boolean(b), DataType::Integer) => Ok(TypedValue::Integer(if b { 1 } else { 0 })), + // to float + (TypedValue::String(s), DataType::Decimal) => { + match s.parse::() { + Ok(i) => Ok(TypedValue::Decimal(i)), + Err(_) => Err(TypedValue::String(s)) + } + }, + (TypedValue::Integer(i), DataType::Decimal) => Ok(TypedValue::Decimal(i as f64)), + (d @ TypedValue::Decimal(_), DataType::Decimal) => Ok(d), + (e @ TypedValue::Boolean(_), DataType::Decimal) => Err(e), + // to bool + (TypedValue::String(s), DataType::Boolean) => { + match &(&s).to_ascii_lowercase()[..] { + "y" | "yes" | "true" | "1" => { + Ok(TypedValue::Boolean(true)) + } + "n" | "no" | "false" | "0" => { + Ok(TypedValue::Boolean(false)) + } + _ => { + Err(TypedValue::String(s)) + } + } + }, + (TypedValue::Integer(i), DataType::Boolean) => Ok(TypedValue::Boolean(i != 0)), + (e @ TypedValue::Decimal(_), DataType::Boolean) => Err(e), + (b @ TypedValue::Boolean(_), DataType::Boolean) => Ok(b), + (s, _) => { + Err(s) + } + } + } + } + + #[cfg(test)] + mod tests { + use super::TypedValue; + use crate::yopa::model::DataType; + + #[test] + fn test_cast_to_bool() { + // Cast to bool + assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::Boolean(true).cast_to(DataType::Boolean)); + assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::Boolean(false).cast_to(DataType::Boolean)); + + assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::Integer(123).cast_to(DataType::Boolean)); + assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::Integer(0).cast_to(DataType::Boolean)); + + assert_eq!(Err(TypedValue::Decimal(0.0)), TypedValue::Decimal(0.0).cast_to(DataType::Boolean)); + assert_eq!(Err(TypedValue::Decimal(123.0)), TypedValue::Decimal(123.0).cast_to(DataType::Boolean)); + + assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::String("true".into()).cast_to(DataType::Boolean)); + assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::String("1".into()).cast_to(DataType::Boolean)); + assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::String("y".into()).cast_to(DataType::Boolean)); + assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::String("yes".into()).cast_to(DataType::Boolean)); + assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::String("false".into()).cast_to(DataType::Boolean)); + assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::String("0".into()).cast_to(DataType::Boolean)); + assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::String("n".into()).cast_to(DataType::Boolean)); + assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::String("no".into()).cast_to(DataType::Boolean)); + assert_eq!(Err(TypedValue::String("blorg".into())), TypedValue::String("blorg".into()).cast_to(DataType::Boolean)); + } + + #[test] + fn test_cast_to_int() { + // Cast to bool + assert_eq!(Ok(TypedValue::Integer(1)), TypedValue::Boolean(true).cast_to(DataType::Integer)); + assert_eq!(Ok(TypedValue::Integer(0)), TypedValue::Boolean(false).cast_to(DataType::Integer)); + + assert_eq!(Ok(TypedValue::Integer(123)), TypedValue::Integer(123).cast_to(DataType::Integer)); + assert_eq!(Ok(TypedValue::Integer(0)), TypedValue::Integer(0).cast_to(DataType::Integer)); + + assert_eq!(Ok(TypedValue::Integer(0)), TypedValue::Decimal(0.0).cast_to(DataType::Integer)); + assert_eq!(Ok(TypedValue::Integer(123)), TypedValue::Decimal(123.0).cast_to(DataType::Integer)); + assert_eq!(Ok(TypedValue::Integer(-124)), TypedValue::Decimal(-123.7).cast_to(DataType::Integer)); + + assert_eq!(Ok(TypedValue::Integer(123)), TypedValue::String("123".into()).cast_to(DataType::Integer)); + assert_eq!(Ok(TypedValue::Integer(-123)), TypedValue::String("-123".into()).cast_to(DataType::Integer)); + assert_eq!(Ok(TypedValue::Integer(0)), TypedValue::String("0".into()).cast_to(DataType::Integer)); + assert_eq!(Err(TypedValue::String("123.456".into())), TypedValue::String("123.456".into()).cast_to(DataType::Integer)); + assert_eq!(Err(TypedValue::String("-123.456".into())), TypedValue::String("-123.456".into()).cast_to(DataType::Integer)); + } + + #[test] + fn test_cast_to_decimal() { + // Cast to bool + assert_eq!(Err(TypedValue::Boolean(true)), TypedValue::Boolean(true).cast_to(DataType::Decimal)); + assert_eq!(Err(TypedValue::Boolean(false)), TypedValue::Boolean(false).cast_to(DataType::Decimal)); + + assert_eq!(Ok(TypedValue::Decimal(123.0)), TypedValue::Integer(123).cast_to(DataType::Decimal)); + assert_eq!(Ok(TypedValue::Decimal(0.0)), TypedValue::Integer(0).cast_to(DataType::Decimal)); + + assert_eq!(Ok(TypedValue::Decimal(0.0)), TypedValue::Decimal(0.0).cast_to(DataType::Decimal)); + assert_eq!(Ok(TypedValue::Decimal(-123.7)), TypedValue::Decimal(-123.7).cast_to(DataType::Decimal)); + + assert_eq!(Ok(TypedValue::Decimal(123.0)), TypedValue::String("123".into()).cast_to(DataType::Decimal)); + assert_eq!(Ok(TypedValue::Decimal(-123.0)), TypedValue::String("-123".into()).cast_to(DataType::Decimal)); + assert_eq!(Ok(TypedValue::Decimal(0.0)), TypedValue::String("0".into()).cast_to(DataType::Decimal)); + assert_eq!(Ok(TypedValue::Decimal(-123.456)), TypedValue::String("-123.456".into()).cast_to(DataType::Decimal)); + } + + #[test] + fn test_cast_to_string() { + // Cast to bool + assert_eq!(Ok(TypedValue::String("1".into())), TypedValue::Boolean(true).cast_to(DataType::String)); + assert_eq!(Ok(TypedValue::String("0".into())), TypedValue::Boolean(false).cast_to(DataType::String)); + + assert_eq!(Ok(TypedValue::String("123".into())), TypedValue::Integer(123).cast_to(DataType::String)); + assert_eq!(Ok(TypedValue::String("0".into())), TypedValue::Integer(0).cast_to(DataType::String)); + + assert_eq!(Ok(TypedValue::String("0".into())), TypedValue::Decimal(0.0).cast_to(DataType::String)); + assert_eq!(Ok(TypedValue::String("123".into())), TypedValue::Decimal(123.0).cast_to(DataType::String)); + assert_eq!(Ok(TypedValue::String("-123.5".into())), TypedValue::Decimal(-123.5).cast_to(DataType::String)); + + assert_eq!(Ok(TypedValue::String("blorg".into())), TypedValue::String("blorg".into()).cast_to(DataType::String)); + } + } /// Instance of an object #[derive(Debug,Clone,Serialize,Deserialize)] @@ -98,7 +272,7 @@ pub mod data { /// PK pub id : ID, /// Object template ID - pub object_tpl_id: ID, + pub model_id: ID, } /// Relation between two objects @@ -109,7 +283,7 @@ pub mod data { /// Source object ID pub object_id : ID, /// Relation template ID - pub rel_tpl_id: ID, + pub model_id: ID, /// Related object ID pub related_id : ID, } @@ -122,21 +296,51 @@ pub mod data { /// Owning object ID pub object_id : ID, /// Property template ID - pub prop_tpl_id: ID, + pub model_id: ID, /// Property value pub value : TypedValue, } } +/// Helper struct for inserting relational data in the database +pub mod insert { + use super::ID; + use super::data::TypedValue; + use serde::{Serialize,Deserialize}; + + /// Value to insert + #[derive(Debug,Clone,Serialize,Deserialize)] + pub struct InsertValue { + pub model_id: ID, + pub value: TypedValue + } + + /// Info for inserting a relation + #[derive(Debug,Clone,Serialize,Deserialize)] + pub struct InsertRel { + pub model_id: ID, + pub related_id: ID, + pub values: Vec + } + + /// Info for inserting a relation + #[derive(Debug,Clone,Serialize,Deserialize)] + pub struct InsertObj { + pub model_id: ID, + pub values: Vec, + pub relations: Vec, + } +} + #[derive(Debug, Default)] pub struct InMemoryStorage { - tpl_objects: HashMap, - tpl_relations: HashMap, - tpl_properties: HashMap, + obj_models: HashMap, + rel_models: HashMap, + prop_models: HashMap, - data_objects: HashMap, - data_relations: HashMap, - data_values: HashMap, + objects: HashMap, + relations: HashMap, + properties: HashMap, } fn next_id() -> ID { @@ -150,9 +354,9 @@ pub fn zero_id() -> ID { #[derive(Debug,Error)] pub enum StorageError { #[error("Referenced {0} does not exist")] - NotExist(&'static str), + NotExist(Cow<'static, str>), #[error("Schema constraint violation: {0}")] - ConstraintViolation(&'static str), + ConstraintViolation(Cow<'static, str>), } impl InMemoryStorage { @@ -160,162 +364,224 @@ impl InMemoryStorage { Self::default() } - pub fn insert_object_template(&mut self, mut tpl : model::ObjectTemplate) -> Result { + pub fn define_object(&mut self, mut tpl : model::ObjectModel) -> 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.obj_models.contains_key(&pid) { + return Err(StorageError::NotExist(format!("parent object model {}", pid).into())); } } - if self.tpl_objects.iter().find(|(_, t)| t.name == tpl.name).is_some() { - return Err(StorageError::ConstraintViolation("object template with this name already exists")); + if self.obj_models.iter().find(|(_, t)| t.name == tpl.name).is_some() { + return Err(StorageError::ConstraintViolation(format!("object model with the name \"{}\" already exists", tpl.name).into())); } let id = next_id(); tpl.id = id; - self.tpl_objects.insert(id, tpl); + self.obj_models.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")); + pub fn define_relation(&mut self, mut rel: model::RelationModel) -> Result { + if !self.obj_models.contains_key(&rel.object_tpl_id) { + return Err(StorageError::NotExist(format!("source object model {}", rel.object_tpl_id).into())); } - if !self.tpl_objects.contains_key(&rel.related_tpl_id) { - return Err(StorageError::NotExist("related object template")); + if !self.obj_models.contains_key(&rel.related_tpl_id) { + return Err(StorageError::NotExist(format!("related object model {}", rel.related_tpl_id).into())); } - 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")); + if self.rel_models.iter().find(|(_, t)| t.name == rel.name && t.object_tpl_id == rel.object_tpl_id).is_some() { + return Err(StorageError::ConstraintViolation( + format!("relation with the name \"{}\" and on model {} already exists", rel.name, rel.object_tpl_id).into())); } let id = next_id(); rel.id = id; - self.tpl_relations.insert(id, rel); + self.rel_models.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) { + pub fn define_property(&mut self, mut prop: model::PropertyModel) -> Result { + if !self.obj_models.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.rel_models.contains_key(&prop.parent_tpl_id) { + return Err(StorageError::NotExist(format!("object or relation model {}", prop.parent_tpl_id).into())); } } - 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")); + if self.prop_models.iter().find(|(_, t)| t.parent_tpl_id == prop.parent_tpl_id && t.name == prop.name).is_some() { + return Err(StorageError::ConstraintViolation( + format!("property with the name \"{}\" already exists on model {}", prop.name, prop.parent_tpl_id).into())); + } + + // Ensure the default type is compatible + if let Some(d) = prop.default { + prop.default = Some(match d.cast_to(prop.data_type) { + Ok(v) => v, + Err(d) => return Err(StorageError::NotExist(format!("default value {:?} has invalid type", d).into())) + }); } let id = next_id(); prop.id = id; - self.tpl_properties.insert(id, prop); + self.prop_models.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) { + pub fn undefine_object(&mut self, id : ID) -> Result { + return if let Some(t) = self.obj_models.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) + let removed_relation_ids = map_drain_filter(&mut self.rel_models, |_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)) + let removed_prop_ids = map_drain_filter(&mut self.prop_models, |_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); + let _ = map_drain_filter(&mut self.objects, |_k, v| v.model_id == id); // Remove property values - let _ = map_drain_filter(&mut self.data_values, |_k, v| removed_prop_ids.contains(&v.prop_tpl_id)); + let _ = map_drain_filter(&mut self.properties, |_k, v| removed_prop_ids.contains(&v.model_id)); // Remove relations - let _ = map_drain_filter(&mut self.data_relations, |_k, v| removed_relation_ids.contains(&v.rel_tpl_id)); + let _ = map_drain_filter(&mut self.relations, |_k, v| removed_relation_ids.contains(&v.model_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")) + Err(StorageError::NotExist(format!("object model {}", id).into())) } } - pub fn delete_relation_template(&mut self, id : ID) -> Result { - return if let Some(t) = self.tpl_relations.remove(&id) { + pub fn undefine_relation(&mut self, id : ID) -> Result { + return if let Some(t) = self.rel_models.remove(&id) { // Remove relations - let _ = map_drain_filter(&mut self.data_relations, |_k, v| v.rel_tpl_id == id).keys(); + let _ = map_drain_filter(&mut self.relations, |_k, v| v.model_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 removed_prop_tpl_ids = map_drain_filter(&mut self.prop_models, |_k, v| v.parent_tpl_id == id).keys(); - let _ = map_drain_filter(&mut self.data_values, |_k, v| removed_prop_tpl_ids.contains(&v.prop_tpl_id)); + let _ = map_drain_filter(&mut self.properties, |_k, v| removed_prop_tpl_ids.contains(&v.model_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")) + Err(StorageError::NotExist(format!("relation model {}", id).into())) } } - pub fn delete_property_template(&mut self, id : ID) -> Result { - return if let Some(t) = self.tpl_properties.remove(&id) { + pub fn undefine_property(&mut self, id : ID) -> Result { + return if let Some(t) = self.prop_models.remove(&id) { // Remove relations - let _ = map_drain_filter(&mut self.data_values, |_k, v| v.prop_tpl_id == id); + let _ = map_drain_filter(&mut self.properties, |_k, v| v.model_id == id); Ok(t) } else { - Err(StorageError::NotExist("property template")) + Err(StorageError::NotExist(format!("property model {}", id).into())) } } // DATA - pub fn insert_object(&mut self, mut obj : data::Object) -> Result { - if !self.tpl_objects.contains_key(&obj.object_tpl_id) { - return Err(StorageError::NotExist("object template")); - } - - let id = next_id(); - obj.id = id; - self.data_objects.insert(id, obj); - Ok(id) - } - - pub fn insert_value(&mut self, mut obj : data::Value) -> Result { - if !self.tpl_properties.contains_key(&obj.prop_tpl_id) { - return Err(StorageError::NotExist("property template")); - } + /// Insert object with relations, validating the data model + pub fn insert_object(&mut self, insobj: InsertObj) -> Result { + let obj_model_id = insobj.model_id; + + let obj_model = match self.obj_models.get(&obj_model_id) { + Some(m) => m, + None => return Err(StorageError::NotExist(format!("object model {}", obj_model_id).into())) + }; + + let object_id = next_id(); + let object = data::Object { + id: object_id, + model_id: obj_model_id + }; + + let find_values_to_insert = |values : Vec, parent_id : ID| -> Result, StorageError> { + let mut values_by_id = values.into_iter().into_group_map_by(|iv| iv.model_id); + let mut values_to_insert = vec![]; + + for (id, prop) in self.prop_models.iter().filter(|(id, p)| p.parent_tpl_id == parent_id) { + if let Some(values) = values_by_id.remove(id) { + if values.len() > 1 && !prop.multiple { + return Err(StorageError::ConstraintViolation(format!("{} of {} cannot have multiple values", prop, obj_model).into())); + } + for val_instance in values { + values_to_insert.push(data::Value { + id: next_id(), + object_id, + model_id: prop.id, + value: val_instance.value.cast_to(prop.data_type) + .map_err(|v| StorageError::ConstraintViolation(format!("{} cannot accept value {:?}", prop, v).into()))? + }); + } + } else { + if !prop.optional { + if let Some(def) = &prop.default { + values_to_insert.push(data::Value { + id: next_id(), + object_id, + model_id: prop.id, + value: def.clone() + }); + } else { + return Err(StorageError::ConstraintViolation(format!("{} is required for {} (and no default value is defined)", prop, obj_model).into())); + } + } + } + } - if !self.data_objects.contains_key(&obj.object_id) { - return Err(StorageError::NotExist("parent object")); + Ok(values_to_insert) + }; + + let mut values_to_insert = find_values_to_insert(insobj.values, obj_model_id)?; + + // And now ..... relations! + let mut relations_by_id = insobj.relations.into_iter().into_group_map_by(|ir| ir.model_id); + let mut relations_to_insert = vec![]; + + for (relation_model_id, relation_model) in self.rel_models.iter().filter(|(id, r)| r.object_tpl_id == obj_model_id) { + if let Some(instances) = relations_by_id.remove(relation_model_id) { + if instances.len() > 1 && !relation_model.multiple { + return Err(StorageError::ConstraintViolation(format!("{} of {} cannot be set multiply", relation_model, obj_model).into())); + } + + for rel_instance in instances { + if let Some(related) = self.objects.get(&rel_instance.related_id) { + if related.model_id != relation_model.related_tpl_id { + return Err(StorageError::ConstraintViolation(format!("{} of {} requires object of type {}, got {}", relation_model, obj_model, relation_model.related_tpl_id, related.model_id).into())); + } + } + + // Relations can have properties + values_to_insert.extend(find_values_to_insert(rel_instance.values, *relation_model_id)?); + + relations_to_insert.push(data::Relation { + id: next_id(), + object_id, + model_id: rel_instance.model_id, + related_id: rel_instance.related_id + }); + } + } else { + if !relation_model.optional { + return Err(StorageError::ConstraintViolation(format!("{} is required for {}", relation_model, obj_model).into())); + } + } } - // TODO validate if it already exists + self.objects.insert(object_id, object); - let id = next_id(); - obj.id = id; - self.data_values.insert(id, obj); - Ok(id) - } - - pub fn insert_relation(&mut self, mut rel: data::Relation) -> Result { - if !self.tpl_relations.contains_key(&rel.rel_tpl_id) { - return Err(StorageError::NotExist("relation template")); - } - - if !self.data_objects.contains_key(&rel.object_id) { - return Err(StorageError::NotExist("parent object")); + for rel in relations_to_insert { + self.relations.insert(rel.id, rel); } - if !self.data_objects.contains_key(&rel.related_id) { - return Err(StorageError::NotExist("related object")); + for value in values_to_insert { + self.properties.insert(value.id, value); } - // TODO validate if it already exists - - let id = next_id(); - rel.id = id; - self.data_relations.insert(id, rel); - Ok(id) + Ok(object_id) } }