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/yopa/src/lib.rs

646 lines
25 KiB

use std::collections::{HashMap};
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;
mod cool;
/*
pub mod id {
/// Common identifier type
#[allow(non_camel_case_types)]
pub type ID = uuid::Uuid;
pub fn next_id() -> ID {
uuid::Uuid::new_v4()
}
pub fn zero_id() -> ID {
uuid::Uuid::nil()
}
}
*/
pub mod id {
/// Common identifier type
#[allow(non_camel_case_types)]
pub type ID = u64;
lazy_static::lazy_static! {
static ref counter: parking_lot::Mutex<u64> = parking_lot::Mutex::new(0);
}
pub fn next_id() -> ID {
let mut m = counter.lock();
let v = *m;
*m += 1;
v
}
pub fn zero_id() -> ID {
0
}
}
pub use id::ID;
use id::next_id;
/// 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;
/// 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 ObjectModel {
/// 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 RelationModel {
/// 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 PropertyModel {
/// 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,Copy,Serialize,Deserialize,Eq,PartialEq)]
pub enum DataType {
/// Text
String,
/// Integer
Integer,
/// Floating point number
Decimal,
/// Boolean yes/no
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 crate::ID;
use std::borrow::Cow;
use crate::StorageError;
use std::num::ParseIntError;
use crate::model::DataType;
/// Value of a particular type
#[derive(Debug,Clone,Serialize,Deserialize,PartialEq)]
pub enum TypedValue {
/// Text
String(Cow<'static, str>),
/// Integer
Integer(i64),
/// Floating point number
Decimal(f64),
/// Boolean yes/no
Boolean(bool),
}
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 crate::TypedValue;
use crate::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)]
pub struct Object {
/// PK
pub id : ID,
/// Object template ID
pub model_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 model_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 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
}
impl InsertValue {
pub fn new(model_id : ID, value : TypedValue) -> Self {
Self {
model_id,
value
}
}
}
/// 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>
}
impl InsertRel {
pub fn new(model_id : ID, related_id: ID, values : Vec<InsertValue>) -> Self {
Self {
model_id,
related_id,
values
}
}
}
/// 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>,
}
impl InsertObj {
pub fn new(model_id : ID, values : Vec<InsertValue>, relations: Vec<InsertRel>) -> Self {
Self {
model_id,
values,
relations
}
}
}
}
#[derive(Debug, Default)]
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 {
pub fn new() -> Self {
Self::default()
}
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)
}
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)
}
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)
}
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()))
}
}
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()))
}
}
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()))
}
}
// DATA
/// 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()));
}
}
}
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)
}
}