Sfoglia il codice sorgente

Improve documentation and tighten trait impl requirements slightly.

Kestrel 7 mesi fa
parent
commit
25603a6dfe

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

@@ -274,19 +274,19 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
             }
 
             fn entity_name() -> &'static str { #entity_name }
-            fn accept_part_visitor(v: &mut impl ::microrm::schema::entity::EntityPartVisitor) {
+            fn accept_part_visitor(v: &mut impl ::microrm::schema::entity::EntityPartVisitor<Entity = Self>) {
                 #(
                     #part_visit
                 );*
             }
 
-            fn accept_part_visitor_ref(&self, v: &mut impl ::microrm::schema::entity::EntityPartVisitor) {
+            fn accept_part_visitor_ref(&self, v: &mut impl ::microrm::schema::entity::EntityPartVisitor<Entity = Self>) {
                 #(
                     #part_ref_visit
                 );*
             }
 
-            fn accept_part_visitor_mut(&mut self, v: &mut impl ::microrm::schema::entity::EntityPartVisitor) {
+            fn accept_part_visitor_mut(&mut self, v: &mut impl ::microrm::schema::entity::EntityPartVisitor<Entity = Self>) {
                 #(
                     #part_mut_visit
                 );*

+ 60 - 0
microrm-macros/src/lib.rs

@@ -1,13 +1,73 @@
+//! This crate contains procedural macros to simplify the use of the `microrm` crate.
+
 use proc_macro::TokenStream;
 
 mod database;
 mod entity;
 
+/// `Entity` trait derivation procedural macro.
+///
+/// This macro performs most of the heavy lifting, type-system wise, of the entire microrm library:
+/// - Derives `Entity` for the given structure,
+/// - Defines and derives `EntityPart` for each field in the source structure,
+/// - Defines an `EntityID` type,
+/// - Derives a `std::fmt::Debug` implementation for the structure that handles some of the
+/// `microrm::schema` types nicely.
+///
+/// The prerequisites for this macro are:
+/// - Must be applied on a struct with named fields,
+/// - All fields of the given struct must derive the `Datum` trait.
+///
+/// Three attributes, applied to fields, modify the behaviour of the generation as follows:
+/// - The `unique` attribute causes the resulting sqlite table to attach a unique constraint for
+/// the given column. Note that this is a restriction that is applied per-column, not over all
+/// columns tagged with `#[unique]`.
+/// - The `elide` attribute removes the datum field from several end-user-visible iterations, such
+/// as the `Debug` impl. Useful for hiding fields that hold secret information that shouldn't be
+/// visible in log files.
+/// - The `key` attribute adds a field to the search key defined for this entity, for smoother
+/// access. This attribute can be applied to multiple fields, and the key-uniqueness constraint is
+/// applied across the tuple of all columns with the attribute. Note that this is not _quite_ the
+/// primary key for the table, as internally the integral ID is used as the primary key; it can be
+/// thought of as the 'secondary key' for the table.
+///
+/// For example:
+///
+/// ```ignore
+/// #[derive(Entity)]
+/// struct ExampleEntity {
+///     /// UUID for this entity
+///     #[unique]
+///     id: String,
+///
+///     /// Stored email address, part of lookup key
+///     #[key]
+///     email: String,
+///
+///     /// Stored name, part of lookup key
+///     #[key]
+///     name: String,
+///
+///     /// Sensitive data that should never be in log files
+///     #[elide]
+///     sin: String,
+/// }
+/// ```
 #[proc_macro_derive(Entity, attributes(unique, elide, key))]
 pub fn derive_entity(tokens: TokenStream) -> TokenStream {
     entity::derive(tokens)
 }
 
+/// `Database` trait derivation procedural macro.
+///
+/// This macro sets up some helper types required for implementing the `Database` trait, in
+/// addition to actually implementing the `Database` trait itself.
+///
+/// The prerequisites for this macro are:
+/// - Must be applied on a struct with named fields,
+/// - All fields of the given struct must derive the `DatabaseItem` trait.
+///
+/// Refer to the `microrm` examples for example usage.
 #[proc_macro_derive(Database)]
 pub fn derive_database(tokens: TokenStream) -> TokenStream {
     database::derive(tokens)

+ 33 - 24
microrm/src/cli/clap_interface.rs

@@ -14,12 +14,13 @@ fn add_keys<E: Entity, IC: InterfaceCustomization>(
     mut cmd: clap::Command,
     role: ValueRole,
 ) -> clap::Command {
-    struct UVisitor<'a, IC: InterfaceCustomization>(
+    struct UVisitor<'a, IC: InterfaceCustomization, E: Entity>(
         &'a mut clap::Command,
         ValueRole,
-        std::marker::PhantomData<IC>,
+        std::marker::PhantomData<(IC, E)>,
     );
-    impl<'a, IC: InterfaceCustomization> EntityPartVisitor for UVisitor<'a, IC> {
+    impl<'a, IC: InterfaceCustomization, E: Entity> EntityPartVisitor for UVisitor<'a, IC, E> {
+        type Entity = E;
         fn visit<EP: microrm::schema::entity::EntityPart>(&mut self) {
             if !IC::has_value_for(EP::Entity::entity_name(), EP::part_name(), self.1) {
                 let arg = clap::Arg::new(EP::part_name())
@@ -30,7 +31,7 @@ fn add_keys<E: Entity, IC: InterfaceCustomization>(
         }
     }
 
-    <E::Keys as EntityPartList>::accept_part_visitor(&mut UVisitor::<IC>(
+    <E::Keys as EntityPartList>::accept_part_visitor(&mut UVisitor::<IC, E>(
         &mut cmd,
         role,
         Default::default(),
@@ -43,13 +44,14 @@ fn collect_keys<E: Entity, IC: InterfaceCustomization>(
     matches: &clap::ArgMatches,
     role: ValueRole,
 ) -> Vec<EntityKey> {
-    struct UVisitor<'a, IC: InterfaceCustomization>(
+    struct UVisitor<'a, IC: InterfaceCustomization, E: Entity>(
         &'a clap::ArgMatches,
         &'a mut Vec<EntityKey>,
         ValueRole,
-        std::marker::PhantomData<IC>,
+        std::marker::PhantomData<(IC, E)>,
     );
-    impl<'a, IC: InterfaceCustomization> EntityPartVisitor for UVisitor<'a, IC> {
+    impl<'a, IC: InterfaceCustomization, E: Entity> EntityPartVisitor for UVisitor<'a, IC, E> {
+        type Entity = E;
         fn visit<EP: microrm::schema::entity::EntityPart>(&mut self) {
             if !IC::has_value_for(EP::Entity::entity_name(), EP::part_name(), self.2) {
                 self.1.push(EntityKey::UserInput(
@@ -69,7 +71,7 @@ fn collect_keys<E: Entity, IC: InterfaceCustomization>(
     }
 
     let mut key_values = vec![];
-    <E::Keys as EntityPartList>::accept_part_visitor(&mut UVisitor::<IC>(
+    <E::Keys as EntityPartList>::accept_part_visitor(&mut UVisitor::<IC, E>(
         matches,
         &mut key_values,
         role,
@@ -128,13 +130,14 @@ impl<O: CLIObject> InterfaceVerb<O> {
             .ok_or(clap::Error::new(clap::error::ErrorKind::MissingSubcommand))?;
 
         // find the relevant relation
-        struct RelationFinder<'l, IC: InterfaceCustomization> {
+        struct RelationFinder<'l, IC: InterfaceCustomization, E: Entity> {
             subcommand: &'l str,
             submatches: &'l clap::ArgMatches,
             keys: &'l mut Vec<EntityKey>,
-            _ghost: std::marker::PhantomData<IC>,
+            _ghost: std::marker::PhantomData<(IC, E)>,
         }
-        impl<'l, IC: InterfaceCustomization> EntityPartVisitor for RelationFinder<'l, IC> {
+        impl<'l, IC: InterfaceCustomization, E: Entity> EntityPartVisitor for RelationFinder<'l, IC, E> {
+            type Entity = E;
             fn visit<EP: microrm::schema::entity::EntityPart>(&mut self) {
                 if EP::part_name() != self.subcommand {
                     return;
@@ -146,7 +149,7 @@ impl<O: CLIObject> InterfaceVerb<O> {
             }
         }
 
-        impl<'l, IC: InterfaceCustomization> EntityVisitor for RelationFinder<'l, IC> {
+        impl<'l, IC: InterfaceCustomization, OE: Entity> EntityVisitor for RelationFinder<'l, IC, OE> {
             fn visit<E: Entity>(&mut self) {
                 println!("\trelationfinder visiting entity {}", E::entity_name());
                 *self.keys = collect_keys::<E, IC>(self.submatches, ValueRole::AttachmentTarget);
@@ -154,7 +157,7 @@ impl<O: CLIObject> InterfaceVerb<O> {
         }
 
         let mut remote_keys = vec![];
-        O::accept_part_visitor(&mut RelationFinder::<IC> {
+        O::accept_part_visitor(&mut RelationFinder::<IC, O> {
             subcommand,
             submatches,
             keys: &mut remote_keys,
@@ -210,14 +213,15 @@ impl<O: CLIObject> InterfaceVerb<O> {
 }
 
 /// helper type for attach and detach verbs
-struct Attacher<'l, Error: CLIError> {
+struct Attacher<'l, Error: CLIError, E: Entity> {
     do_attach: bool,
     relation: &'l str,
     remote_keys: Vec<String>,
     err: Option<Error>,
+    _ghost: std::marker::PhantomData<E>,
 }
 
-impl<'l, Error: CLIError> Attacher<'l, Error> {
+impl<'l, Error: CLIError, OE: Entity> Attacher<'l, Error, OE> {
     fn do_operation<E: Entity>(&mut self, map: &impl AssocInterface<RemoteEntity = E>) {
         match map
             .query_all()
@@ -252,7 +256,8 @@ impl<'l, Error: CLIError> Attacher<'l, Error> {
     }
 }
 
-impl<'l, Error: CLIError> EntityPartVisitor for Attacher<'l, Error> {
+impl<'l, Error: CLIError, E: Entity> EntityPartVisitor for Attacher<'l, Error, E> {
+    type Entity = E;
     fn visit_datum<EP: microrm::schema::entity::EntityPart>(&mut self, datum: &EP::Datum) {
         if EP::part_name() != self.relation {
             return;
@@ -262,7 +267,7 @@ impl<'l, Error: CLIError> EntityPartVisitor for Attacher<'l, Error> {
     }
 }
 
-impl<'l, Error: CLIError> DatumDiscriminatorRef for Attacher<'l, Error> {
+impl<'l, Error: CLIError, OE: Entity> DatumDiscriminatorRef for Attacher<'l, Error, OE> {
     fn visit_entity_id<E: Entity>(&mut self, _: &E::ID) {
         unreachable!()
     }
@@ -359,6 +364,7 @@ impl<O: CLIObject, IC: InterfaceCustomization> ClapInterface<O, IC> {
                     relation,
                     remote_keys,
                     err: None,
+                    _ghost: Default::default(),
                 };
                 outer_obj.accept_part_visitor_ref(&mut attacher);
 
@@ -405,6 +411,7 @@ impl<O: CLIObject, IC: InterfaceCustomization> ClapInterface<O, IC> {
                     relation,
                     remote_keys,
                     err: None,
+                    _ghost: Default::default(),
                 };
                 outer_obj.accept_part_visitor_ref(&mut attacher);
 
@@ -446,8 +453,9 @@ impl<O: CLIObject, IC: InterfaceCustomization> ClapInterface<O, IC> {
                     }
                 }
 
-                struct AssocFieldWalker;
-                impl EntityPartVisitor for AssocFieldWalker {
+                struct AssocFieldWalker<E: Entity>(std::marker::PhantomData<E>);
+                impl<E: Entity> EntityPartVisitor for AssocFieldWalker<E> {
+                    type Entity = E;
                     fn visit_datum<EP: microrm::schema::entity::EntityPart>(
                         &mut self,
                         datum: &EP::Datum,
@@ -485,7 +493,7 @@ impl<O: CLIObject, IC: InterfaceCustomization> ClapInterface<O, IC> {
                     }
                 }
 
-                obj.accept_part_visitor_ref(&mut AssocFieldWalker);
+                obj.accept_part_visitor_ref(&mut AssocFieldWalker(Default::default()));
             }
             InterfaceVerb::Extra(extra) => {
                 O::run_extra_command(data, extra, query_ctx, insert_ctx)?;
@@ -497,11 +505,12 @@ impl<O: CLIObject, IC: InterfaceCustomization> ClapInterface<O, IC> {
     fn make_relation_subcommands() -> impl Iterator<Item = clap::Command> {
         let mut out = vec![];
 
-        struct PartVisitor<'l, IC: InterfaceCustomization>(
+        struct PartVisitor<'l, IC: InterfaceCustomization, E: Entity>(
             &'l mut Vec<clap::Command>,
-            std::marker::PhantomData<IC>,
+            std::marker::PhantomData<(IC, E)>,
         );
-        impl<'l, IC: InterfaceCustomization> EntityPartVisitor for PartVisitor<'l, IC> {
+        impl<'l, IC: InterfaceCustomization, E: Entity> EntityPartVisitor for PartVisitor<'l, IC, E> {
+            type Entity = E;
             fn visit<EP: microrm::schema::entity::EntityPart>(&mut self) {
                 struct Discriminator<'l, IC: InterfaceCustomization>(
                     &'l mut Vec<clap::Command>,
@@ -544,7 +553,7 @@ impl<O: CLIObject, IC: InterfaceCustomization> ClapInterface<O, IC> {
             }
         }
 
-        O::accept_part_visitor(&mut PartVisitor::<IC>(&mut out, Default::default()));
+        O::accept_part_visitor(&mut PartVisitor::<IC, O>(&mut out, Default::default()));
 
         out.into_iter()
     }

+ 12 - 1
microrm/src/lib.rs

@@ -33,7 +33,6 @@ pub mod cli;
 
 pub mod prelude {
     pub use crate::query::{AssocInterface, Insertable, Queryable};
-    // pub use crate::schema::entity::Entity;
     pub use crate::schema::{AssocMap, Database, IDMap};
     pub use microrm_macros::{Database, Entity};
 }
@@ -42,21 +41,33 @@ pub mod prelude {
 // Generically-useful database types
 // ----------------------------------------------------------------------
 
+/// microrm error type, returned from most microrm methods.
 #[derive(Debug)]
 pub enum Error {
+    /// No result was present where one was expected.
     EmptyResult,
+    /// Stored value encountered that is incompatible with the schema.
     UnknownValue(String),
+    /// Schema mismatch between on-disk database and current schema.
     IncompatibleSchema,
+    /// Internal error occured, likely is a bug in microrm or very unexpected behaviour.
     InternalError(&'static str),
+    /// Non-UTF8 data was encountered somewhere that UTF8 data was expected.
     EncodingError(std::str::Utf8Error),
+    /// User error in API usage.
     LogicError(&'static str),
+    /// Attempted to insert an entity with values that violate a database constraint, probably
+    /// due to a uniqueness requirement.
     ConstraintViolation(&'static str),
+    /// Sqlite internal error that has not been translated into a more specific error.
     Sqlite {
         code: i32,
         msg: String,
         sql: Option<String>,
     },
+    /// Something strange happened with JSON serialization/deserialization.
     JSON(serde_json::Error),
+    /// One of the mutexes failed in a spectacular way.
     LockError(String),
 }
 

+ 40 - 15
microrm/src/query.rs

@@ -21,8 +21,13 @@ pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID>
         || {
             let mut part_names = String::new();
             let mut placeholders = String::new();
-            struct PartNameVisitor<'a>(&'a mut String, &'a mut String);
-            impl<'a> EntityPartVisitor for PartNameVisitor<'a> {
+            struct PartNameVisitor<'a, E: Entity>(
+                &'a mut String,
+                &'a mut String,
+                std::marker::PhantomData<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
                     // with it here; it doesn't have a column
@@ -41,7 +46,11 @@ pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID>
                 }
             }
 
-            E::accept_part_visitor(&mut PartNameVisitor(&mut part_names, &mut placeholders));
+            E::accept_part_visitor(&mut PartNameVisitor(
+                &mut part_names,
+                &mut placeholders,
+                Default::default(),
+            ));
 
             format!(
                 "INSERT INTO `{}` ({}) VALUES ({}) RETURNING `id`",
@@ -51,8 +60,13 @@ pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID>
             )
         },
         |mut ctx| {
-            struct PartBinder<'a, 'b>(&'a mut StatementContext<'b>, i32);
-            impl<'a, 'b> EntityPartVisitor for PartBinder<'a, 'b> {
+            struct PartBinder<'a, 'b, E: Entity>(
+                &'a mut StatementContext<'b>,
+                i32,
+                std::marker::PhantomData<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>() {
@@ -64,7 +78,7 @@ pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID>
                 }
             }
 
-            value.accept_part_visitor_ref(&mut PartBinder(&mut ctx, 1));
+            value.accept_part_visitor_ref(&mut PartBinder(&mut ctx, 1, Default::default()));
 
             ctx.run()?
                 .ok_or(Error::InternalError("No result row from INSERT query"))
@@ -77,8 +91,9 @@ pub(crate) fn insert_and_return<E: Entity>(conn: &Connection, mut value: E) -> D
     let id = insert(conn, &value)?;
 
     // update assoc data in all fields
-    struct DatumWalker<'l>(&'l Connection, i64);
-    impl<'l> EntityPartVisitor for DatumWalker<'l> {
+    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 {
                 conn: self.0.clone(),
@@ -89,7 +104,7 @@ pub(crate) fn insert_and_return<E: Entity>(conn: &Connection, mut value: E) -> D
         }
     }
 
-    value.accept_part_visitor_mut(&mut DatumWalker(conn, id.into_raw()));
+    value.accept_part_visitor_mut(&mut DatumWalker(conn, id.into_raw(), Default::default()));
 
     Ok(Stored::new(conn.clone(), id, value))
 }
@@ -101,8 +116,9 @@ pub(crate) fn update_entity<E: Entity>(conn: &Connection, value: &Stored<E>) ->
         std::any::TypeId::of::<UpdateQuery<E>>(),
         || {
             let mut set_columns = String::new();
-            struct PartNameVisitor<'a>(&'a mut String);
-            impl<'a> EntityPartVisitor for PartNameVisitor<'a> {
+            struct PartNameVisitor<'a, E: Entity>(&'a mut String, std::marker::PhantomData<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
                     // with it here; it doesn't have a column
@@ -119,15 +135,20 @@ pub(crate) fn update_entity<E: Entity>(conn: &Connection, value: &Stored<E>) ->
                 }
             }
 
-            E::accept_part_visitor(&mut PartNameVisitor(&mut set_columns));
+            E::accept_part_visitor(&mut PartNameVisitor(&mut set_columns, Default::default()));
             format!(
                 "UPDATE {entity_name} SET {set_columns} WHERE `id` = ?",
                 entity_name = E::entity_name()
             )
         },
         |mut ctx| {
-            struct PartBinder<'a, 'b>(&'a mut StatementContext<'b>, &'a mut i32);
-            impl<'a, 'b> EntityPartVisitor for PartBinder<'a, 'b> {
+            struct PartBinder<'a, 'b, E: Entity>(
+                &'a mut StatementContext<'b>,
+                &'a mut i32,
+                std::marker::PhantomData<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>() {
@@ -141,7 +162,11 @@ pub(crate) fn update_entity<E: Entity>(conn: &Connection, value: &Stored<E>) ->
 
             // first bind all the updating clauses
             let mut index = 1;
-            value.accept_part_visitor_ref(&mut PartBinder(&mut ctx, &mut index));
+            value.accept_part_visitor_ref(&mut PartBinder(
+                &mut ctx,
+                &mut index,
+                Default::default(),
+            ));
 
             // then bind the id
             value.id().bind_to(&mut ctx, index);

+ 4 - 3
microrm/src/query/components.rs

@@ -217,8 +217,9 @@ impl<
     fn build(&self) -> Query {
         let mut query = self.parent.build();
 
-        struct PartVisitor<'a>(&'a mut Query);
-        impl<'a> EntityPartVisitor for PartVisitor<'a> {
+        struct PartVisitor<'a, E: Entity>(&'a mut Query, std::marker::PhantomData<E>);
+        impl<'a, E: Entity> EntityPartVisitor for PartVisitor<'a, E> {
+            type Entity = E;
             fn visit<EP: EntityPart>(&mut self) {
                 self.0.attach_mut(
                     QueryPart::Where,
@@ -231,7 +232,7 @@ impl<
             }
         }
 
-        <E::Keys>::accept_part_visitor(&mut PartVisitor(&mut query));
+        <E::Keys>::accept_part_visitor(&mut PartVisitor::<E>(&mut query, Default::default()));
 
         query
     }

+ 18 - 10
microrm/src/schema/collect.rs

@@ -114,19 +114,20 @@ pub struct EntityState {
 
 impl EntityState {
     fn build<E: Entity>() -> Self {
-        #[derive(Default)]
-        struct PartVisitor(Vec<PartState>);
-        impl EntityPartVisitor for PartVisitor {
+        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::default();
+        let mut pv = PartVisitor(vec![], Default::default());
         E::accept_part_visitor(&mut pv);
 
-        struct KeyVisitor<'l>(&'l mut Vec<PartState>);
-        impl<'l> EntityPartVisitor for KeyVisitor<'l> {
+        struct KeyVisitor<'l, E: Entity>(&'l mut Vec<PartState>, std::marker::PhantomData<E>);
+        impl<'l, E: Entity> EntityPartVisitor for KeyVisitor<'l, E> {
+            type Entity = E;
             fn visit<EP: EntityPart>(&mut self) {
                 for part in self.0.iter_mut() {
                     if part.name == EP::part_name() {
@@ -136,7 +137,10 @@ impl EntityState {
             }
         }
 
-        <E::Keys as EntityPartList>::accept_part_visitor(&mut KeyVisitor(&mut pv.0));
+        <E::Keys as EntityPartList>::accept_part_visitor(&mut KeyVisitor::<E>(
+            &mut pv.0,
+            Default::default(),
+        ));
 
         Self {
             name: E::entity_name(),
@@ -183,13 +187,17 @@ impl<'a> EntityVisitor for EntityContext<'a> {
             panic!("Identical entity name but different typeid!");
         }
 
-        struct RecursiveVisitor<'a, 'b>(&'a mut EntityContext<'b>);
-        impl<'a, 'b> EntityPartVisitor for RecursiveVisitor<'a, 'b> {
+        struct RecursiveVisitor<'a, 'b, E: Entity>(
+            &'a mut EntityContext<'b>,
+            std::marker::PhantomData<E>,
+        );
+        impl<'a, 'b, E: Entity> EntityPartVisitor for RecursiveVisitor<'a, 'b, E> {
+            type Entity = E;
             fn visit<EP: EntityPart>(&mut self) {
                 EP::Datum::accept_entity_visitor(self.0);
             }
         }
 
-        E::accept_part_visitor(&mut RecursiveVisitor(self));
+        E::accept_part_visitor(&mut RecursiveVisitor(self, Default::default()));
     }
 }

+ 16 - 12
microrm/src/schema/entity.rs

@@ -38,11 +38,12 @@ pub trait EntityPart: Default + Clone + 'static {
     fn get_datum(from: &Self::Entity) -> &Self::Datum;
 }
 
-/// Visitor for traversing all `EntityPart`s in an `Entity` or `EntityPartList`.
+/// Visitor for traversing all [`EntityPart`]s in an [`Entity`] or [`EntityPartList`].
 pub trait EntityPartVisitor {
-    fn visit<EP: EntityPart>(&mut self) {}
-    fn visit_datum<EP: EntityPart>(&mut self, _datum: &EP::Datum) {}
-    fn visit_datum_mut<EP: EntityPart>(&mut self, _datum: &mut EP::Datum) {}
+    type Entity: Entity;
+    fn visit<EP: EntityPart<Entity = Self::Entity>>(&mut self) {}
+    fn visit_datum<EP: EntityPart<Entity = Self::Entity>>(&mut self, _datum: &EP::Datum) {}
+    fn visit_datum_mut<EP: EntityPart<Entity = Self::Entity>>(&mut self, _datum: &mut EP::Datum) {}
 }
 
 /// List of EntityParts.
@@ -56,8 +57,11 @@ pub trait EntityPartList: 'static {
 
     fn build_datum_list(conn: &Connection, stmt: &mut StatementRow) -> DBResult<Self::DatumList>;
 
-    fn accept_part_visitor(_: &mut impl EntityPartVisitor);
-    fn accept_part_visitor_ref(datum_list: &Self::DatumList, _: &mut impl EntityPartVisitor);
+    fn accept_part_visitor(_: &mut impl EntityPartVisitor<Entity = Self::Entity>);
+    fn accept_part_visitor_ref(
+        datum_list: &Self::DatumList,
+        _: &mut impl EntityPartVisitor<Entity = Self::Entity>,
+    );
 }
 
 // trait implementations for EntityPartList
@@ -71,19 +75,19 @@ pub use part_list::EmptyList;
 
 /// A single database entity, aka an object type that gets its own table.
 pub trait Entity: 'static + std::fmt::Debug {
-    type Parts: EntityPartList;
-    type Keys: EntityPartList;
+    type Parts: EntityPartList<Entity = Self>;
+    type Keys: EntityPartList<Entity = Self>;
     type ID: EntityID<Entity = Self> + EntityPart<Datum = Self::ID, Entity = Self>;
 
     fn build(values: <Self::Parts as EntityPartList>::DatumList) -> Self;
 
     fn entity_name() -> &'static str;
-    fn accept_part_visitor(visitor: &mut impl EntityPartVisitor);
-    fn accept_part_visitor_ref(&self, visitor: &mut impl EntityPartVisitor);
-    fn accept_part_visitor_mut(&mut self, _: &mut impl EntityPartVisitor);
+    fn accept_part_visitor(visitor: &mut impl EntityPartVisitor<Entity = Self>);
+    fn accept_part_visitor_ref(&self, visitor: &mut impl EntityPartVisitor<Entity = Self>);
+    fn accept_part_visitor_mut(&mut self, _: &mut impl EntityPartVisitor<Entity = Self>);
 }
 
-/// Visitor for traversing all `Entity`s in a container.
+/// Visitor for traversing all [`Entity`]s in a container type.
 pub trait EntityVisitor {
     fn visit<E: Entity>(&mut self);
 }

+ 12 - 6
microrm/src/schema/entity/part_list.rs

@@ -98,10 +98,13 @@ impl<E: Entity, P0: EntityPart<Entity = E>> EntityPartList for P0 {
         Ok(d0)
     }
 
-    fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
+    fn accept_part_visitor(v: &mut impl EntityPartVisitor<Entity = Self::Entity>) {
         v.visit::<P0>();
     }
-    fn accept_part_visitor_ref(datum_list: &Self::DatumList, v: &mut impl EntityPartVisitor) {
+    fn accept_part_visitor_ref(
+        datum_list: &Self::DatumList,
+        v: &mut impl EntityPartVisitor<Entity = Self::Entity>,
+    ) {
         v.visit_datum::<P0>(datum_list);
     }
 }
@@ -117,10 +120,13 @@ impl<E: Entity, P0: EntityPart<Entity = E>> EntityPartList for (P0,) {
         <P0 as EntityPartList>::build_datum_list(conn, stmt)
     }
 
-    fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
+    fn accept_part_visitor(v: &mut impl EntityPartVisitor<Entity = Self::Entity>) {
         v.visit::<P0>();
     }
-    fn accept_part_visitor_ref(datum_list: &Self::DatumList, v: &mut impl EntityPartVisitor) {
+    fn accept_part_visitor_ref(
+        datum_list: &Self::DatumList,
+        v: &mut impl EntityPartVisitor<Entity = Self::Entity>,
+    ) {
         v.visit_datum::<P0>(datum_list);
     }
 }
@@ -145,14 +151,14 @@ macro_rules! part_list_impl {
                 Ok(( $d0, $( $d ),* ))
             }
 
-            fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
+            fn accept_part_visitor(v: &mut impl EntityPartVisitor<Entity = Self::Entity>) {
                 v.visit::< $p0 >();
                 $(
                     v.visit::< $p >();
                 )*
             }
 
-            fn accept_part_visitor_ref(datum_list: &Self::DatumList, v: &mut impl EntityPartVisitor) {
+            fn accept_part_visitor_ref(datum_list: &Self::DatumList, v: &mut impl EntityPartVisitor<Entity = Self::Entity>) {
                 v.visit_datum::< $p0 >(&datum_list . $n0);
                 $(
                     v.visit_datum::< $p >(&datum_list . $n);

+ 11 - 6
microrm/src/schema/tests.rs

@@ -120,13 +120,13 @@ mod manual_test_db {
         fn entity_name() -> &'static str {
             "simple_entity"
         }
-        fn accept_part_visitor(visitor: &mut impl EntityPartVisitor) {
+        fn accept_part_visitor(visitor: &mut impl EntityPartVisitor<Entity = Self>) {
             visitor.visit::<SimpleEntityName>();
         }
-        fn accept_part_visitor_ref(&self, visitor: &mut impl EntityPartVisitor) {
+        fn accept_part_visitor_ref(&self, visitor: &mut impl EntityPartVisitor<Entity = Self>) {
             visitor.visit_datum::<SimpleEntityName>(&self.name);
         }
-        fn accept_part_visitor_mut(&mut self, visitor: &mut impl EntityPartVisitor) {
+        fn accept_part_visitor_mut(&mut self, visitor: &mut impl EntityPartVisitor<Entity = Self>) {
             visitor.visit_datum_mut::<SimpleEntityName>(&mut self.name);
         }
     }
@@ -169,16 +169,21 @@ mod manual_test_db {
 
     #[test]
     fn part_visitor() {
-        struct V {
+        struct V<E: Entity> {
             v: Vec<std::any::TypeId>,
+            _ghost: std::marker::PhantomData<E>,
         }
-        impl EntityPartVisitor for V {
+        impl<E: Entity> EntityPartVisitor for V<E> {
+            type Entity = E;
             fn visit<EP: EntityPart>(&mut self) {
                 self.v.push(std::any::TypeId::of::<EP>());
             }
         }
 
-        let mut vis = V { v: vec![] };
+        let mut vis = V {
+            v: vec![],
+            _ghost: Default::default(),
+        };
         SimpleEntity::accept_part_visitor(&mut vis);
         assert_eq!(
             vis.v.as_slice(),

+ 5 - 0
rust-analyzer.json

@@ -0,0 +1,5 @@
+{
+    "cargo": {
+        "features": ["clap"]
+    }
+}