a small relational database with user-editable schema for manual data entry
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.
 
 
 
 
 
 
yopa/src/yopa.rs

321 lines
10 KiB

use std::collections::{HashMap};
use crate::yopa::model::{ObjectTemplate, ID};
use thiserror::Error;
use crate::cool::{map_drain_filter, KVVecToKeysOrValues};
/// 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_values: 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_values, |_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_values, |_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_values, |_k, v| v.prop_tpl_id == id);
Ok(t)
} else {
Err(StorageError::NotExist("property template"))
}
}
// 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"));
}
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<ID, StorageError> {
if !self.tpl_properties.contains_key(&obj.prop_tpl_id) {
return Err(StorageError::NotExist("property template"));
}
if !self.data_objects.contains_key(&obj.object_id) {
return Err(StorageError::NotExist("parent object"));
}
// 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)
}
}