master
Ondřej Hruška 4 years ago
parent be70c7f497
commit c4148271df
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 28
      Cargo.lock
  2. 2
      Cargo.toml
  3. 9
      yopa/src/cool.rs
  4. 10
      yopa/src/data.rs
  5. 48
      yopa/src/id.rs
  6. 15
      yopa/src/insert.rs
  7. 84
      yopa/src/lib.rs
  8. 9
      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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 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]] [[package]]
name = "instant" name = "instant"
version = "0.1.9" version = "0.1.9"
@ -229,6 +240,22 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 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]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -263,6 +290,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
"uuid",
] ]
[[package]] [[package]]

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

@ -1,6 +1,9 @@
use std::hash::Hash; //! Utilities for internal use and other cool stuff
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::Hash;
/// 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)> { 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 removed = vec![];
let mut retain = vec![]; let mut retain = vec![];
@ -15,8 +18,12 @@ pub fn map_drain_filter<K : Eq + Hash, V>(map : &mut HashMap<K, V>, filter : imp
removed 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> { pub trait KVVecToKeysOrValues<K, V> {
/// Get the first item of each tuple
fn keys(self) -> Vec<K>; fn keys(self) -> Vec<K>;
/// Get the second item of each tuple
fn values(self) -> Vec<V>; fn values(self) -> Vec<V>;
} }

@ -1,10 +1,10 @@
//! Data value structs //! Data value structs
use serde::{Serialize, Deserialize};
use crate::ID;
use std::borrow::Cow; use std::borrow::Cow;
use serde::{Deserialize, Serialize};
use crate::ID;
use crate::model::DataType; use crate::model::DataType;
/// Value of a particular type /// Value of a particular type
@ -35,7 +35,7 @@ impl TypedValue {
Ok(i) => Ok(TypedValue::Integer(i)), Ok(i) => Ok(TypedValue::Integer(i)),
Err(_) => Err(TypedValue::String(s)) Err(_) => Err(TypedValue::String(s))
} }
}, }
(s @ TypedValue::Integer(_), DataType::Integer) => Ok(s), (s @ TypedValue::Integer(_), DataType::Integer) => Ok(s),
(TypedValue::Decimal(f), DataType::Integer) => Ok(TypedValue::Integer(f.round() as i64)), (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 })), (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)), Ok(i) => Ok(TypedValue::Decimal(i)),
Err(_) => Err(TypedValue::String(s)) Err(_) => Err(TypedValue::String(s))
} }
}, }
(TypedValue::Integer(i), DataType::Decimal) => Ok(TypedValue::Decimal(i as f64)), (TypedValue::Integer(i), DataType::Decimal) => Ok(TypedValue::Decimal(i as f64)),
(d @ TypedValue::Decimal(_), DataType::Decimal) => Ok(d), (d @ TypedValue::Decimal(_), DataType::Decimal) => Ok(d),
(e @ TypedValue::Boolean(_), DataType::Decimal) => Err(e), (e @ TypedValue::Boolean(_), DataType::Decimal) => Err(e),
@ -62,7 +62,7 @@ impl TypedValue {
Err(TypedValue::String(s)) Err(TypedValue::String(s))
} }
} }
}, }
(TypedValue::Integer(i), DataType::Boolean) => Ok(TypedValue::Boolean(i != 0)), (TypedValue::Integer(i), DataType::Boolean) => Ok(TypedValue::Boolean(i != 0)),
(e @ TypedValue::Decimal(_), DataType::Boolean) => Err(e), (e @ TypedValue::Decimal(_), DataType::Boolean) => Err(e),
(b @ TypedValue::Boolean(_), DataType::Boolean) => Ok(b), (b @ TypedValue::Boolean(_), DataType::Boolean) => Ok(b),

@ -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,21 +1,22 @@
//! Helper struct for inserting relational data in the database //! Helper struct for inserting relational data in the database
use super::ID; use serde::{Deserialize, Serialize};
use super::data::TypedValue; use super::data::TypedValue;
use serde::{Serialize,Deserialize}; use super::ID;
/// Value to insert /// Value to insert
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InsertValue { pub struct InsertValue {
pub model_id: ID, pub model_id: ID,
pub value: TypedValue pub value: TypedValue,
} }
impl InsertValue { impl InsertValue {
pub fn new(model_id: ID, value: TypedValue) -> Self { pub fn new(model_id: ID, value: TypedValue) -> Self {
Self { Self {
model_id, model_id,
value value,
} }
} }
} }
@ -25,7 +26,7 @@ impl InsertValue {
pub struct InsertRel { pub struct InsertRel {
pub model_id: ID, pub model_id: ID,
pub related_id: ID, pub related_id: ID,
pub values: Vec<InsertValue> pub values: Vec<InsertValue>,
} }
impl InsertRel { impl InsertRel {
@ -33,7 +34,7 @@ impl InsertRel {
Self { Self {
model_id, model_id,
related_id, related_id,
values values,
} }
} }
} }
@ -51,7 +52,7 @@ impl InsertObj {
Self { Self {
model_id, model_id,
values, 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::borrow::Cow;
use std::collections::HashMap;
use insert::InsertValue; use itertools::Itertools;
use serde::{Deserialize, Serialize};
use thiserror::Error;
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 cool::{KVVecToKeysOrValues, map_drain_filter};
pub use id::ID; pub use id::ID;
use id::next_id; use id::next_id;
use insert::InsertObj;
use insert::InsertValue;
use model::ObjectModel;
pub mod model; pub mod model;
pub mod data; pub mod data;
pub mod insert; 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 { pub struct InMemoryStorage {
obj_models: HashMap<ID, model::ObjectModel>, obj_models: HashMap<ID, model::ObjectModel>,
rel_models: HashMap<ID, model::RelationModel>, rel_models: HashMap<ID, model::RelationModel>,
@ -76,10 +39,12 @@ pub enum StorageError {
} }
impl InMemoryStorage { impl InMemoryStorage {
/// Create empty store
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
/// Define a data object
pub fn define_object(&mut self, mut tpl: model::ObjectModel) -> Result<ID, StorageError> { pub fn define_object(&mut self, mut tpl: model::ObjectModel) -> Result<ID, StorageError> {
if let Some(pid) = tpl.parent_tpl_id { if let Some(pid) = tpl.parent_tpl_id {
if !self.obj_models.contains_key(&pid) { if !self.obj_models.contains_key(&pid) {
@ -97,6 +62,7 @@ impl InMemoryStorage {
Ok(id) Ok(id)
} }
/// Define a relation between two data objects
pub fn define_relation(&mut self, mut rel: model::RelationModel) -> Result<ID, StorageError> { pub fn define_relation(&mut self, mut rel: model::RelationModel) -> Result<ID, StorageError> {
if !self.obj_models.contains_key(&rel.object_tpl_id) { if !self.obj_models.contains_key(&rel.object_tpl_id) {
return Err(StorageError::NotExist(format!("source object model {}", rel.object_tpl_id).into())); return Err(StorageError::NotExist(format!("source object model {}", rel.object_tpl_id).into()));
@ -116,6 +82,7 @@ impl InMemoryStorage {
Ok(id) 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> { pub fn define_property(&mut self, mut prop: model::PropertyModel) -> Result<ID, StorageError> {
if !self.obj_models.contains_key(&prop.parent_tpl_id) { if !self.obj_models.contains_key(&prop.parent_tpl_id) {
// Maybe it's attached to a relation? // Maybe it's attached to a relation?
@ -143,6 +110,7 @@ impl InMemoryStorage {
Ok(id) Ok(id)
} }
/// Delete an object definition and associated data
pub fn undefine_object(&mut self, id: ID) -> Result<ObjectModel, StorageError> { pub fn undefine_object(&mut self, id: ID) -> Result<ObjectModel, StorageError> {
return if let Some(t) = self.obj_models.remove(&id) { return if let Some(t) = self.obj_models.remove(&id) {
// Remove relation templates // Remove relation templates
@ -167,9 +135,10 @@ impl InMemoryStorage {
Ok(t) Ok(t)
} else { } else {
Err(StorageError::NotExist(format!("object model {}", id).into())) 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> { pub fn undefine_relation(&mut self, id: ID) -> Result<model::RelationModel, StorageError> {
return if let Some(t) = self.rel_models.remove(&id) { return if let Some(t) = self.rel_models.remove(&id) {
// Remove relations // Remove relations
@ -185,9 +154,10 @@ impl InMemoryStorage {
Ok(t) Ok(t)
} else { } else {
Err(StorageError::NotExist(format!("relation model {}", id).into())) 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> { pub fn undefine_property(&mut self, id: ID) -> Result<model::PropertyModel, StorageError> {
return if let Some(t) = self.prop_models.remove(&id) { return if let Some(t) = self.prop_models.remove(&id) {
// Remove relations // Remove relations
@ -195,12 +165,12 @@ impl InMemoryStorage {
Ok(t) Ok(t)
} else { } else {
Err(StorageError::NotExist(format!("property model {}", id).into())) Err(StorageError::NotExist(format!("property model {}", id).into()))
} };
} }
// DATA // 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> { pub fn insert_object(&mut self, insobj: InsertObj) -> Result<ID, StorageError> {
let obj_model_id = insobj.model_id; let obj_model_id = insobj.model_id;
@ -212,7 +182,7 @@ impl InMemoryStorage {
let object_id = next_id(); let object_id = next_id();
let object = data::Object { let object = data::Object {
id: object_id, 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> {
@ -230,7 +200,7 @@ impl InMemoryStorage {
object_id, object_id,
model_id: prop.id, model_id: prop.id,
value: val_instance.value.cast_to(prop.data_type) 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 { } else {
@ -240,7 +210,7 @@ impl InMemoryStorage {
id: next_id(), id: next_id(),
object_id, object_id,
model_id: prop.id, model_id: prop.id,
value: def.clone() value: def.clone(),
}); });
} else { } else {
return Err(StorageError::ConstraintViolation(format!("{} is required for {} (and no default value is defined)", prop, obj_model).into())); 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(), id: next_id(),
object_id, object_id,
model_id: rel_instance.model_id, model_id: rel_instance.model_id,
related_id: rel_instance.related_id related_id: rel_instance.related_id,
}); });
} }
} else { } else {

@ -1,12 +1,13 @@
//! Data model structs and enums //! Data model structs and enums
use serde::{Serialize, Deserialize};
use super::ID;
use super::data::TypedValue;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::fmt; use std::fmt;
use serde::{Deserialize, Serialize};
use super::data::TypedValue;
use super::ID;
/// Get a description of a struct /// Get a description of a struct
pub trait Describe { pub trait Describe {
/// Short but informative description for error messages /// Short but informative description for error messages

Loading…
Cancel
Save