microrm 0.6.3

Lightweight ORM using sqlite as a backend
Documentation
use super::{
    parse::{EntityKey, Verb},
    Autogenerate, CLIError, EntityInterface,
};
use crate::{
    query::{Insertable, Queryable, RelationInterface},
    schema::{
        datum::{Datum, DatumDiscriminatorRef, OwnedDatumList},
        entity::{Entity, EntityID, EntityPart, EntityPartList, EntityPartVisitor},
        relation::{Relation, RelationDomain, RelationMap, RelationRange},
    },
    DBResult, Transaction,
};

// helper alias for later
type UniqueList<E> = <<E as Entity>::Keys as EntityPartList>::DatumList;

impl<EI: EntityInterface> Autogenerate<EI> {
    /// Execute the parsed command.
    pub fn perform(
        self,
        ctx: &EI::Context,
        txn: &mut Transaction,
        query_ctx: impl Queryable<EntityOutput = EI::Entity> + Insertable<EI::Entity>,
    ) -> Result<(), EI::Error> {
        match self.verb {
            Verb::Attach {
                local_keys,
                relation,
                remote_keys,
            } => {
                let local_keys = EntityKey::to_string_vec::<EI>(&local_keys, ctx);
                let remote_keys = EntityKey::to_string_vec::<EI>(&remote_keys, ctx);
                let outer_obj = query_ctx
                    .keyed(UniqueList::<EI::Entity>::build_equivalent(
                        local_keys.iter().map(String::as_str),
                    ))
                    .get(txn)?
                    .ok_or(<EI::Error>::no_such_entity(
                        EI::Entity::entity_name(),
                        local_keys
                            .iter()
                            .cloned()
                            .reduce(|a, b| format!("{},{}", a, b))
                            .unwrap()
                            .to_string(),
                    ))?;

                let mut attacher = Attacher {
                    do_attach: true,
                    relation: relation.as_str(),
                    txn,
                    remote_keys,
                    err: None,
                    _ghost: Default::default(),
                };
                outer_obj.accept_part_visitor_ref(&mut attacher)?;

                if let Some(err) = attacher.err {
                    return Err(err);
                }
            },
            Verb::Delete(keys) => {
                let keys = EntityKey::to_string_vec::<EI>(&keys, ctx);
                query_ctx
                    .keyed(UniqueList::<EI::Entity>::build_equivalent(
                        keys.iter().map(String::as_str),
                    ))
                    .delete(txn)?;
            },
            Verb::Detach {
                local_keys,
                relation,
                remote_keys,
            } => {
                let local_keys = EntityKey::to_string_vec::<EI>(&local_keys, ctx);
                let remote_keys = EntityKey::to_string_vec::<EI>(&remote_keys, ctx);
                let outer_obj = query_ctx
                    .keyed(UniqueList::<EI::Entity>::build_equivalent(
                        local_keys.iter().map(String::as_str),
                    ))
                    .get(txn)?
                    .ok_or(<EI::Error>::no_such_entity(
                        EI::Entity::entity_name(),
                        local_keys
                            .iter()
                            .cloned()
                            .reduce(|a, b| format!("{},{}", a, b))
                            .unwrap()
                            .to_string(),
                    ))?;

                let mut attacher = Attacher {
                    do_attach: false,
                    relation: relation.as_str(),
                    txn,
                    remote_keys,
                    err: None,
                    _ghost: Default::default(),
                };
                outer_obj.accept_part_visitor_ref(&mut attacher)?;

                if let Some(err) = attacher.err {
                    return Err(err);
                }
            },
            Verb::ListAll => {
                println!(
                    "Listing all {}(s): ({})",
                    EI::Entity::entity_name(),
                    query_ctx.clone().count(txn)?
                );
                for obj in query_ctx.get(txn)?.into_iter() {
                    println!(
                        " - {}",
                        EI::summarize(&obj).unwrap_or_else(|| format!("{:?}", obj))
                    );
                }
            },
            Verb::Inspect(keys) => {
                let keys = EntityKey::to_string_vec::<EI>(&keys, ctx);
                let obj = query_ctx
                    .keyed(UniqueList::<EI::Entity>::build_equivalent(
                        keys.iter().map(String::as_str),
                    ))
                    .get(txn)?
                    .ok_or(<EI::Error>::no_such_entity(
                        EI::Entity::entity_name(),
                        keys.iter()
                            .cloned()
                            .reduce(|a, b| format!("{},{}", a, b))
                            .unwrap() // XXX: handle case of no unique key!
                            .to_string(),
                    ))?;
                println!("{:#?}", obj.as_ref());

                fn inspect_ai<AI: RelationInterface>(
                    name: &'static str,
                    txn: &mut Transaction,
                    ai: &AI,
                ) {
                    println!("{}: ({})", name, ai.count(txn).unwrap());
                    for a in ai.get(txn).expect("couldn't get object relations") {
                        println!("[#{:3}]: {:?}", a.id().into_raw(), a.wrapped());
                    }
                }

                struct RelationFieldWalker<'r, E: Entity>(
                    &'r mut Transaction,
                    std::marker::PhantomData<E>,
                );
                impl<E: Entity> EntityPartVisitor for RelationFieldWalker<'_, E> {
                    type Entity = E;
                    fn visit_datum<EP: EntityPart>(&mut self, datum: &EP::Datum) -> DBResult<()> {
                        struct Discriminator<'r>(&'r mut Transaction, &'static str);

                        impl DatumDiscriminatorRef for Discriminator<'_> {
                            fn visit_serialized<
                                T: serde::Serialize + serde::de::DeserializeOwned,
                            >(
                                &mut self,
                                _: &T,
                            ) {
                            }
                            fn visit_bare_field<T: Datum>(&mut self, _: &T) {}
                            fn visit_entity_id<E: Entity>(&mut self, _: &E::ID) {}
                            fn visit_relation_map<E: Entity>(&mut self, amap: &RelationMap<E>) {
                                inspect_ai(self.1, self.0, amap);
                            }
                            fn visit_relation_domain<R: Relation>(
                                &mut self,
                                adomain: &RelationDomain<R>,
                            ) {
                                inspect_ai(self.1, self.0, adomain);
                            }
                            fn visit_relation_range<R: Relation>(
                                &mut self,
                                arange: &RelationRange<R>,
                            ) {
                                inspect_ai(self.1, self.0, arange);
                            }

                            fn visit_value<T: serde::de::DeserializeOwned>(&mut self, _: &T) {}
                        }

                        datum.accept_discriminator_ref(&mut Discriminator(self.0, EP::part_name()));
                        Ok(())
                    }
                }

                obj.accept_part_visitor_ref(&mut RelationFieldWalker(txn, Default::default()))?;
            },
            Verb::Custom(custom) => {
                EI::run_custom(ctx, custom, txn, query_ctx)?;
            },
        }

        Ok(())
    }
}

/// helper type for attach and detach verbs
struct Attacher<'r, Error: CLIError, E: Entity> {
    do_attach: bool,
    relation: &'r str,
    txn: &'r mut Transaction,
    remote_keys: Vec<String>,
    err: Option<Error>,
    _ghost: std::marker::PhantomData<E>,
}

impl<Error: CLIError, OE: Entity> Attacher<'_, Error, OE> {
    fn do_operation<E: Entity>(&mut self, map: &impl RelationInterface<RemoteEntity = E>) {
        match map
            .query_all()
            .keyed(UniqueList::<E>::build_equivalent(
                self.remote_keys.iter().map(String::as_str),
            ))
            .get(self.txn)
        {
            Ok(Some(obj)) => {
                if self.do_attach {
                    self.err = map.connect_to(self.txn, obj.id()).err().map(Into::into);
                } else {
                    self.err = map
                        .disconnect_from(self.txn, obj.id())
                        .err()
                        .map(Into::into);
                }
            },
            Ok(None) => {
                self.err = Some(Error::no_such_entity(
                    E::entity_name(),
                    self.remote_keys
                        .iter()
                        .cloned()
                        .reduce(|a, b| format!("{},{}", a, b))
                        .unwrap_or_default()
                        .to_string(),
                ));
            },
            Err(e) => {
                self.err = Some(e.into());
            },
        }
    }
}

impl<Error: CLIError, E: Entity> EntityPartVisitor for Attacher<'_, Error, E> {
    type Entity = E;
    fn visit_datum<EP: EntityPart>(&mut self, datum: &EP::Datum) -> DBResult<()> {
        if EP::part_name() != self.relation {
            return Ok(());
        }

        datum.accept_discriminator_ref(self);
        Ok(())
    }
}

impl<Error: CLIError, OE: Entity> DatumDiscriminatorRef for Attacher<'_, Error, OE> {
    fn visit_entity_id<E: Entity>(&mut self, _: &E::ID) {
        unreachable!()
    }
    fn visit_serialized<T: serde::Serialize + serde::de::DeserializeOwned>(&mut self, _: &T) {
        unreachable!()
    }
    fn visit_bare_field<T: Datum>(&mut self, _: &T) {
        unreachable!()
    }
    fn visit_value<T: serde::de::DeserializeOwned>(&mut self, _: &T) {
        unreachable!()
    }

    fn visit_relation_map<E: Entity>(&mut self, map: &RelationMap<E>) {
        self.do_operation(map);
    }
    fn visit_relation_range<R: Relation>(&mut self, map: &RelationRange<R>) {
        self.do_operation(map);
    }
    fn visit_relation_domain<R: Relation>(&mut self, map: &RelationDomain<R>) {
        self.do_operation(map);
    }
}