master
Ondřej Hruška 3 years ago
parent be70c7f497
commit c4148271df
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 28
      Cargo.lock
  2. 2
      Cargo.toml
  3. 13
      yopa/src/cool.rs
  4. 34
      yopa/src/data.rs
  5. 48
      yopa/src/id.rs
  6. 27
      yopa/src/insert.rs
  7. 96
      yopa/src/lib.rs
  8. 19
      yopa/src/model.rs

28
Cargo.lock generated

@ -18,6 +18,17 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "getrandom"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "instant"
version = "0.1.9"
@ -229,6 +240,22 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom",
"serde",
]
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.9"
@ -263,6 +290,7 @@ dependencies = [
"serde",
"serde_json",
"thiserror",
"uuid",
]
[[package]]

@ -10,7 +10,7 @@ edition = "2018"
log = "0.4.13"
simple-logging = "2.0.2"
yopa = { path = "./yopa" }
yopa = { path = "./yopa", features = [ "uuid-ids" ] }
serde_json = "1.0.61"
serde = { version = "1.0.120", features = ["derive"] }

@ -1,7 +1,10 @@
use std::hash::Hash;
//! Utilities for internal use and other cool stuff
use std::collections::HashMap;
use std::hash::Hash;
pub fn map_drain_filter<K : Eq + Hash, V>(map : &mut HashMap<K, V>, filter : impl Fn(&K, &V) -> bool) -> Vec<(K, V)> {
/// drain_filter() implemented for HashMap. It returns the removed items as a Vec
pub 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() {
@ -15,12 +18,16 @@ pub fn map_drain_filter<K : Eq + Hash, V>(map : &mut HashMap<K, V>, filter : imp
removed
}
/// Get the first or second item from a Vec of (Key, Value) pairs.
/// Use when only one part of the pair is needed
pub trait KVVecToKeysOrValues<K, V> {
/// Get the first item of each tuple
fn keys(self) -> Vec<K>;
/// Get the second item of each tuple
fn values(self) -> Vec<V>;
}
impl<K, V> KVVecToKeysOrValues<K, V> for Vec<(K,V)> {
impl<K, V> KVVecToKeysOrValues<K, V> for Vec<(K, V)> {
fn keys(self) -> Vec<K> {
self.into_iter().map(|(k, _v)| k).collect()
}

@ -1,14 +1,14 @@
//! Data value structs
use serde::{Serialize, Deserialize};
use crate::ID;
use std::borrow::Cow;
use serde::{Deserialize, Serialize};
use crate::ID;
use crate::model::DataType;
/// Value of a particular type
#[derive(Debug,Clone,Serialize,Deserialize,PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TypedValue {
/// Text
String(Cow<'static, str>),
@ -22,7 +22,7 @@ pub enum 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> {
pub fn cast_to(self, ty: DataType) -> Result<TypedValue, TypedValue> {
match (self, ty) {
// to string
(s @ TypedValue::String(_), DataType::String) => Ok(s),
@ -35,7 +35,7 @@ impl TypedValue {
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 })),
@ -45,7 +45,7 @@ impl TypedValue {
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),
@ -62,7 +62,7 @@ impl TypedValue {
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),
@ -155,36 +155,36 @@ mod tests {
}
/// Instance of an object
#[derive(Debug,Clone,Serialize,Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Object {
/// PK
pub id : ID,
pub id: ID,
/// Object template ID
pub model_id: ID,
}
/// Relation between two objects
#[derive(Debug,Clone,Serialize,Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Relation {
/// PK
pub id : ID,
pub id: ID,
/// Source object ID
pub object_id : ID,
pub object_id: ID,
/// Relation template ID
pub model_id: ID,
/// Related object ID
pub related_id : ID,
pub related_id: ID,
}
/// Value attached to an object
#[derive(Debug,Clone,Serialize,Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Value {
/// PK
pub id : ID,
pub id: ID,
/// Owning object ID
pub object_id : ID,
pub object_id: ID,
/// Property template ID
pub model_id: ID,
/// Property value
pub value : TypedValue,
pub value: TypedValue,
}

@ -0,0 +1,48 @@
//! IDs used for data objects and models.
//!
//! UUID is always unique; the numeric ID used as a fallback
//! is auto-incrementing, but some values may be skipped.
//!
//! It is better to treat both ID implementations as opaque.
#[cfg(feature = "uuid-ids")]
mod impl_uuid {
/// 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()
}
}
#[cfg(not(feature = "uuid-ids"))]
mod impl_u64 {
/// 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
}
}
#[cfg(feature = "uuid-ids")]
pub use impl_uuid::{ID, next_id, zero_id};
#[cfg(not(feature = "uuid-ids"))]
pub use impl_u64::{ID, next_id, zero_id};

@ -1,45 +1,46 @@
//! Helper struct for inserting relational data in the database
use super::ID;
use serde::{Deserialize, Serialize};
use super::data::TypedValue;
use serde::{Serialize,Deserialize};
use super::ID;
/// Value to insert
#[derive(Debug,Clone,Serialize,Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InsertValue {
pub model_id: ID,
pub value: TypedValue
pub value: TypedValue,
}
impl InsertValue {
pub fn new(model_id : ID, value : TypedValue) -> Self {
pub fn new(model_id: ID, value: TypedValue) -> Self {
Self {
model_id,
value
value,
}
}
}
/// Info for inserting a relation
#[derive(Debug,Clone,Serialize,Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InsertRel {
pub model_id: ID,
pub related_id: ID,
pub values: Vec<InsertValue>
pub values: Vec<InsertValue>,
}
impl InsertRel {
pub fn new(model_id : ID, related_id: ID, values : Vec<InsertValue>) -> Self {
pub fn new(model_id: ID, related_id: ID, values: Vec<InsertValue>) -> Self {
Self {
model_id,
related_id,
values
values,
}
}
}
/// Info for inserting a relation
#[derive(Debug,Clone,Serialize,Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InsertObj {
pub model_id: ID,
pub values: Vec<InsertValue>,
@ -47,11 +48,11 @@ pub struct InsertObj {
}
impl InsertObj {
pub fn new(model_id : ID, values : Vec<InsertValue>, relations: Vec<InsertRel>) -> Self {
pub fn new(model_id: ID, values: Vec<InsertValue>, relations: Vec<InsertRel>) -> Self {
Self {
model_id,
values,
relations
relations,
}
}
}

@ -1,62 +1,25 @@
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 std::collections::HashMap;
use insert::InsertValue;
mod cool;
#[cfg(feature="uuid-ids")]
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()
}
}
#[cfg(not(feature="uuid-ids"))]
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
}
}
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;
#[derive(Debug, Default)]
/// 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>,
@ -67,7 +30,7 @@ pub struct InMemoryStorage {
properties: HashMap<ID, data::Value>,
}
#[derive(Debug,Error)]
#[derive(Debug, Error)]
pub enum StorageError {
#[error("Referenced {0} does not exist")]
NotExist(Cow<'static, str>),
@ -76,11 +39,13 @@ pub enum StorageError {
}
impl InMemoryStorage {
/// Create empty store
pub fn new() -> Self {
Self::default()
}
pub fn define_object(&mut self, mut tpl : model::ObjectModel) -> Result<ID, StorageError> {
/// 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()));
@ -97,6 +62,7 @@ impl InMemoryStorage {
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()));
@ -116,6 +82,7 @@ impl InMemoryStorage {
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?
@ -143,7 +110,8 @@ impl InMemoryStorage {
Ok(id)
}
pub fn undefine_object(&mut self, id : ID) -> Result<ObjectModel, StorageError> {
/// 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)
@ -167,10 +135,11 @@ impl InMemoryStorage {
Ok(t)
} else {
Err(StorageError::NotExist(format!("object model {}", id).into()))
}
};
}
pub fn undefine_relation(&mut self, id : ID) -> Result<model::RelationModel, StorageError> {
/// 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();
@ -185,22 +154,23 @@ impl InMemoryStorage {
Ok(t)
} else {
Err(StorageError::NotExist(format!("relation model {}", id).into()))
}
};
}
pub fn undefine_property(&mut self, id : ID) -> Result<model::PropertyModel, StorageError> {
/// 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()))
}
};
}
// DATA
/// Insert object with relations, validating the data model
/// 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;
@ -212,10 +182,10 @@ impl InMemoryStorage {
let object_id = next_id();
let object = data::Object {
id: object_id,
model_id: obj_model_id
model_id: obj_model_id,
};
let find_values_to_insert = |values : Vec<InsertValue>, parent_id : ID| -> Result<Vec<data::Value>, StorageError> {
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![];
@ -230,7 +200,7 @@ impl InMemoryStorage {
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()))?
.map_err(|v| StorageError::ConstraintViolation(format!("{} cannot accept value {:?}", prop, v).into()))?,
});
}
} else {
@ -240,7 +210,7 @@ impl InMemoryStorage {
id: next_id(),
object_id,
model_id: prop.id,
value: def.clone()
value: def.clone(),
});
} else {
return Err(StorageError::ConstraintViolation(format!("{} is required for {} (and no default value is defined)", prop, obj_model).into()));
@ -278,7 +248,7 @@ impl InMemoryStorage {
id: next_id(),
object_id,
model_id: rel_instance.model_id,
related_id: rel_instance.related_id
related_id: rel_instance.related_id,
});
}
} else {

@ -1,12 +1,13 @@
//! Data model structs and enums
use serde::{Serialize, Deserialize};
use super::ID;
use super::data::TypedValue;
use std::fmt::{Display, Formatter};
use std::fmt;
use serde::{Deserialize, Serialize};
use super::data::TypedValue;
use super::ID;
/// Get a description of a struct
pub trait Describe {
/// Short but informative description for error messages
@ -14,10 +15,10 @@ pub trait Describe {
}
/// Object template
#[derive(Debug,Clone,Serialize,Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObjectModel {
/// PK
pub id : ID,
pub id: ID,
/// Template name, unique within the database
pub name: String,
/// Parent object template ID
@ -25,7 +26,7 @@ pub struct ObjectModel {
}
/// Relation between templates
#[derive(Debug,Clone,Serialize,Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelationModel {
/// PK
pub id: ID,
@ -42,7 +43,7 @@ pub struct RelationModel {
}
/// Property definition
#[derive(Debug,Clone,Serialize,Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PropertyModel {
/// PK
pub id: ID,
@ -61,7 +62,7 @@ pub struct PropertyModel {
}
/// Value data type
#[derive(Debug,Clone,Copy,Serialize,Deserialize,Eq,PartialEq)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
pub enum DataType {
/// Text
String,

Loading…
Cancel
Save