use std::collections::HashMap;
use crate::schema::{
datum::{Datum, DatumDiscriminator},
entity::{Entity, EntityPart, EntityPartList, EntityPartVisitor, EntityVisitor},
relation::Relation,
};
#[derive(Debug)]
pub enum PartType {
Datum(&'static str),
IDReference(&'static str),
RelationDomain {
table_name: String,
range_name: &'static str,
injective: bool,
},
RelationRange {
table_name: String,
domain_name: &'static str,
injective: bool,
},
}
#[derive(Debug)]
pub struct PartState {
pub name: &'static str,
pub ty: PartType,
pub unique: bool,
pub key: bool,
}
impl PartState {
fn build<EP: EntityPart>() -> Self {
struct Discriminator<EP: EntityPart> {
ty: Option<PartType>,
_ghost: std::marker::PhantomData<EP>,
}
impl<EP: EntityPart> DatumDiscriminator for Discriminator<EP> {
fn visit_entity_id<E: Entity>(&mut self) {
self.ty = Some(PartType::IDReference(E::entity_name()));
}
fn visit_bare_field<T: Datum>(&mut self) {
self.ty = Some(PartType::Datum(T::sql_type()));
}
fn visit_serialized<T: serde::Serialize + serde::de::DeserializeOwned>(&mut self) {
self.ty = Some(PartType::Datum("text"));
}
fn visit_relation_map<E: Entity>(&mut self) {
self.ty = Some(PartType::RelationDomain {
table_name: format!(
"{}_{}_relation_{}",
EP::Entity::entity_name(),
E::entity_name(),
EP::part_name()
),
range_name: E::entity_name(),
injective: false,
});
}
fn visit_relation_domain<R: Relation>(&mut self) {
self.ty = Some(PartType::RelationDomain {
table_name: format!(
"{}_{}_relation_{}",
R::Domain::entity_name(),
R::Range::entity_name(),
R::NAME
),
range_name: R::Range::entity_name(),
injective: R::INJECTIVE,
});
}
fn visit_relation_range<R: Relation>(&mut self) {
self.ty = Some(PartType::RelationRange {
table_name: format!(
"{}_{}_relation_{}",
R::Domain::entity_name(),
R::Range::entity_name(),
R::NAME
),
domain_name: R::Domain::entity_name(),
injective: R::INJECTIVE,
});
}
fn visit_value<T: serde::de::DeserializeOwned>(&mut self) {
self.ty = Some(PartType::Datum("text"));
}
}
let mut discrim = Discriminator::<EP> {
ty: None,
_ghost: Default::default(),
};
<EP::Datum>::accept_discriminator(&mut discrim);
if let Some(ty) = discrim.ty {
PartState {
name: EP::part_name(),
ty,
unique: EP::unique(),
key: false,
}
} else {
unreachable!("no PartType extracted from EntityPart")
}
}
}
#[derive(Debug)]
pub struct EntityState {
pub name: &'static str,
typeid: std::any::TypeId,
pub parts: Vec<PartState>,
pub has_idmap: bool,
}
impl EntityState {
fn build<E: Entity>() -> Self {
struct PartVisitor<E: Entity>(Vec<PartState>, std::marker::PhantomData<E>);
impl<E: Entity> EntityPartVisitor for PartVisitor<E> {
type Entity = E;
fn visit<EP: EntityPart>(&mut self) {
self.0.push(PartState::build::<EP>());
}
}
let mut pv = PartVisitor(vec![], Default::default());
E::accept_part_visitor(&mut pv);
struct KeyVisitor<'l, E: Entity>(&'l mut Vec<PartState>, std::marker::PhantomData<E>);
impl<E: Entity> EntityPartVisitor for KeyVisitor<'_, E> {
type Entity = E;
fn visit<EP: EntityPart>(&mut self) {
for part in self.0.iter_mut() {
if part.name == EP::part_name() {
part.key = true;
}
}
}
}
<E::Keys as EntityPartList>::accept_part_visitor(&mut KeyVisitor::<E>(
&mut pv.0,
Default::default(),
));
Self {
name: E::entity_name(),
typeid: std::any::TypeId::of::<E>(),
parts: pv.0,
has_idmap: false,
}
}
}
#[derive(Default, Debug)]
pub struct EntityStateContainer {
states: HashMap<&'static str, EntityState>,
}
impl EntityStateContainer {
pub fn iter_states(&self) -> impl Iterator<Item = &EntityState> {
self.states.values()
}
pub fn visit_idmap<E: Entity>(&mut self) {
self.handle_visit::<E>(true);
}
fn handle_visit<E: Entity>(&mut self, is_idmap: bool) {
let entry = self.states.entry(E::entity_name());
use std::collections::hash_map::Entry;
if let Entry::Occupied(mut entry) = entry {
if entry.get().has_idmap && is_idmap {
panic!("duplicate IDMap for entity {}", E::entity_name())
} else if is_idmap {
entry.get_mut().has_idmap = true;
}
return;
}
let entry = entry.or_insert_with(EntityState::build::<E>);
entry.has_idmap = is_idmap;
if entry.typeid != std::any::TypeId::of::<E>() {
panic!("Identical entity name but different typeid!");
}
struct RecursiveVisitor<'a, E: Entity>(
&'a mut EntityStateContainer,
std::marker::PhantomData<E>,
);
impl<E: Entity> EntityPartVisitor for RecursiveVisitor<'_, E> {
type Entity = E;
fn visit<EP: EntityPart>(&mut self) {
EP::Datum::accept_entity_visitor(self.0);
}
}
E::accept_part_visitor(&mut RecursiveVisitor(self, Default::default()));
}
}
impl EntityVisitor for EntityStateContainer {
fn visit<E: Entity>(&mut self) {
self.handle_visit::<E>(false);
}
}