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 ;
#[ 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
}
}
pub use id ::ID ;
use id ::next_id ;
pub mod model ;
pub mod data ;
pub mod insert ;
#[ 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 )
}
}