data inserting
This commit is contained in:
Generated
+16
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
+59
-12
@@ -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(())
|
||||
}
|
||||
|
||||
+370
-104
@@ -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;
|
||||
use super::ID;
|
||||
use super::data::TypedValue;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt;
|
||||
|
||||
/// Common identifier type
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type ID = uuid::Uuid;
|
||||
/// 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<TypedValue, TypedValue> {
|
||||
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::<i64>() {
|
||||
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::<f64>() {
|
||||
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<InsertValue>
|
||||
}
|
||||
|
||||
/// Info for inserting a relation
|
||||
#[derive(Debug,Clone,Serialize,Deserialize)]
|
||||
pub struct InsertObj {
|
||||
pub model_id: ID,
|
||||
pub values: Vec<InsertValue>,
|
||||
pub relations: Vec<InsertRel>,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InMemoryStorage {
|
||||
tpl_objects: HashMap<ID, model::ObjectTemplate>,
|
||||
tpl_relations: HashMap<ID, model::RelationTemplate>,
|
||||
tpl_properties: HashMap<ID, model::PropertyTemplate>,
|
||||
obj_models: HashMap<ID, model::ObjectModel>,
|
||||
rel_models: HashMap<ID, model::RelationModel>,
|
||||
prop_models: HashMap<ID, model::PropertyModel>,
|
||||
|
||||
data_objects: HashMap<ID, data::Object>,
|
||||
data_relations: HashMap<ID, data::Relation>,
|
||||
data_values: HashMap<ID, data::Value>,
|
||||
objects: HashMap<ID, data::Object>,
|
||||
relations: HashMap<ID, data::Relation>,
|
||||
properties: HashMap<ID, data::Value>,
|
||||
}
|
||||
|
||||
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<ID, StorageError> {
|
||||
pub fn define_object(&mut self, mut tpl : model::ObjectModel) -> Result<ID, StorageError> {
|
||||
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<ID, StorageError> {
|
||||
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<ID, StorageError> {
|
||||
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<ID, StorageError> {
|
||||
if !self.tpl_objects.contains_key(&prop.parent_tpl_id) {
|
||||
pub fn define_property(&mut self, mut prop: model::PropertyModel) -> Result<ID, StorageError> {
|
||||
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<ObjectTemplate, StorageError> {
|
||||
return if let Some(t) = self.tpl_objects.remove(&id) {
|
||||
pub fn undefine_object(&mut self, id : ID) -> Result<ObjectModel, StorageError> {
|
||||
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<model::RelationTemplate, StorageError> {
|
||||
return if let Some(t) = self.tpl_relations.remove(&id) {
|
||||
pub fn undefine_relation(&mut self, id : ID) -> Result<model::RelationModel, StorageError> {
|
||||
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<model::PropertyTemplate, StorageError> {
|
||||
return if let Some(t) = self.tpl_properties.remove(&id) {
|
||||
pub fn undefine_property(&mut self, id : ID) -> Result<model::PropertyModel, StorageError> {
|
||||
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<ID, StorageError> {
|
||||
if !self.tpl_objects.contains_key(&obj.object_tpl_id) {
|
||||
return Err(StorageError::NotExist("object template"));
|
||||
/// Insert object with relations, validating the data model
|
||||
pub fn insert_object(&mut self, insobj: InsertObj) -> Result<ID, StorageError> {
|
||||
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<InsertValue>, parent_id : ID| -> Result<Vec<data::Value>, 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let id = next_id();
|
||||
obj.id = id;
|
||||
self.data_objects.insert(id, obj);
|
||||
Ok(id)
|
||||
}
|
||||
self.objects.insert(object_id, object);
|
||||
|
||||
pub fn insert_value(&mut self, mut obj : data::Value) -> Result<ID, StorageError> {
|
||||
if !self.tpl_properties.contains_key(&obj.prop_tpl_id) {
|
||||
return Err(StorageError::NotExist("property template"));
|
||||
for rel in relations_to_insert {
|
||||
self.relations.insert(rel.id, rel);
|
||||
}
|
||||
|
||||
if !self.data_objects.contains_key(&obj.object_id) {
|
||||
return Err(StorageError::NotExist("parent object"));
|
||||
for value in values_to_insert {
|
||||
self.properties.insert(value.id, value);
|
||||
}
|
||||
|
||||
// TODO validate if it already exists
|
||||
|
||||
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<ID, StorageError> {
|
||||
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"));
|
||||
}
|
||||
|
||||
if !self.data_objects.contains_key(&rel.related_id) {
|
||||
return Err(StorageError::NotExist("related object"));
|
||||
}
|
||||
|
||||
// TODO validate if it already exists
|
||||
|
||||
let id = next_id();
|
||||
rel.id = id;
|
||||
self.data_relations.insert(id, rel);
|
||||
Ok(id)
|
||||
Ok(object_id)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user