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.
300 lines
9.3 KiB
300 lines
9.3 KiB
4 years ago
|
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<ID>,
|
||
|
}
|
||
|
|
||
|
/// 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<TypedValue>,
|
||
|
}
|
||
|
|
||
|
/// 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<ID, model::ObjectTemplate>,
|
||
|
tpl_relations: HashMap<ID, model::RelationTemplate>,
|
||
|
tpl_properties: HashMap<ID, model::PropertyTemplate>,
|
||
|
|
||
|
data_objects: HashMap<ID, data::Object>,
|
||
|
data_relations: HashMap<ID, data::Relation>,
|
||
|
data_properties: HashMap<ID, data::Value>,
|
||
|
}
|
||
|
|
||
|
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<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.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<ID, StorageError> {
|
||
|
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<ID, StorageError> {
|
||
|
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<ObjectTemplate, StorageError> {
|
||
|
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<model::RelationTemplate, StorageError> {
|
||
|
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<model::PropertyTemplate, StorageError> {
|
||
|
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<K : Eq + Hash, V>(map : &mut HashMap<K, V>, 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<K, V> {
|
||
|
fn keys(self) -> Vec<K>;
|
||
|
fn values(self) -> Vec<V>;
|
||
|
}
|
||
|
|
||
|
impl<K, V> KVVecToKeysOrValues<K, V> for Vec<(K,V)> {
|
||
|
fn keys(self) -> Vec<K> {
|
||
|
self.into_iter().map(|(k, _v)| k).collect()
|
||
|
}
|
||
|
|
||
|
fn values(self) -> Vec<V> {
|
||
|
self.into_iter().map(|(_k, v)| v).collect()
|
||
|
}
|
||
|
}
|