microrm 0.6.3

Lightweight ORM using sqlite as a backend
Documentation
use std::collections::HashMap;

use crate::schema::{
    datum::{Datum, DatumDiscriminator},
    entity::{Entity, EntityPart, EntityPartList, EntityPartVisitor, EntityVisitor},
    relation::Relation,
};

#[derive(Debug)]
pub enum PartType {
    /// stores sql data type
    Datum(&'static str),
    /// stores the entity name
    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());
        // cases:
        // 1. we haven't seen this entity
        // 2. we've seen this entity before
        //  2a. this entity has no idmap yet
        //  2b. this entity has an idmap already

        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;
        // sanity-check
        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);
    }
}