Browse Source

Rename 'association' to 'relation'.

Kestrel 7 months ago
parent
commit
ca6342f818

+ 2 - 2
microrm-macros/src/entity.rs

@@ -226,14 +226,14 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
             }
 
             fn build_from<'a>(
-                adata: ::microrm::schema::AssocData,
+                rdata: ::microrm::schema::relation::RelationData,
                 stmt: &mut ::microrm::db::StatementRow,
                 index: &mut i32,
             ) -> ::microrm::DBResult<Self>
             where
                 Self: Sized,
             {
-                Ok(Self(<i64 as ::microrm::schema::datum::Datum>::build_from(adata, stmt, index)?))
+                Ok(Self(<i64 as ::microrm::schema::datum::Datum>::build_from(rdata, stmt, index)?))
             }
 
             fn accept_discriminator(d: &mut impl ::microrm::schema::datum::DatumDiscriminator) where Self: Sized {

+ 5 - 5
microrm/src/cli.rs

@@ -102,7 +102,7 @@ impl clap::Subcommand for EmptyCommand {
 
 #[cfg(test)]
 mod tests {
-    use crate::schema::{AssocDomain, AssocRange, Relation};
+    use crate::schema::{RelationDomain, RelationRange, Relation};
 
     use super::{CLIObject, ClapInterface, InterfaceCustomization, ValueRole};
     use clap::Parser;
@@ -128,7 +128,7 @@ mod tests {
         #[key]
         name: String,
 
-        txs: AssocRange<CTRelation>,
+        txs: RelationRange<CTRelation>,
     }
 
     #[derive(Entity)]
@@ -136,7 +136,7 @@ mod tests {
         #[key]
         name: String,
 
-        txs: AssocRange<ETRelation>,
+        txs: RelationRange<ETRelation>,
     }
 
     #[derive(Entity)]
@@ -145,8 +145,8 @@ mod tests {
         title: String,
         amount: isize,
 
-        customer: AssocDomain<CTRelation>,
-        employee: AssocDomain<ETRelation>,
+        customer: RelationDomain<CTRelation>,
+        employee: RelationDomain<ETRelation>,
     }
 
     #[derive(Database)]

+ 19 - 19
microrm/src/cli/clap_interface.rs

@@ -222,7 +222,7 @@ struct Attacher<'l, Error: CLIError, E: Entity> {
 }
 
 impl<'l, Error: CLIError, OE: Entity> Attacher<'l, Error, OE> {
-    fn do_operation<E: Entity>(&mut self, map: &impl AssocInterface<RemoteEntity = E>) {
+    fn do_operation<E: Entity>(&mut self, map: &impl RelationInterface<RemoteEntity = E>) {
         match map
             .query_all()
             .keyed(
@@ -278,18 +278,18 @@ impl<'l, Error: CLIError, OE: Entity> DatumDiscriminatorRef for Attacher<'l, Err
         unreachable!()
     }
 
-    fn visit_assoc_map<E: Entity>(&mut self, map: &AssocMap<E>) {
+    fn visit_relation_map<E: Entity>(&mut self, map: &RelationMap<E>) {
         self.do_operation(map);
     }
-    fn visit_assoc_range<R: microrm::schema::Relation>(
+    fn visit_relation_range<R: microrm::schema::Relation>(
         &mut self,
-        map: &microrm::schema::AssocRange<R>,
+        map: &microrm::schema::RelationRange<R>,
     ) {
         self.do_operation(map);
     }
-    fn visit_assoc_domain<R: microrm::schema::Relation>(
+    fn visit_relation_domain<R: microrm::schema::Relation>(
         &mut self,
-        map: &microrm::schema::AssocDomain<R>,
+        map: &microrm::schema::RelationDomain<R>,
     ) {
         self.do_operation(map);
     }
@@ -454,15 +454,15 @@ impl<O: CLIObject, IC: InterfaceCustomization> ClapInterface<O, IC> {
                     ))?;
                 println!("{:#?}", obj.as_ref());
 
-                fn inspect_ai<AI: AssocInterface>(name: &'static str, ai: &AI) {
+                fn inspect_ai<AI: RelationInterface>(name: &'static str, ai: &AI) {
                     println!("{}: ({})", name, ai.count().unwrap());
-                    for a in ai.get().expect("couldn't get object associations") {
+                    for a in ai.get().expect("couldn't get object relations") {
                         println!("[#{:3}]: {:?}", a.id().into_raw(), a.wrapped());
                     }
                 }
 
-                struct AssocFieldWalker<E: Entity>(std::marker::PhantomData<E>);
-                impl<E: Entity> EntityPartVisitor for AssocFieldWalker<E> {
+                struct RelationFieldWalker<E: Entity>(std::marker::PhantomData<E>);
+                impl<E: Entity> EntityPartVisitor for RelationFieldWalker<E> {
                     type Entity = E;
                     fn visit_datum<EP: microrm::schema::entity::EntityPart>(
                         &mut self,
@@ -480,18 +480,18 @@ impl<O: CLIObject, IC: InterfaceCustomization> ClapInterface<O, IC> {
                             }
                             fn visit_bare_field<T: Datum>(&mut self, _: &T) {}
                             fn visit_entity_id<E: Entity>(&mut self, _: &E::ID) {}
-                            fn visit_assoc_map<E: Entity>(&mut self, amap: &AssocMap<E>) {
+                            fn visit_relation_map<E: Entity>(&mut self, amap: &RelationMap<E>) {
                                 inspect_ai(self.0, amap);
                             }
-                            fn visit_assoc_domain<R: microrm::schema::Relation>(
+                            fn visit_relation_domain<R: microrm::schema::Relation>(
                                 &mut self,
-                                adomain: &microrm::schema::AssocDomain<R>,
+                                adomain: &microrm::schema::RelationDomain<R>,
                             ) {
                                 inspect_ai(self.0, adomain);
                             }
-                            fn visit_assoc_range<R: microrm::schema::Relation>(
+                            fn visit_relation_range<R: microrm::schema::Relation>(
                                 &mut self,
-                                arange: &microrm::schema::AssocRange<R>,
+                                arange: &microrm::schema::RelationRange<R>,
                             ) {
                                 inspect_ai(self.0, arange);
                             }
@@ -501,7 +501,7 @@ impl<O: CLIObject, IC: InterfaceCustomization> ClapInterface<O, IC> {
                     }
                 }
 
-                obj.accept_part_visitor_ref(&mut AssocFieldWalker(Default::default()));
+                obj.accept_part_visitor_ref(&mut RelationFieldWalker(Default::default()));
             }
             InterfaceVerb::Extra(extra) => {
                 O::run_extra_command(data, extra, query_ctx, insert_ctx)?;
@@ -533,19 +533,19 @@ impl<O: CLIObject, IC: InterfaceCustomization> ClapInterface<O, IC> {
                     ) {
                     }
                     fn visit_bare_field<T: Datum>(&mut self) {}
-                    fn visit_assoc_map<E: Entity>(&mut self) {
+                    fn visit_relation_map<E: Entity>(&mut self) {
                         self.0.push(add_keys::<E, IC>(
                             clap::Command::new(self.1),
                             ValueRole::AttachmentTarget,
                         ));
                     }
-                    fn visit_assoc_domain<R: microrm::schema::Relation>(&mut self) {
+                    fn visit_relation_domain<R: microrm::schema::Relation>(&mut self) {
                         self.0.push(add_keys::<R::Range, IC>(
                             clap::Command::new(self.1),
                             ValueRole::AttachmentTarget,
                         ));
                     }
-                    fn visit_assoc_range<R: microrm::schema::Relation>(&mut self) {
+                    fn visit_relation_range<R: microrm::schema::Relation>(&mut self) {
                         self.0.push(add_keys::<R::Domain, IC>(
                             clap::Command::new(self.1),
                             ValueRole::AttachmentTarget,

+ 5 - 4
microrm/src/lib.rs

@@ -12,7 +12,7 @@
 //! - Object modelling (via the [`Datum`](schema/datum/trait.Datum.html) and
 //! [`Entity`](schema/entity/trait.Entity.html) traits)
 //! - Database querying (via [`Queryable`](prelude/trait.Queryable.html),
-//! [`AssocInterface`](prelude/trait.AssocInterface.html) and
+//! [`RelationInterface`](prelude/trait.RelationInterface.html) and
 //! [`Insertable`](prelude/trait.Insertable.html) traits)
 //! - Command-line interface generation via the [`clap`](https://docs.rs/clap/latest/clap/) crate
 //! (see [`cli::CLIObject`] and [`cli::ClapInterface`]; requires the optional crate feature `clap`)
@@ -90,15 +90,16 @@ pub mod db;
 mod query;
 pub mod schema;
 
-pub use schema::{AssocDomain, AssocMap, AssocRange, IDMap, Relation, Serialized, Stored};
+pub use schema::{IDMap, Serialized, Stored};
+pub use schema::relation::{Relation, RelationMap, RelationDomain, RelationRange};
 
 #[cfg(feature = "clap")]
 pub mod cli;
 
 /// Module prelude with commonly-used traits that shouldn't have overlap with other crates.
 pub mod prelude {
-    pub use crate::query::{AssocInterface, Insertable, Queryable};
-    pub use crate::schema::{AssocMap, Database, IDMap};
+    pub use crate::query::{RelationInterface, Insertable, Queryable};
+    pub use crate::schema::{Database, relation::Relation};
     pub use microrm_macros::{Database, Entity};
 }
 

+ 77 - 77
microrm/src/query.rs

@@ -1,13 +1,13 @@
-use crate::db::{Connection, StatementContext, StatementRow, Transaction};
-use crate::prelude::IDMap;
-use crate::schema::datum::{QueryEquivalent, QueryEquivalentList};
-use crate::schema::entity::helpers::check_assoc;
-use crate::schema::{AssocData, LocalSide, Stored};
 use crate::{
-    schema::datum::Datum,
-    schema::entity::{Entity, EntityID, EntityPart, EntityPartList, EntityPartVisitor},
+    DBResult, Error,
+    db::{Connection, StatementContext, StatementRow, Transaction},
+    schema::{
+        datum::{Datum, QueryEquivalent, QueryEquivalentList},
+        entity::{Entity, EntityID, EntityPart, EntityPartList, EntityPartVisitor, helpers::check_relation},
+        relation::{RelationData, LocalSide}, Stored, IDMap,
+    },
 };
-use crate::{DBResult, Error};
+
 use std::collections::HashMap;
 use std::hash::{Hash, Hasher};
 
@@ -29,9 +29,9 @@ pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID>
             impl<'a, E: Entity> EntityPartVisitor for PartNameVisitor<'a, E> {
                 type Entity = E;
                 fn visit<EP: EntityPart>(&mut self) {
-                    // if this is a set-association, then we don't actually want to do anything
+                    // if this is a set-relation, then we don't actually want to do anything
                     // with it here; it doesn't have a column
-                    if check_assoc::<EP>() {
+                    if check_relation::<EP>() {
                         return;
                     }
 
@@ -68,8 +68,8 @@ pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID>
             impl<'a, 'b, E: Entity> EntityPartVisitor for PartBinder<'a, 'b, E> {
                 type Entity = E;
                 fn visit_datum<EP: EntityPart>(&mut self, datum: &EP::Datum) {
-                    // skip associations, as with the query preparation above
-                    if check_assoc::<EP>() {
+                    // skip relations, as with the query preparation above
+                    if check_relation::<EP>() {
                         return;
                     }
 
@@ -90,12 +90,12 @@ pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID>
 pub(crate) fn insert_and_return<E: Entity>(conn: &Connection, mut value: E) -> DBResult<Stored<E>> {
     let id = insert(conn, &value)?;
 
-    // update assoc data in all fields
+    // update relation data in all fields
     struct DatumWalker<'l, E: Entity>(&'l Connection, i64, std::marker::PhantomData<E>);
     impl<'l, E: Entity> EntityPartVisitor for DatumWalker<'l, E> {
         type Entity = E;
         fn visit_datum_mut<EP: EntityPart>(&mut self, datum: &mut EP::Datum) {
-            datum.update_adata(AssocData {
+            datum.update_adata(RelationData {
                 conn: self.0.clone(),
                 part_name: EP::part_name(),
                 local_name: <EP::Entity as Entity>::entity_name(),
@@ -120,9 +120,9 @@ pub(crate) fn update_entity<E: Entity>(conn: &Connection, value: &Stored<E>) ->
             impl<'a, E: Entity> EntityPartVisitor for PartNameVisitor<'a, E> {
                 type Entity = E;
                 fn visit<EP: EntityPart>(&mut self) {
-                    // if this is a set-association, then we don't actually want to do anything
+                    // if this is a set-relation, then we don't actually want to do anything
                     // with it here; it doesn't have a column
-                    if check_assoc::<EP>() {
+                    if check_relation::<EP>() {
                         return;
                     }
 
@@ -150,8 +150,8 @@ pub(crate) fn update_entity<E: Entity>(conn: &Connection, value: &Stored<E>) ->
             impl<'a, 'b, E: Entity> EntityPartVisitor for PartBinder<'a, 'b, E> {
                 type Entity = E;
                 fn visit_datum<EP: EntityPart>(&mut self, datum: &EP::Datum) {
-                    // skip associations, as with the query preparation above
-                    if check_assoc::<EP>() {
+                    // skip relations, as with the query preparation above
+                    if check_relation::<EP>() {
                         return;
                     }
 
@@ -283,7 +283,7 @@ impl Query {
     }
 }
 
-pub(crate) struct AssocNames {
+pub(crate) struct RelationNames {
     local_name: &'static str,
     remote_name: &'static str,
     part_name: &'static str,
@@ -294,12 +294,12 @@ pub(crate) struct AssocNames {
     remote_field: &'static str,
 }
 
-impl AssocNames {
-    fn collect<AI: AssocInterface>(iface: &AI) -> DBResult<AssocNames> {
-        let adata = iface.get_data()?;
-        let local_name = adata.local_name;
+impl RelationNames {
+    fn collect<AI: RelationInterface>(iface: &AI) -> DBResult<RelationNames> {
+        let rdata = iface.get_data()?;
+        let local_name = rdata.local_name;
         let remote_name = <AI::RemoteEntity>::entity_name();
-        let part_name = adata.part_name;
+        let part_name = rdata.part_name;
         let dist_name = iface.get_distinguishing_name()?;
 
         let (domain_name, range_name) = match AI::SIDE {
@@ -322,9 +322,9 @@ impl AssocNames {
         })
     }
 
-    fn assoc_name(&self) -> String {
+    fn relation_name(&self) -> String {
         format!(
-            "{domain_name}_{range_name}_assoc_{dist_name}",
+            "{domain_name}_{range_name}_relation_{dist_name}",
             domain_name = self.domain_name,
             range_name = self.range_name,
             dist_name = self.dist_name
@@ -339,22 +339,22 @@ fn hash_of<T: Hash>(val: T) -> u64 {
 }
 
 fn do_connect<Remote: Entity>(
-    adata: &AssocData,
-    an: AssocNames,
+    rdata: &RelationData,
+    an: RelationNames,
     remote_id: Remote::ID,
 ) -> DBResult<()> {
-    adata.conn.with_prepared(
+    rdata.conn.with_prepared(
         hash_of(("connect", an.local_name, an.remote_name, an.part_name)),
         || {
             format!(
-                "insert into `{assoc_name}` (`{local_field}`, `{remote_field}`) values (?, ?) returning (`id`)",
-                assoc_name = an.assoc_name(),
+                "insert into `{relation_name}` (`{local_field}`, `{remote_field}`) values (?, ?) returning (`id`)",
+                relation_name = an.relation_name(),
                 local_field = an.local_field,
                 remote_field = an.remote_field
             )
         },
         |ctx| {
-            ctx.bind(1, adata.local_id)?;
+            ctx.bind(1, rdata.local_id)?;
             ctx.bind(2, remote_id.into_raw())?;
 
             ctx.run()?
@@ -364,20 +364,20 @@ fn do_connect<Remote: Entity>(
     )
 }
 
-/// Assocation (relation) map generic interface trait.
-pub trait AssocInterface: 'static {
+/// Relation map generic interface trait.
+pub trait RelationInterface: 'static {
     /// The type of the entity on the non-local end of the relation.
     type RemoteEntity: Entity;
 
     #[doc(hidden)]
-    fn get_data(&self) -> DBResult<&AssocData>;
+    fn get_data(&self) -> DBResult<&RelationData>;
     #[doc(hidden)]
     fn get_distinguishing_name(&self) -> DBResult<&'static str>;
 
     /// Which side is the "local" side of the relation.
     const SIDE: LocalSide;
 
-    /// Query this entity type without the association filter.
+    /// Query this entity type without the relation filter.
     fn query_all(&self) -> impl Queryable<EntityOutput = Self::RemoteEntity> {
         components::TableComponent::<Self::RemoteEntity>::new(self.get_data().unwrap().conn.clone())
     }
@@ -387,12 +387,12 @@ pub trait AssocInterface: 'static {
     where
         Self: Sized,
     {
-        let adata = self.get_data()?;
-        let an = AssocNames::collect::<Self>(self)?;
+        let rdata = self.get_data()?;
+        let an = RelationNames::collect::<Self>(self)?;
 
-        let txn = Transaction::new(&adata.conn)?;
+        let txn = Transaction::new(&rdata.conn)?;
 
-        do_connect::<Self::RemoteEntity>(adata, an, remote_id)?;
+        do_connect::<Self::RemoteEntity>(rdata, an, remote_id)?;
 
         txn.commit()
     }
@@ -402,24 +402,24 @@ pub trait AssocInterface: 'static {
     where
         Self: Sized,
     {
-        let adata = self.get_data()?;
-        let an = AssocNames::collect::<Self>(self)?;
+        let rdata = self.get_data()?;
+        let an = RelationNames::collect::<Self>(self)?;
 
-        let txn = Transaction::new(&adata.conn)?;
+        let txn = Transaction::new(&rdata.conn)?;
 
-        // second, add to the assoc table
-        adata.conn.with_prepared(
+        // second, add to the relation table
+        rdata.conn.with_prepared(
             hash_of(("disconnect", an.local_name, an.remote_name, an.part_name)),
             || {
                 format!(
-                    "delete from `{assoc_name}` where `{local_field}` = ? and `{remote_field}` = ?",
-                    assoc_name = an.assoc_name(),
+                    "delete from `{relation_name}` where `{local_field}` = ? and `{remote_field}` = ?",
+                    relation_name = an.relation_name(),
                     local_field = an.local_field,
                     remote_field = an.remote_field
                 )
             },
             |ctx| {
-                ctx.bind(1, adata.local_id)?;
+                ctx.bind(1, rdata.local_id)?;
                 ctx.bind(2, remote_id.into_raw())?;
 
                 ctx.run().map(|_| ())
@@ -443,24 +443,24 @@ pub trait Insertable<E: Entity> {
     fn insert_and_return(&self, value: E) -> DBResult<Stored<E>>;
 }
 
-impl<AI: AssocInterface> Insertable<AI::RemoteEntity> for AI {
+impl<AI: RelationInterface> Insertable<AI::RemoteEntity> for AI {
     fn insert(&self, value: AI::RemoteEntity) -> DBResult<<AI::RemoteEntity as Entity>::ID>
     where
         Self: Sized,
     {
         // we're doing two things:
         // - inserting the entity into the target table
-        // - adding the association row into the assoc table
+        // - adding the relation row into the relation table
 
-        let adata = self.get_data()?;
-        let an = AssocNames::collect::<Self>(self)?;
+        let rdata = self.get_data()?;
+        let an = RelationNames::collect::<Self>(self)?;
 
-        let txn = Transaction::new(&adata.conn)?;
+        let txn = Transaction::new(&rdata.conn)?;
 
         // so first, into the remote table
-        let remote_id = insert(&adata.conn, &value)?;
-        // then the association
-        do_connect::<AI::RemoteEntity>(adata, an, remote_id)?;
+        let remote_id = insert(&rdata.conn, &value)?;
+        // then the relation
+        do_connect::<AI::RemoteEntity>(rdata, an, remote_id)?;
 
         txn.commit()?;
 
@@ -473,17 +473,17 @@ impl<AI: AssocInterface> Insertable<AI::RemoteEntity> for AI {
     {
         // we're doing two things:
         // - inserting the entity into the target table
-        // - adding the association row into the assoc table
+        // - adding the relation row into the relation table
 
-        let adata = self.get_data()?;
-        let an = AssocNames::collect::<Self>(self)?;
+        let rdata = self.get_data()?;
+        let an = RelationNames::collect::<Self>(self)?;
 
-        let txn = Transaction::new(&adata.conn)?;
+        let txn = Transaction::new(&rdata.conn)?;
 
         // so first, into the remote table
-        let remote = insert_and_return(&adata.conn, value)?;
-        // then the association
-        do_connect::<AI::RemoteEntity>(adata, an, remote.id())?;
+        let remote = insert_and_return(&rdata.conn, value)?;
+        // then the relation
+        do_connect::<AI::RemoteEntity>(rdata, an, remote.id())?;
 
         txn.commit()?;
 
@@ -678,10 +678,10 @@ pub trait Queryable: Clone {
     }
 
     // ----------------------------------------------------------------------
-    // Association-following and joining methods
+    // Relationiation-following and joining methods
     // ----------------------------------------------------------------------
-    /// Join based on an existing association
-    fn join<AD: AssocInterface + Datum, EP: EntityPart<Entity = Self::EntityOutput, Datum = AD>>(
+    /// Join based on an existing relation
+    fn join<AD: RelationInterface + Datum, EP: EntityPart<Entity = Self::EntityOutput, Datum = AD>>(
         self,
         part: EP,
     ) -> impl Queryable<EntityOutput = AD::RemoteEntity, OutputContainer = Vec<Stored<AD::RemoteEntity>>>
@@ -711,38 +711,38 @@ impl<'a, T: Entity> Queryable for &'a IDMap<T> {
     }
 }
 
-// Generic implementation for all assoc specification types
-impl<'a, AI: AssocInterface> Queryable for &'a AI {
+// Generic implementation for all relation specification types
+impl<'a, AI: RelationInterface> Queryable for &'a AI {
     type EntityOutput = AI::RemoteEntity;
     type OutputContainer = Vec<Stored<AI::RemoteEntity>>;
     type StaticVersion = &'static AI;
 
     fn build(&self) -> Query {
-        let anames = AssocNames::collect(*self).unwrap();
-        let assoc_name = anames.assoc_name();
+        let anames = RelationNames::collect(*self).unwrap();
+        let relation_name = anames.relation_name();
         Query::new()
             .attach(QueryPart::Root, "SELECT DISTINCT".into())
             .attach(QueryPart::Columns, format!("`{}`.*", anames.remote_name))
-            .attach(QueryPart::From, format!("`{}`", assoc_name))
+            .attach(QueryPart::From, format!("`{}`", relation_name))
             .attach(
                 QueryPart::Join,
                 format!(
                     "`{}` ON `{}`.`id` = `{}`.`{}`",
-                    anames.remote_name, anames.remote_name, assoc_name, anames.remote_field
+                    anames.remote_name, anames.remote_name, relation_name, anames.remote_field
                 ),
             )
             .attach(
                 QueryPart::Where,
-                format!("`{}`.`{}` = ?", assoc_name, anames.local_field),
+                format!("`{}`.`{}` = ?", relation_name, anames.local_field),
             )
     }
     fn bind(&self, ctx: &mut StatementContext, index: &mut i32) {
-        let adata = self
+        let rdata = self
             .get_data()
-            .expect("binding query for assoc with no data");
+            .expect("binding query for relation with no data");
 
-        ctx.bind(*index, adata.local_id)
-            .expect("couldn't bind assoc id");
+        ctx.bind(*index, rdata.local_id)
+            .expect("couldn't bind relation id");
         *index += 1;
     }
 

+ 23 - 23
microrm/src/query/components.rs

@@ -3,11 +3,11 @@
 use crate::{
     db::{Connection, StatementContext},
     prelude::Queryable,
-    query::{AssocInterface, QueryPart},
+    query::{RelationInterface, QueryPart},
     schema::{
         datum::{Datum, DatumDiscriminator, DatumVisitor, QueryEquivalent, QueryEquivalentList},
         entity::{Entity, EntityPart, EntityPartList, EntityPartVisitor},
-        LocalSide, Stored,
+        relation::{Relation, LocalSide}, Stored,
     },
 };
 
@@ -287,7 +287,7 @@ impl<Parent: Queryable> Queryable for SingleComponent<Parent> {
     }
 }
 
-/// Join with another entity via an association
+/// Join with another entity via an relation
 pub(crate) struct JoinComponent<R: Entity, L: Entity, EP: EntityPart<Entity = L>, Parent: Queryable>
 {
     parent: Parent,
@@ -320,7 +320,7 @@ impl<
         R: Entity,
         L: Entity,
         EP: EntityPart<Entity = L, Datum = AI>,
-        AI: AssocInterface + Datum,
+        AI: RelationInterface + Datum,
         Parent: Queryable,
     > Queryable for JoinComponent<R, L, EP, Parent>
 {
@@ -344,14 +344,14 @@ impl<
                 unreachable!()
             }
 
-            fn visit_assoc_map<E: Entity>(&mut self) {
+            fn visit_relation_map<E: Entity>(&mut self) {
                 self.0 = Some(("domain", "range"));
             }
-            fn visit_assoc_domain<R: crate::schema::Relation>(&mut self) {
+            fn visit_relation_domain<R: Relation>(&mut self) {
                 self.0 = Some(("domain", "range"));
                 self.1 = Some(R::NAME);
             }
-            fn visit_assoc_range<R: crate::schema::Relation>(&mut self) {
+            fn visit_relation_range<R: Relation>(&mut self) {
                 self.0 = Some(("range", "domain"));
                 self.1 = Some(R::NAME);
             }
@@ -362,17 +362,17 @@ impl<
 
         let (local_field, remote_field) = d.0.unwrap();
 
-        // table name format is {domain}_{range}_assoc_{name}
-        let assoc_name = match AI::SIDE {
+        // table name format is {domain}_{range}_relation_{name}
+        let relation_name = match AI::SIDE {
             LocalSide::Range => {
                 format!(
-                    "{remote_name}_{local_name}_assoc_{}",
+                    "{remote_name}_{local_name}_relation_{}",
                     d.1.unwrap_or(EP::part_name())
                 )
             }
             LocalSide::Domain => {
                 format!(
-                    "{local_name}_{remote_name}_assoc_{}",
+                    "{local_name}_{remote_name}_relation_{}",
                     d.1.unwrap_or(EP::part_name())
                 )
             }
@@ -382,12 +382,12 @@ impl<
             .build()
             .attach(
                 QueryPart::Join,
-                format!("`{assoc_name}` ON `{local_name}`.`id` = `{assoc_name}`.`{local_field}`"),
+                format!("`{relation_name}` ON `{local_name}`.`id` = `{relation_name}`.`{local_field}`"),
             )
             .attach(
                 QueryPart::Join,
                 format!(
-                    "`{remote_name}` ON `{assoc_name}`.`{remote_field}` = `{remote_name}`.`id`"
+                    "`{remote_name}` ON `{relation_name}`.`{remote_field}` = `{remote_name}`.`id`"
                 ),
             )
             .replace(QueryPart::Columns, format!("`{remote_name}`.*"))
@@ -413,20 +413,20 @@ UniqueComponent<E, MapQueryable<E>>::get()
 WithComponent<E, EP, MapQueryable<E>>::get()
  -> SELECT * FROM {E::entity_name()} WHERE `col1` = ?
 
-AssocQueryable<AI>::get()
+RelationQueryable<AI>::get()
  -> SELECT
         `{AI::RemoteEntity::entity_name()}`.*
-    FROM `{assoc_table_name}`
-    LEFT JOIN `{AI::RemoteEntity::entity_name()}`.id = `{assoc_table_name}`.`{remote_field}`
-    WHERE `{assoc_table_name}`.`{local_field}` = ?
+    FROM `{relation_table_name}`
+    LEFT JOIN `{AI::RemoteEntity::entity_name()}`.id = `{relation_table_name}`.`{remote_field}`
+    WHERE `{relation_table_name}`.`{local_field}` = ?
 
-UniqueComponent<E, AssocQueryable<AI>>::get()
+UniqueComponent<E, RelationQueryable<AI>>::get()
  -> SELECT
         `{AI::RemoteEntity::entity_name()}`.*
-    FROM `{assoc_table_name}`
-    LEFT JOIN `{AI::RemoteEntity::entity_name()}`.id = `{assoc_table_name}`.`{remote_field}`
+    FROM `{relation_table_name}`
+    LEFT JOIN `{AI::RemoteEntity::entity_name()}`.id = `{relation_table_name}`.`{remote_field}`
     WHERE
-        `{assoc_table_name}`.`{local_field}` = ?
+        `{relation_table_name}`.`{local_field}` = ?
         AND `{AI::RemoteEntity::entity_name()}`.`col1` = ?
         AND `{AI::RemoteEntity::entity_name()}`.`col2` = ?
         ...
@@ -436,7 +436,7 @@ JoinComponent<R, E, EP, MapQueryable<E>>::get()
         `{R::entity_name()}`.*
     FROM
         `{E::entity_name()}`
-    LEFT JOIN `{assoc_table_name}` ON `{E::entity_name()}`.`id` = `{assoc_table_name}`.`{local_field}`
-    LEFT JOIN `{R::entity_name()}` ON `{assoc_table_name}`.`{remote_field}` = `{R::entity_name()}`.`id`
+    LEFT JOIN `{relation_table_name}` ON `{E::entity_name()}`.`id` = `{relation_table_name}`.`{local_field}`
+    LEFT JOIN `{R::entity_name()}` ON `{relation_table_name}`.`{remote_field}` = `{R::entity_name()}`.`id`
 
 */

+ 10 - 345
microrm/src/schema.rs

@@ -1,16 +1,16 @@
 //! Schema specification types.
 //!
 //! The following terminology used in some types in this module:
-//! - *domain*: one side of an association/relation, the "pointed-from" side for one-sided relations
-//! - *range*: one side of an association/relation, the "pointed-to" side for one-sided relations
-//! - *local*: the current side of an association
-//! - *remote*: the opposite side of an association
+//! - *domain*: one side of a relation, the "pointed-from" side for one-sided relations
+//! - *range*: one side of a relation, the "pointed-to" side for one-sided relations
+//! - *local*: the current side of a relation
+//! - *remote*: the opposite side of a relation
 
 use query::Queryable;
 
 use crate::{
     db::{Connection, StatementContext, StatementRow, Transaction},
-    query::{self, AssocInterface, Insertable},
+    query::{self, Insertable},
     schema::datum::{Datum, DatumDiscriminator},
     schema::entity::{Entity, EntityVisitor},
 };
@@ -27,6 +27,9 @@ pub mod datum;
 /// database.
 pub mod entity;
 
+/// Types related to inter-entity relationships.
+pub mod relation;
+
 mod build;
 mod collect;
 pub(crate) mod meta;
@@ -115,344 +118,6 @@ impl<T: Entity> PartialEq for Stored<T> {
 // Entity field types
 // ----------------------------------------------------------------------
 
-/// Represents an arbitrary relation between two types of entities.
-///
-/// In its most generic form, this represents a table that stores an arbitrary set of `(Domain::ID,
-/// Range::ID)` pairs used to define the relation. Can be restricted to be injective if this is
-/// desired. Doing so will incur a small runtime cost, since an extra index must be maintained.
-pub trait Relation: 'static {
-    /// The domain of the relation, aka the "pointed-from" type. This is interchangeable with
-    /// `Range` unless `INJECTIVE` is set to true.
-    type Domain: Entity;
-    /// The range of the relation, aka the "pointed-to" type. This is interchangeable with
-    /// `Domain` unless `INJECTIVE` is set to true.
-    type Range: Entity;
-
-    /// If true, then each Range-type entity can only be referred to by a single Domain-type
-    /// entity. This roughly translates to the relation being computationally-efficiently
-    /// invertible for a two-way map.
-    const INJECTIVE: bool = false;
-
-    /// A unique, human-readable name for the relation, which should be short and not include
-    /// spaces.
-    const NAME: &'static str;
-}
-
-/// Enumeration used to represent which side of a relation an [`AssocInterface`] trait implementation is representing.
-#[derive(Debug)]
-pub enum LocalSide {
-    /// The side of the relation that the [`AssocInterface`] represents is the `Domain` side.
-    Domain,
-    /// The side of the relation that the [`AssocInterface`] represents is the `Range` side.
-    Range,
-}
-
-/// Opaque data structure used for constructing `Assoc{Map,Domain,Range}` instances.
-#[derive(Clone)]
-pub struct AssocData {
-    pub(crate) conn: Connection,
-    pub(crate) local_name: &'static str,
-    pub(crate) part_name: &'static str,
-    pub(crate) local_id: i64,
-}
-
-/// Represents a simple one-to-many non-injective entity relationship.
-///
-/// This is a 'shortcut' type that doesn't require defining a tag type that implements `Relation`.
-pub struct AssocMap<T: Entity> {
-    pub(crate) data: Option<AssocData>,
-    _ghost: std::marker::PhantomData<T>,
-}
-
-impl<T: Entity> Clone for AssocMap<T> {
-    fn clone(&self) -> Self {
-        Self {
-            data: self.data.clone(),
-            _ghost: Default::default(),
-        }
-    }
-}
-
-impl<T: Entity> std::fmt::Debug for AssocMap<T> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_fmt(format_args!(
-            "AssocMap {{ id: {:?} }}",
-            self.data.as_ref().map(|d| d.local_id)
-        ))
-    }
-}
-
-impl<T: Entity> Default for AssocMap<T> {
-    fn default() -> Self {
-        Self {
-            data: None,
-            _ghost: Default::default(),
-        }
-    }
-}
-
-impl<T: Entity> AssocInterface for AssocMap<T> {
-    type RemoteEntity = T;
-    const SIDE: LocalSide = LocalSide::Domain;
-
-    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
-        self.data
-            .as_ref()
-            .ok_or(Error::LogicError(
-                "no distinguishing name for empty AssocMap",
-            ))
-            .map(|d| d.part_name)
-    }
-
-    fn get_data(&self) -> DBResult<&AssocData> {
-        self.data
-            .as_ref()
-            .ok_or(Error::LogicError("Reading from unassigned AssocMap"))
-    }
-}
-
-impl<T: Entity> Datum for AssocMap<T> {
-    fn sql_type() -> &'static str {
-        unreachable!()
-    }
-
-    fn debug_field(&self, _field: &'static str, _fmt: &mut std::fmt::DebugStruct)
-    where
-        Self: Sized,
-    {
-    }
-
-    fn accept_entity_visitor(v: &mut impl EntityVisitor) {
-        v.visit::<T>();
-    }
-
-    fn accept_discriminator(d: &mut impl DatumDiscriminator)
-    where
-        Self: Sized,
-    {
-        d.visit_assoc_map::<T>();
-    }
-
-    fn accept_discriminator_ref(&self, d: &mut impl DatumDiscriminatorRef)
-    where
-        Self: Sized,
-    {
-        d.visit_assoc_map::<T>(self);
-    }
-
-    fn bind_to(&self, _stmt: &mut StatementContext, _index: i32) {
-        unreachable!()
-    }
-
-    fn build_from(adata: AssocData, _stmt: &mut StatementRow, _index: &mut i32) -> DBResult<Self>
-    where
-        Self: Sized,
-    {
-        Ok(Self {
-            data: Some(adata),
-            _ghost: Default::default(),
-        })
-    }
-
-    fn update_adata(&mut self, adata: AssocData) {
-        self.data = Some(adata);
-    }
-}
-impl<T: Entity> ConcreteDatum for AssocMap<T> {}
-
-/// The domain part of a full Relation definition.
-pub struct AssocDomain<R: Relation> {
-    pub(crate) data: Option<AssocData>,
-    _ghost: std::marker::PhantomData<R>,
-}
-
-impl<R: Relation> Clone for AssocDomain<R> {
-    fn clone(&self) -> Self {
-        Self {
-            data: self.data.clone(),
-            _ghost: Default::default(),
-        }
-    }
-}
-
-impl<R: Relation> Default for AssocDomain<R> {
-    fn default() -> Self {
-        Self {
-            data: None,
-            _ghost: Default::default(),
-        }
-    }
-}
-
-impl<R: Relation> std::fmt::Debug for AssocDomain<R> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_fmt(format_args!(
-            "AssocDomain {{ id: {:?} }}",
-            self.data.as_ref().map(|d| d.local_id)
-        ))
-    }
-}
-
-impl<R: Relation> AssocInterface for AssocDomain<R> {
-    type RemoteEntity = R::Range;
-    const SIDE: LocalSide = LocalSide::Domain;
-
-    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
-        Ok(R::NAME)
-    }
-
-    fn get_data(&self) -> DBResult<&AssocData> {
-        self.data
-            .as_ref()
-            .ok_or(Error::LogicError("Reading from unassigned AssocDomain"))
-    }
-}
-
-impl<R: Relation> Datum for AssocDomain<R> {
-    fn sql_type() -> &'static str {
-        unreachable!()
-    }
-
-    fn debug_field(&self, _field: &'static str, _fmt: &mut std::fmt::DebugStruct)
-    where
-        Self: Sized,
-    {
-    }
-
-    fn accept_entity_visitor(v: &mut impl EntityVisitor) {
-        v.visit::<R::Range>();
-    }
-
-    fn accept_discriminator(d: &mut impl DatumDiscriminator)
-    where
-        Self: Sized,
-    {
-        d.visit_assoc_domain::<R>();
-    }
-
-    fn accept_discriminator_ref(&self, d: &mut impl DatumDiscriminatorRef)
-    where
-        Self: Sized,
-    {
-        d.visit_assoc_domain(self);
-    }
-
-    fn bind_to(&self, _stmt: &mut StatementContext, _index: i32) {
-        unreachable!()
-    }
-
-    fn build_from(adata: AssocData, _stmt: &mut StatementRow, _index: &mut i32) -> DBResult<Self>
-    where
-        Self: Sized,
-    {
-        Ok(Self {
-            data: Some(adata),
-            _ghost: Default::default(),
-        })
-    }
-
-    fn update_adata(&mut self, adata: AssocData) {
-        self.data = Some(adata);
-    }
-}
-impl<R: Relation> ConcreteDatum for AssocDomain<R> {}
-
-/// The range part of a full Relation definition.
-pub struct AssocRange<R: Relation> {
-    pub(crate) data: Option<AssocData>,
-    _ghost: std::marker::PhantomData<R>,
-}
-
-impl<R: Relation> Clone for AssocRange<R> {
-    fn clone(&self) -> Self {
-        Self {
-            data: self.data.clone(),
-            _ghost: Default::default(),
-        }
-    }
-}
-
-impl<R: Relation> Default for AssocRange<R> {
-    fn default() -> Self {
-        Self {
-            data: None,
-            _ghost: Default::default(),
-        }
-    }
-}
-
-impl<R: Relation> std::fmt::Debug for AssocRange<R> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_fmt(format_args!(
-            "AssocRange {{ id: {:?} }}",
-            self.data.as_ref().map(|d| d.local_id)
-        ))
-    }
-}
-
-impl<R: Relation> AssocInterface for AssocRange<R> {
-    type RemoteEntity = R::Domain;
-    const SIDE: LocalSide = LocalSide::Range;
-
-    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
-        Ok(R::NAME)
-    }
-
-    fn get_data(&self) -> DBResult<&AssocData> {
-        self.data
-            .as_ref()
-            .ok_or(Error::LogicError("Reading from unassigned AssocRange"))
-    }
-}
-
-impl<R: Relation> Datum for AssocRange<R> {
-    fn sql_type() -> &'static str {
-        unreachable!()
-    }
-
-    fn debug_field(&self, _field: &'static str, _fmt: &mut std::fmt::DebugStruct)
-    where
-        Self: Sized,
-    {
-    }
-
-    fn accept_entity_visitor(v: &mut impl EntityVisitor) {
-        v.visit::<R::Domain>();
-    }
-
-    fn accept_discriminator(d: &mut impl DatumDiscriminator)
-    where
-        Self: Sized,
-    {
-        d.visit_assoc_range::<R>();
-    }
-
-    fn accept_discriminator_ref(&self, d: &mut impl DatumDiscriminatorRef)
-    where
-        Self: Sized,
-    {
-        d.visit_assoc_range(self);
-    }
-
-    fn bind_to(&self, _stmt: &mut StatementContext, _index: i32) {
-        unreachable!()
-    }
-
-    fn build_from(adata: AssocData, _stmt: &mut StatementRow, _index: &mut i32) -> DBResult<Self>
-    where
-        Self: Sized,
-    {
-        Ok(Self {
-            data: Some(adata),
-            _ghost: Default::default(),
-        })
-    }
-
-    fn update_adata(&mut self, adata: AssocData) {
-        self.data = Some(adata);
-    }
-}
-impl<R: Relation> ConcreteDatum for AssocRange<R> {}
 
 /// Stores an arbitrary Rust data type as serialized JSON in a string field.
 #[derive(Clone)]
@@ -514,11 +179,11 @@ impl<T: 'static + serde::Serialize + serde::de::DeserializeOwned + std::fmt::Deb
         stmt.transfer(json);
     }
 
-    fn build_from(adata: AssocData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
+    fn build_from(rdata: relation::RelationData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
     where
         Self: Sized,
     {
-        let s = <String as Datum>::build_from(adata, stmt, index)?;
+        let s = <String as Datum>::build_from(rdata, stmt, index)?;
 
         let d = serde_json::from_str::<T>(s.as_str()).map_err(Error::JSON)?;
 

+ 14 - 14
microrm/src/schema/build.rs

@@ -153,58 +153,58 @@ pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
                     fkey: Some(format!("`{}`(`id`)", entity_name)),
                     unique: part.unique,
                 }),
-                PartType::AssocDomain {
-                    table_name: assoc_table_name,
+                PartType::RelationDomain {
+                    table_name: relation_table_name,
                     range_name,
                     injective
                 } => {
-                    let mut assoc_table = TableInfo::new(assoc_table_name.clone());
+                    let mut relation_table = TableInfo::new(relation_table_name.clone());
 
-                    assoc_table.columns.push(ColumnInfo {
+                    relation_table.columns.push(ColumnInfo {
                         name: "domain",
                         ty: "int".into(),
                         fkey: Some(format!("`{}`(`id`)", table_name)),
                         unique: false,
                     });
 
-                    assoc_table.columns.push(ColumnInfo {
+                    relation_table.columns.push(ColumnInfo {
                         name: "range",
                         ty: "int".into(),
                         fkey: Some(format!("`{}`(`id`)", range_name)),
                         unique: *injective,
                     });
 
-                    assoc_table
+                    relation_table
                         .constraints
                         .push("unique(`range`, `domain`)".to_string());
 
-                    tables.insert(assoc_table_name.clone(), assoc_table);
+                    tables.insert(relation_table_name.clone(), relation_table);
                 }
-                PartType::AssocRange {
-                    table_name: assoc_table_name,
+                PartType::RelationRange {
+                    table_name: relation_table_name,
                     domain_name,
                     injective
                 } => {
-                    let mut assoc_table = TableInfo::new(assoc_table_name.clone());
+                    let mut relation_table = TableInfo::new(relation_table_name.clone());
 
-                    assoc_table.columns.push(ColumnInfo {
+                    relation_table.columns.push(ColumnInfo {
                         name: "domain",
                         ty: "int".into(),
                         fkey: Some(format!("`{}`(`id`)", domain_name)),
                         unique: false,
                     });
 
-                    assoc_table.columns.push(ColumnInfo {
+                    relation_table.columns.push(ColumnInfo {
                         name: "range",
                         ty: "int".into(),
                         fkey: Some(format!("`{}`(`id`)", table_name)),
                         unique: *injective,
                     });
 
-                    assoc_table
+                    relation_table
                         .constraints
                         .push("unique(`range`, `domain`)".to_string());
-                    tables.insert(assoc_table_name.clone(), assoc_table);
+                    tables.insert(relation_table_name.clone(), relation_table);
                 }
             }
         }

+ 16 - 14
microrm/src/schema/collect.rs

@@ -1,8 +1,10 @@
 use std::collections::HashMap;
 
-use crate::schema::datum::Datum;
-use crate::schema::entity::{Entity, EntityPart, EntityPartList, EntityPartVisitor, EntityVisitor};
-use crate::schema::{DatumDiscriminator, Relation};
+use crate::schema::{
+    datum::{Datum,DatumDiscriminator},
+    entity::{Entity, EntityPart, EntityPartList, EntityPartVisitor, EntityVisitor},
+    relation::Relation,
+};
 
 #[derive(Debug)]
 pub enum PartType {
@@ -10,12 +12,12 @@ pub enum PartType {
     Datum(&'static str),
     /// stores the entity name
     IDReference(&'static str),
-    AssocDomain {
+    RelationDomain {
         table_name: String,
         range_name: &'static str,
         injective: bool,
     },
-    AssocRange {
+    RelationRange {
         table_name: String,
         domain_name: &'static str,
         injective: bool,
@@ -49,10 +51,10 @@ impl PartState {
                 self.ty = Some(PartType::Datum("text"));
             }
 
-            fn visit_assoc_map<E: Entity>(&mut self) {
-                self.ty = Some(PartType::AssocDomain {
+            fn visit_relation_map<E: Entity>(&mut self) {
+                self.ty = Some(PartType::RelationDomain {
                     table_name: format!(
-                        "{}_{}_assoc_{}",
+                        "{}_{}_relation_{}",
                         EP::Entity::entity_name(),
                         E::entity_name(),
                         EP::part_name()
@@ -62,10 +64,10 @@ impl PartState {
                 });
             }
 
-            fn visit_assoc_domain<R: Relation>(&mut self) {
-                self.ty = Some(PartType::AssocDomain {
+            fn visit_relation_domain<R: Relation>(&mut self) {
+                self.ty = Some(PartType::RelationDomain {
                     table_name: format!(
-                        "{}_{}_assoc_{}",
+                        "{}_{}_relation_{}",
                         R::Domain::entity_name(),
                         R::Range::entity_name(),
                         R::NAME
@@ -75,10 +77,10 @@ impl PartState {
                 });
             }
 
-            fn visit_assoc_range<R: Relation>(&mut self) {
-                self.ty = Some(PartType::AssocRange {
+            fn visit_relation_range<R: Relation>(&mut self) {
+                self.ty = Some(PartType::RelationRange {
                     table_name: format!(
-                        "{}_{}_assoc_{}",
+                        "{}_{}_relation_{}",
                         R::Domain::entity_name(),
                         R::Range::entity_name(),
                         R::NAME

+ 13 - 12
microrm/src/schema/datum.rs

@@ -1,6 +1,7 @@
 use crate::{
     db::{StatementContext, StatementRow},
-    schema::{AssocData, AssocDomain, AssocMap, AssocRange, Entity, EntityVisitor, Relation},
+    schema::entity::{Entity, EntityVisitor},
+    schema::relation::{RelationData, RelationDomain, RelationMap, RelationRange, Relation},
     DBResult,
 };
 
@@ -30,16 +31,16 @@ pub trait Datum: Clone + std::fmt::Debug {
     /// Bind this datum to a [`StatementContext`] at a given index.
     fn bind_to(&self, _stmt: &mut StatementContext, index: i32);
     /// Construct an instance of this datum from a table row.
-    fn build_from(_adata: AssocData, _stmt: &mut StatementRow, _index: &mut i32) -> DBResult<Self>
+    fn build_from(_adata: RelationData, _stmt: &mut StatementRow, _index: &mut i32) -> DBResult<Self>
     where
         Self: Sized,
     {
         unreachable!()
     }
 
-    /// Update any [`AssocData`] instances.
+    /// Update any [`RelationData`] instances.
     #[doc(hidden)]
-    fn update_adata(&mut self, _adata: AssocData) {}
+    fn update_adata(&mut self, _adata: RelationData) {}
 
     /// Accept an entity visitor to iterate across any entities this Datum type references.
     fn accept_entity_visitor(_: &mut impl EntityVisitor) {}
@@ -72,12 +73,12 @@ pub trait DatumDiscriminator {
     fn visit_serialized<T: serde::Serialize + serde::de::DeserializeOwned>(&mut self);
     /// Visit a bare datum field instance, such as a `String` or `usize`.
     fn visit_bare_field<T: Datum>(&mut self);
-    /// Visit a one-direction association map.
-    fn visit_assoc_map<E: Entity>(&mut self);
+    /// Visit a one-direction relation map.
+    fn visit_relation_map<E: Entity>(&mut self);
     /// Visit the domain side of a bidirectional relation map.
-    fn visit_assoc_domain<R: Relation>(&mut self);
+    fn visit_relation_domain<R: Relation>(&mut self);
     /// Visit the range side of a bidirectional relation map.
-    fn visit_assoc_range<R: Relation>(&mut self);
+    fn visit_relation_range<R: Relation>(&mut self);
 }
 
 /// Visitor for allowing for type-specific behaviour, with instances passed as references.
@@ -88,12 +89,12 @@ pub trait DatumDiscriminatorRef {
     fn visit_serialized<T: serde::Serialize + serde::de::DeserializeOwned>(&mut self, _: &T);
     /// Visit a bare datum field instance, such as a `String` or `usize`.
     fn visit_bare_field<T: Datum>(&mut self, _: &T);
-    /// Visit a one-direction association map instance.
-    fn visit_assoc_map<E: Entity>(&mut self, _: &AssocMap<E>);
+    /// Visit a one-direction relation map instance.
+    fn visit_relation_map<E: Entity>(&mut self, _: &RelationMap<E>);
     /// Visit an instance of the domain side of a bidirectional relation map.
-    fn visit_assoc_domain<R: Relation>(&mut self, _: &AssocDomain<R>);
+    fn visit_relation_domain<R: Relation>(&mut self, _: &RelationDomain<R>);
     /// Visit an instance of the range side of a bidirectional relation map.
-    fn visit_assoc_range<R: Relation>(&mut self, _: &AssocRange<R>);
+    fn visit_relation_range<R: Relation>(&mut self, _: &RelationRange<R>);
 }
 
 /// A fixed-length list of EntityDatums, usually a tuple.

+ 16 - 16
microrm/src/schema/datum/datum_common.rs

@@ -1,6 +1,6 @@
 use crate::{
     db::{self, Bindable, StatementContext, StatementRow},
-    schema::{AssocData, Datum},
+    schema::{relation::RelationData, datum::Datum},
     DBResult, Error,
 };
 
@@ -32,11 +32,11 @@ impl Datum for time::OffsetDateTime {
         ts.bind_to(stmt, index)
     }
 
-    fn build_from(adata: AssocData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
+    fn build_from(rdata: RelationData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
     where
         Self: Sized,
     {
-        let unix = i64::build_from(adata, stmt, index)?;
+        let unix = i64::build_from(rdata, stmt, index)?;
         Self::from_unix_timestamp(unix).map_err(|e| Error::UnknownValue(e.to_string()))
     }
 }
@@ -52,7 +52,7 @@ impl Datum for String {
             .expect("couldn't bind string");
     }
 
-    fn build_from(_: AssocData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
+    fn build_from(_: RelationData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
     where
         Self: Sized,
     {
@@ -87,11 +87,11 @@ impl Datum for usize {
             .expect("couldn't bind usize as i64");
     }
 
-    fn build_from(adata: AssocData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
+    fn build_from(rdata: RelationData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
     where
         Self: Sized,
     {
-        Ok(i64::build_from(adata, stmt, index)? as usize)
+        Ok(i64::build_from(rdata, stmt, index)? as usize)
     }
 }
 
@@ -106,11 +106,11 @@ impl Datum for isize {
             .expect("couldn't bind isize as i64");
     }
 
-    fn build_from(adata: AssocData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
+    fn build_from(rdata: RelationData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
     where
         Self: Sized,
     {
-        Ok(i64::build_from(adata, stmt, index)? as isize)
+        Ok(i64::build_from(rdata, stmt, index)? as isize)
     }
 }
 
@@ -124,11 +124,11 @@ impl Datum for u64 {
         todo!()
     }
 
-    fn build_from(adata: AssocData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
+    fn build_from(rdata: RelationData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
     where
         Self: Sized,
     {
-        Ok(i64::build_from(adata, stmt, index)? as u64)
+        Ok(i64::build_from(rdata, stmt, index)? as u64)
     }
 }
 
@@ -142,7 +142,7 @@ impl Datum for i64 {
         stmt.bind(index, *self).expect("couldn't bind i64")
     }
 
-    fn build_from(_: AssocData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
+    fn build_from(_: RelationData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
     where
         Self: Sized,
     {
@@ -167,7 +167,7 @@ impl<T: Datum> Datum for Option<T> {
         }
     }
 
-    fn build_from(adata: AssocData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
+    fn build_from(rdata: RelationData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
     where
         Self: Sized,
     {
@@ -175,7 +175,7 @@ impl<T: Datum> Datum for Option<T> {
             *index += 1;
             Ok(None)
         } else {
-            T::build_from(adata, stmt, index).map(Some)
+            T::build_from(rdata, stmt, index).map(Some)
         }
     }
 }
@@ -191,11 +191,11 @@ impl Datum for bool {
             .expect("couldn't bind bool");
     }
 
-    fn build_from(adata: AssocData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
+    fn build_from(rdata: RelationData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
     where
         Self: Sized,
     {
-        Ok(i64::build_from(adata, stmt, index)? != 0)
+        Ok(i64::build_from(rdata, stmt, index)? != 0)
     }
 }
 
@@ -210,7 +210,7 @@ impl Datum for Vec<u8> {
             .expect("couldn't bind Vec<u8>");
     }
 
-    fn build_from(_: AssocData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
+    fn build_from(_: RelationData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
     where
         Self: Sized,
     {

+ 1 - 1
microrm/src/schema/entity/helpers.rs

@@ -2,7 +2,7 @@ use crate::schema::entity::{Datum, Entity, EntityVisitor};
 
 use super::EntityPart;
 
-pub fn check_assoc<EP: EntityPart>() -> bool {
+pub fn check_relation<EP: EntityPart>() -> bool {
     struct Checker(bool);
     impl EntityVisitor for Checker {
         fn visit<E: Entity>(&mut self) {

+ 2 - 2
microrm/src/schema/entity/part_list.rs

@@ -1,6 +1,6 @@
 use crate::{
     db::{Connection, StatementRow},
-    schema::AssocData,
+    schema::relation::RelationData,
     DBResult,
 };
 
@@ -9,7 +9,7 @@ use super::{Datum, Entity, EntityPart, EntityPartList, EntityPartVisitor};
 macro_rules! build_datum {
     ($conn:ident,$local_id:ident,$stmt:ident,$idx:ident,$d:ident,$t:ident) => {
         let $d = <$t::Datum as Datum>::build_from(
-            AssocData {
+            RelationData {
                 conn: $conn.clone(),
                 local_name: <$t::Entity as Entity>::entity_name(),
                 part_name: $t::part_name(),

+ 349 - 0
microrm/src/schema/relation.rs

@@ -0,0 +1,349 @@
+use crate::{
+    Error, DBResult,
+    db::{Connection, StatementRow, StatementContext},
+    schema::{
+        datum::{ConcreteDatum, Datum, DatumDiscriminator, DatumDiscriminatorRef},
+        entity::{Entity, EntityVisitor},
+    },
+    query::RelationInterface,
+};
+
+
+/// Represents an arbitrary relation between two types of entities.
+///
+/// In its most generic form, this represents a table that stores an arbitrary set of `(Domain::ID,
+/// Range::ID)` pairs used to define the relation. Can be restricted to be injective if this is
+/// desired. Doing so will incur a small runtime cost, since an extra index must be maintained.
+pub trait Relation: 'static {
+    /// The domain of the relation, aka the "pointed-from" type. This is interchangeable with
+    /// `Range` unless `INJECTIVE` is set to true.
+    type Domain: Entity;
+    /// The range of the relation, aka the "pointed-to" type. This is interchangeable with
+    /// `Domain` unless `INJECTIVE` is set to true.
+    type Range: Entity;
+
+    /// If true, then each Range-type entity can only be referred to by a single Domain-type
+    /// entity. This roughly translates to the relation being computationally-efficiently
+    /// invertible for a two-way map.
+    const INJECTIVE: bool = false;
+
+    /// A unique, human-readable name for the relation, which should be short and not include
+    /// spaces.
+    const NAME: &'static str;
+}
+
+/// Enumeration used to represent which side of a relation an [`RelationInterface`] trait implementation is representing.
+#[derive(Debug)]
+pub enum LocalSide {
+    /// The side of the relation that the [`RelationInterface`] represents is the `Domain` side.
+    Domain,
+    /// The side of the relation that the [`RelationInterface`] represents is the `Range` side.
+    Range,
+}
+
+/// Opaque data structure used for constructing `Relation{Map,Domain,Range}` instances.
+#[derive(Clone)]
+pub struct RelationData {
+    pub(crate) conn: Connection,
+    pub(crate) local_name: &'static str,
+    pub(crate) part_name: &'static str,
+    pub(crate) local_id: i64,
+}
+
+/// Represents a simple one-to-many non-injective entity relationship.
+///
+/// This is a 'shortcut' type that doesn't require defining a tag type that implements `Relation`.
+pub struct RelationMap<T: Entity> {
+    pub(crate) data: Option<RelationData>,
+    _ghost: std::marker::PhantomData<T>,
+}
+
+impl<T: Entity> Clone for RelationMap<T> {
+    fn clone(&self) -> Self {
+        Self {
+            data: self.data.clone(),
+            _ghost: Default::default(),
+        }
+    }
+}
+
+impl<T: Entity> std::fmt::Debug for RelationMap<T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_fmt(format_args!(
+            "RelationMap {{ id: {:?} }}",
+            self.data.as_ref().map(|d| d.local_id)
+        ))
+    }
+}
+
+impl<T: Entity> Default for RelationMap<T> {
+    fn default() -> Self {
+        Self {
+            data: None,
+            _ghost: Default::default(),
+        }
+    }
+}
+
+impl<T: Entity> RelationInterface for RelationMap<T> {
+    type RemoteEntity = T;
+    const SIDE: LocalSide = LocalSide::Domain;
+
+    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
+        self.data
+            .as_ref()
+            .ok_or(Error::LogicError(
+                "no distinguishing name for empty RelationMap",
+            ))
+            .map(|d| d.part_name)
+    }
+
+    fn get_data(&self) -> DBResult<&RelationData> {
+        self.data
+            .as_ref()
+            .ok_or(Error::LogicError("Reading from unassigned RelationMap"))
+    }
+}
+
+impl<T: Entity> Datum for RelationMap<T> {
+    fn sql_type() -> &'static str {
+        unreachable!()
+    }
+
+    fn debug_field(&self, _field: &'static str, _fmt: &mut std::fmt::DebugStruct)
+    where
+        Self: Sized,
+    {
+    }
+
+    fn accept_entity_visitor(v: &mut impl EntityVisitor) {
+        v.visit::<T>();
+    }
+
+    fn accept_discriminator(d: &mut impl DatumDiscriminator)
+    where
+        Self: Sized,
+    {
+        d.visit_relation_map::<T>();
+    }
+
+    fn accept_discriminator_ref(&self, d: &mut impl DatumDiscriminatorRef)
+    where
+        Self: Sized,
+    {
+        d.visit_relation_map::<T>(self);
+    }
+
+    fn bind_to(&self, _stmt: &mut StatementContext, _index: i32) {
+        unreachable!()
+    }
+
+    fn build_from(rdata: RelationData, _stmt: &mut StatementRow, _index: &mut i32) -> DBResult<Self>
+    where
+        Self: Sized,
+    {
+        Ok(Self {
+            data: Some(rdata),
+            _ghost: Default::default(),
+        })
+    }
+
+    fn update_adata(&mut self, rdata: RelationData) {
+        self.data = Some(rdata);
+    }
+}
+impl<T: Entity> ConcreteDatum for RelationMap<T> {}
+
+/// The domain part of a full Relation definition.
+pub struct RelationDomain<R: Relation> {
+    pub(crate) data: Option<RelationData>,
+    _ghost: std::marker::PhantomData<R>,
+}
+
+impl<R: Relation> Clone for RelationDomain<R> {
+    fn clone(&self) -> Self {
+        Self {
+            data: self.data.clone(),
+            _ghost: Default::default(),
+        }
+    }
+}
+
+impl<R: Relation> Default for RelationDomain<R> {
+    fn default() -> Self {
+        Self {
+            data: None,
+            _ghost: Default::default(),
+        }
+    }
+}
+
+impl<R: Relation> std::fmt::Debug for RelationDomain<R> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_fmt(format_args!(
+            "RelationDomain {{ id: {:?} }}",
+            self.data.as_ref().map(|d| d.local_id)
+        ))
+    }
+}
+
+impl<R: Relation> RelationInterface for RelationDomain<R> {
+    type RemoteEntity = R::Range;
+    const SIDE: LocalSide = LocalSide::Domain;
+
+    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
+        Ok(R::NAME)
+    }
+
+    fn get_data(&self) -> DBResult<&RelationData> {
+        self.data
+            .as_ref()
+            .ok_or(Error::LogicError("Reading from unassigned RelationDomain"))
+    }
+}
+
+impl<R: Relation> Datum for RelationDomain<R> {
+    fn sql_type() -> &'static str {
+        unreachable!()
+    }
+
+    fn debug_field(&self, _field: &'static str, _fmt: &mut std::fmt::DebugStruct)
+    where
+        Self: Sized,
+    {
+    }
+
+    fn accept_entity_visitor(v: &mut impl EntityVisitor) {
+        v.visit::<R::Range>();
+    }
+
+    fn accept_discriminator(d: &mut impl DatumDiscriminator)
+    where
+        Self: Sized,
+    {
+        d.visit_relation_domain::<R>();
+    }
+
+    fn accept_discriminator_ref(&self, d: &mut impl DatumDiscriminatorRef)
+    where
+        Self: Sized,
+    {
+        d.visit_relation_domain(self);
+    }
+
+    fn bind_to(&self, _stmt: &mut StatementContext, _index: i32) {
+        unreachable!()
+    }
+
+    fn build_from(rdata: RelationData, _stmt: &mut StatementRow, _index: &mut i32) -> DBResult<Self>
+    where
+        Self: Sized,
+    {
+        Ok(Self {
+            data: Some(rdata),
+            _ghost: Default::default(),
+        })
+    }
+
+    fn update_adata(&mut self, rdata: RelationData) {
+        self.data = Some(rdata);
+    }
+}
+impl<R: Relation> ConcreteDatum for RelationDomain<R> {}
+
+/// The range part of a full Relation definition.
+pub struct RelationRange<R: Relation> {
+    pub(crate) data: Option<RelationData>,
+    _ghost: std::marker::PhantomData<R>,
+}
+
+impl<R: Relation> Clone for RelationRange<R> {
+    fn clone(&self) -> Self {
+        Self {
+            data: self.data.clone(),
+            _ghost: Default::default(),
+        }
+    }
+}
+
+impl<R: Relation> Default for RelationRange<R> {
+    fn default() -> Self {
+        Self {
+            data: None,
+            _ghost: Default::default(),
+        }
+    }
+}
+
+impl<R: Relation> std::fmt::Debug for RelationRange<R> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_fmt(format_args!(
+            "RelationRange {{ id: {:?} }}",
+            self.data.as_ref().map(|d| d.local_id)
+        ))
+    }
+}
+
+impl<R: Relation> RelationInterface for RelationRange<R> {
+    type RemoteEntity = R::Domain;
+    const SIDE: LocalSide = LocalSide::Range;
+
+    fn get_distinguishing_name(&self) -> DBResult<&'static str> {
+        Ok(R::NAME)
+    }
+
+    fn get_data(&self) -> DBResult<&RelationData> {
+        self.data
+            .as_ref()
+            .ok_or(Error::LogicError("Reading from unassigned RelationRange"))
+    }
+}
+
+impl<R: Relation> Datum for RelationRange<R> {
+    fn sql_type() -> &'static str {
+        unreachable!()
+    }
+
+    fn debug_field(&self, _field: &'static str, _fmt: &mut std::fmt::DebugStruct)
+    where
+        Self: Sized,
+    {
+    }
+
+    fn accept_entity_visitor(v: &mut impl EntityVisitor) {
+        v.visit::<R::Domain>();
+    }
+
+    fn accept_discriminator(d: &mut impl DatumDiscriminator)
+    where
+        Self: Sized,
+    {
+        d.visit_relation_range::<R>();
+    }
+
+    fn accept_discriminator_ref(&self, d: &mut impl DatumDiscriminatorRef)
+    where
+        Self: Sized,
+    {
+        d.visit_relation_range(self);
+    }
+
+    fn bind_to(&self, _stmt: &mut StatementContext, _index: i32) {
+        unreachable!()
+    }
+
+    fn build_from(rdata: RelationData, _stmt: &mut StatementRow, _index: &mut i32) -> DBResult<Self>
+    where
+        Self: Sized,
+    {
+        Ok(Self {
+            data: Some(rdata),
+            _ghost: Default::default(),
+        })
+    }
+
+    fn update_adata(&mut self, rdata: RelationData) {
+        self.data = Some(rdata);
+    }
+}
+impl<R: Relation> ConcreteDatum for RelationRange<R> {}

+ 29 - 31
microrm/src/schema/tests.rs

@@ -40,7 +40,7 @@ mod manual_test_db {
             todo!()
         }
         fn build_from<'a>(
-            adata: crate::schema::AssocData,
+            rdata: crate::schema::relation::RelationData,
             stmt: &mut StatementRow,
             index: &mut i32,
         ) -> crate::DBResult<Self>
@@ -183,7 +183,7 @@ mod derive_tests {
     #![allow(unused)]
 
     use crate::prelude::*;
-    use crate::schema::{AssocMap, Database, IDMap};
+    use crate::schema::{Database, IDMap};
     use microrm_macros::{Database, Entity};
     use test_log::test;
 
@@ -197,7 +197,7 @@ mod derive_tests {
     struct Person {
         #[key]
         name: String,
-        roles: AssocMap<Role>,
+        roles: microrm::RelationMap<Role>,
     }
 
     #[derive(Database)]
@@ -227,7 +227,7 @@ mod derive_tests {
         db.people
             .insert(Person {
                 name: name_string.clone(),
-                roles: AssocMap::default(),
+                roles: Default::default(),
             })
             .expect("failed to insert");
 
@@ -236,14 +236,14 @@ mod derive_tests {
     }
 
     #[test]
-    fn check_assoc_query_construction() {
+    fn check_relation_query_construction() {
         let db = PeopleDB::open_path(":memory:").expect("couldn't open database");
 
         let name_string = "name_here".to_string();
         db.people
             .insert(Person {
                 name: name_string.clone(),
-                roles: AssocMap::default(),
+                roles: Default::default(),
             })
             .expect("couldn't insert test person");
 
@@ -258,11 +258,11 @@ mod derive_tests {
         person
             .roles
             .get()
-            .expect("couldn't get associated role entity");
+            .expect("couldn't get related role entity");
     }
 
     #[test]
-    fn check_assoc_insertion() {
+    fn check_relation_insertion() {
         std::fs::remove_file("/tmp/insert.db").ok();
         let db = PeopleDB::open_path("/tmp/insert.db").expect("couldn't open database");
         // let db = PeopleDB::open_path(":memory:").expect("couldn't open database");
@@ -271,7 +271,7 @@ mod derive_tests {
         db.people
             .insert(Person {
                 name: name_string.clone(),
-                roles: AssocMap::default(),
+                roles: Default::default(),
             })
             .expect("couldn't insert test person");
 
@@ -343,12 +343,11 @@ mod derive_tests {
 mod mutual_relationship {
     use super::open_test_db;
     use crate::prelude::*;
-    use crate::schema::{AssocDomain, AssocMap, AssocRange, Database, IDMap};
     use microrm_macros::{Database, Entity};
     use test_log::test;
 
     struct CR;
-    impl microrm::schema::Relation for CR {
+    impl Relation for CR {
         type Domain = Customer;
         type Range = Receipt;
 
@@ -358,19 +357,19 @@ mod mutual_relationship {
     #[derive(Entity)]
     struct Customer {
         name: String,
-        receipts: AssocDomain<CR>,
+        receipts: microrm::RelationDomain<CR>,
     }
 
     #[derive(Entity)]
     struct Receipt {
         value: usize,
-        customers: AssocRange<CR>,
+        customers: microrm::RelationRange<CR>,
     }
 
     #[derive(Database)]
     struct ReceiptDB {
-        customers: IDMap<Customer>,
-        receipts: IDMap<Receipt>,
+        customers: microrm::IDMap<Customer>,
+        receipts: microrm::IDMap<Receipt>,
     }
 
     #[test]
@@ -418,14 +417,14 @@ mod mutual_relationship {
         );
         e_ca.receipts
             .connect_to(ra)
-            .expect("couldn't associate customer with receipt a");
+            .expect("couldn't relationiate customer with receipt a");
         println!(
             "connecting customer a (ID {:?}) to receipt b (ID {:?})",
             ca, rb
         );
         e_ca.receipts
             .connect_to(rb)
-            .expect("couldn't associate customer with receipt b");
+            .expect("couldn't relationiate customer with receipt b");
 
         println!("connected!");
 
@@ -434,7 +433,7 @@ mod mutual_relationship {
         assert_eq!(
             e_ca.receipts
                 .get()
-                .expect("couldn't get receipts associated with customer")
+                .expect("couldn't get receipts related with customer")
                 .into_iter()
                 .map(|x| x.id())
                 .collect::<Vec<_>>(),
@@ -451,7 +450,7 @@ mod mutual_relationship {
         assert_eq!(
             e_ra.customers
                 .get()
-                .expect("couldn't get associated customers")
+                .expect("couldn't get related customers")
                 .into_iter()
                 .map(|x| x.id())
                 .collect::<Vec<_>>(),
@@ -464,7 +463,6 @@ mod reserved_words {
     use crate::db::Connection;
     use crate::prelude::*;
     use crate::schema::entity::Entity;
-    use crate::schema::{AssocDomain, AssocRange, Database, IDMap};
     use test_log::test;
 
     #[derive(Entity)]
@@ -474,12 +472,12 @@ mod reserved_words {
 
     #[derive(Entity)]
     struct Group {
-        by: AssocMap<Select>,
+        by: microrm::RelationMap<Select>,
     }
 
     #[derive(Database)]
     struct ReservedWordDB {
-        group: IDMap<Group>,
+        group: microrm::IDMap<Group>,
     }
 
     #[test]
@@ -497,13 +495,13 @@ mod join_test {
     #[derive(Default, Entity)]
     struct Base {
         name: String,
-        targets: AssocMap<Target>,
+        targets: microrm::RelationMap<Target>,
     }
 
     #[derive(Default, Entity)]
     struct Target {
         name: String,
-        indirect_targets: AssocMap<IndirectTarget>,
+        indirect_targets: microrm::RelationMap<IndirectTarget>,
     }
 
     #[derive(Default, Entity)]
@@ -513,9 +511,9 @@ mod join_test {
 
     #[derive(Database)]
     struct JoinDB {
-        bases: IDMap<Base>,
-        targets: IDMap<Target>,
-        indirect: IDMap<IndirectTarget>,
+        bases: microrm::IDMap<Base>,
+        targets: microrm::IDMap<Target>,
+        indirect: microrm::IDMap<IndirectTarget>,
     }
 
     #[test]
@@ -671,8 +669,8 @@ mod query_equivalence {
 
     #[derive(Database)]
     struct ItemDB {
-        single_items: IDMap<SingleItem>,
-        double_items: IDMap<DoubleItem>,
+        single_items: microrm::IDMap<SingleItem>,
+        double_items: microrm::IDMap<DoubleItem>,
     }
 
     #[test]
@@ -822,7 +820,7 @@ mod injective_test {
         #[key]
         name: String,
 
-        works: microrm::AssocDomain<AuthorBookRelation>,
+        works: microrm::RelationDomain<AuthorBookRelation>,
     }
 
     #[derive(Entity)]
@@ -830,7 +828,7 @@ mod injective_test {
         #[key]
         title: String,
 
-        creator: microrm::AssocRange<AuthorBookRelation>,
+        creator: microrm::RelationRange<AuthorBookRelation>,
     }
 
     #[derive(Database)]