You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
289 lines
12 KiB
289 lines
12 KiB
use std::borrow::Cow;
|
|
use std::collections::HashMap;
|
|
|
|
use itertools::Itertools;
|
|
use serde::{Deserialize, Serialize};
|
|
use thiserror::Error;
|
|
|
|
use cool::{KVVecToKeysOrValues, map_drain_filter};
|
|
pub use id::ID;
|
|
use id::next_id;
|
|
use insert::InsertObj;
|
|
use insert::InsertValue;
|
|
use model::ObjectModel;
|
|
|
|
pub mod model;
|
|
pub mod data;
|
|
pub mod insert;
|
|
pub mod id;
|
|
mod cool;
|
|
|
|
/// Stupid storage with no persistence
|
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
|
pub struct InMemoryStorage {
|
|
obj_models: HashMap<ID, model::ObjectModel>,
|
|
rel_models: HashMap<ID, model::RelationModel>,
|
|
prop_models: HashMap<ID, model::PropertyModel>,
|
|
|
|
objects: HashMap<ID, data::Object>,
|
|
relations: HashMap<ID, data::Relation>,
|
|
properties: HashMap<ID, data::Value>,
|
|
}
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum StorageError {
|
|
#[error("Referenced {0} does not exist")]
|
|
NotExist(Cow<'static, str>),
|
|
#[error("Schema constraint violation: {0}")]
|
|
ConstraintViolation(Cow<'static, str>),
|
|
}
|
|
|
|
impl InMemoryStorage {
|
|
/// Create empty store
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
/// Define a data object
|
|
pub fn define_object(&mut self, mut tpl: model::ObjectModel) -> Result<ID, StorageError> {
|
|
if let Some(pid) = tpl.parent_tpl_id {
|
|
if !self.obj_models.contains_key(&pid) {
|
|
return Err(StorageError::NotExist(format!("parent object model {}", pid).into()));
|
|
}
|
|
}
|
|
|
|
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.obj_models.insert(id, tpl);
|
|
Ok(id)
|
|
}
|
|
|
|
/// Define a relation between two data objects
|
|
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.obj_models.contains_key(&rel.related_tpl_id) {
|
|
return Err(StorageError::NotExist(format!("related object model {}", rel.related_tpl_id).into()));
|
|
}
|
|
|
|
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.rel_models.insert(id, rel);
|
|
Ok(id)
|
|
}
|
|
|
|
/// Define a property attached to an object or a relation
|
|
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.rel_models.contains_key(&prop.parent_tpl_id) {
|
|
return Err(StorageError::NotExist(format!("object or relation model {}", prop.parent_tpl_id).into()));
|
|
}
|
|
}
|
|
|
|
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.prop_models.insert(id, prop);
|
|
Ok(id)
|
|
}
|
|
|
|
/// Delete an object definition and associated data
|
|
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.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.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.objects, |_k, v| v.model_id == id);
|
|
|
|
// Remove property values
|
|
let _ = map_drain_filter(&mut self.properties, |_k, v| removed_prop_ids.contains(&v.model_id));
|
|
|
|
// Remove relations
|
|
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(format!("object model {}", id).into()))
|
|
};
|
|
}
|
|
|
|
/// Delete a relation definition and associated data
|
|
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.relations, |_k, v| v.model_id == id).keys();
|
|
|
|
// Remove related property templates
|
|
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.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(format!("relation model {}", id).into()))
|
|
};
|
|
}
|
|
|
|
/// Delete a property definition and associated data
|
|
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.properties, |_k, v| v.model_id == id);
|
|
Ok(t)
|
|
} else {
|
|
Err(StorageError::NotExist(format!("property model {}", id).into()))
|
|
};
|
|
}
|
|
|
|
pub fn describe_id(&self, id: ID) -> String {
|
|
if let Some(x) = self.obj_models.get(&id) {
|
|
x.to_string()
|
|
} else if let Some(x) = self.rel_models.get(&id) {
|
|
x.to_string()
|
|
} else if let Some(x) = self.prop_models.get(&id) {
|
|
x.to_string()
|
|
} else {
|
|
id.to_string()
|
|
}
|
|
}
|
|
|
|
// DATA
|
|
|
|
/// Insert object with relations, validating the data model constraints
|
|
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_model_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_model_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, self.describe_id(parent_model_id)).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, self.describe_id(parent_model_id)).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,
|
|
self.describe_id(relation_model.related_tpl_id),
|
|
self.describe_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()));
|
|
}
|
|
}
|
|
}
|
|
|
|
self.objects.insert(object_id, object);
|
|
|
|
for rel in relations_to_insert {
|
|
self.relations.insert(rel.id, rel);
|
|
}
|
|
|
|
for value in values_to_insert {
|
|
self.properties.insert(value.id, value);
|
|
}
|
|
|
|
Ok(object_id)
|
|
}
|
|
}
|
|
|