2 İşlemeler 5998372025 ... d5f05ba334

Yazar SHA1 Mesaj Tarih
  Kestrel d5f05ba334 Add lease-carrier support to Queryable. 1 hafta önce
  Kestrel 469efbee52 Add test for Value derivation macro/DatumProxy trait. 1 hafta önce

+ 6 - 1
microrm/src/glue.rs

@@ -2,7 +2,7 @@
 
 use crate::{
     db::{ConnectionLease, StatementContext, Transaction},
-    query::{self, Insertable, Query, QueryPart, Queryable, RelationInterface},
+    query::{self, Insertable, Query, QueryPart, QueryVerbs, Queryable, RelationInterface},
     schema::{
         entity::Entity,
         relation::{LocalSide, Relation, RelationData, RelationDomain, RelationMap, RelationRange},
@@ -39,9 +39,14 @@ impl<'a, T: Entity> Queryable for &'a IDMap<T> {
     type EntityOutput = T;
     type OutputContainer = Vec<Stored<T>>;
     type StaticVersion = &'static IDMap<T>;
+    type Carrier = ();
 
     const IS_UNIQUE: bool = false;
 
+    fn carried(&mut self) -> &mut Self::Carrier {
+        unreachable!()
+    }
+
     fn build(&self) -> Query {
         Query::new()
             .attach(QueryPart::Root, "SELECT DISTINCT")

+ 3 - 1
microrm/src/lib.rs

@@ -246,7 +246,9 @@ pub mod cli;
 
 /// Re-exported traits and macros for easy access.
 pub mod prelude {
-    pub use crate::query::{Insertable, Queryable, RelationInterface};
+    pub use crate::query::{
+        CarrierQueryVerbs, Insertable, QueryVerbs, Queryable, RelationInterface,
+    };
     pub use crate::schema::{relation::Relation, Schema, Serializable};
     pub use microrm_macros::{Entity, Schema, Value};
 }

+ 241 - 124
microrm/src/query.rs

@@ -339,14 +339,17 @@ impl<AI: RelationInterface> Insertable<AI::RemoteEntity> for AI {
     }
 }
 
-/// Represents a searchable context of a given entity.
-pub trait Queryable: Clone {
+/// Represents a searchable context of a given entity. See [`QueryVerbs`] for query verbs.
+pub trait Queryable {
     /// The entity that results from a search in this context.
     type EntityOutput: Entity;
     /// How results will be provided. This is either a `Vec` or an `Option`.
     type OutputContainer: OutputContainer<Self::EntityOutput>;
     /// A `'static`-version of `Self`, used for `TypeId`-based caching.
     type StaticVersion: Queryable + 'static;
+    /// A possible tag-along type stored in the query object. See [`Self::carry_lease`] for one
+    /// use.
+    type Carrier;
 
     /// True if the query result is guaranteed to be either unique or absent.
     const IS_UNIQUE: bool;
@@ -358,9 +361,202 @@ pub trait Queryable: Clone {
     #[doc(hidden)]
     fn bind(&self, stmt: &mut StatementContext, index: &mut i32);
 
+    #[doc(hidden)]
+    fn carried(&mut self) -> &mut Self::Carrier;
+
+    /// Construct a new [`Queryable`] that carries a connection lease along with it.
+    ///
+    /// This allows the use of the [`CarrierQueryVerbs`] trait instead of the [`QueryVerbs`] trait,
+    /// which reduces the number of objects that need be passed around.
+    fn carry_lease<'r, 'l: 'r>(
+        self,
+        lease: &'r mut ConnectionLease<'l>,
+    ) -> impl Queryable<
+        EntityOutput = Self::EntityOutput,
+        OutputContainer = Self::OutputContainer,
+        Carrier = Option<&'r mut ConnectionLease<'l>>,
+    >
+    where
+        Self: Sized,
+    {
+        components::LeaseCarrierComponent::new(self, lease)
+    }
+
+    // Verbs are now implemented in [`QueryVerbs`]
+
+    // ----------------------------------------------------------------------
+    // Filtering methods
+    // ----------------------------------------------------------------------
+    /// Filter using the keying index on the entity.
+    fn keyed(
+        self,
+        values: impl QueryEquivalentList<
+            <<Self::EntityOutput as Entity>::Keys as EntityPartList>::DatumList,
+        >,
+    ) -> impl Queryable<
+        EntityOutput = Self::EntityOutput,
+        OutputContainer = Option<Stored<Self::EntityOutput>>,
+        Carrier = Self::Carrier,
+    >
+    where
+        Self: Sized,
+    {
+        components::IndexComponent::<_, _, <Self::EntityOutput as Entity>::Keys, _>::new(
+            self, values,
+        )
+    }
+
+    /// Filter using an arbitrary index on the entity.
+    fn indexed<EPL: EntityPartList<Entity = Self::EntityOutput>>(
+        self,
+        _index: &Index<Self::EntityOutput, EPL>,
+        values: impl QueryEquivalentList<EPL::DatumList>,
+    ) -> impl Queryable<
+        EntityOutput = Self::EntityOutput,
+        OutputContainer = Option<Stored<Self::EntityOutput>>,
+        Carrier = Self::Carrier,
+    >
+    where
+        Self: Sized,
+    {
+        components::IndexComponent::<_, _, EPL, _>::new(self, values)
+    }
+
+    /// Filter using an arbitrary column on the entity.
+    fn with<EP: EntityPart<Entity = Self::EntityOutput>>(
+        self,
+        part: EP,
+        value: impl QueryEquivalent<EP::Datum>,
+    ) -> impl Queryable<
+        EntityOutput = Self::EntityOutput,
+        OutputContainer = Self::OutputContainer,
+        Carrier = Self::Carrier,
+    >
+    where
+        Self: Sized,
+    {
+        components::WithComponent::new(self, part, value)
+    }
+
+    /// Filter exactly on an entity ID.
+    fn with_id(
+        self,
+        id: <Self::EntityOutput as Entity>::ID,
+    ) -> impl Queryable<
+        EntityOutput = Self::EntityOutput,
+        OutputContainer = Option<Stored<Self::EntityOutput>>,
+        Carrier = Self::Carrier,
+    >
+    where
+        Self: Sized,
+    {
+        self.with(<Self::EntityOutput as Entity>::IDPart::default(), id)
+            .first()
+    }
+
+    /// Ask to return at most a single result.
+    fn first(
+        self,
+    ) -> impl Queryable<
+        EntityOutput = Self::EntityOutput,
+        OutputContainer = Option<Stored<Self::EntityOutput>>,
+        Carrier = Self::Carrier,
+    >
+    where
+        Self: Sized,
+    {
+        components::SingleComponent::new(self)
+    }
+
     // ----------------------------------------------------------------------
-    // Verbs
+    // Relation-following and joining methods
     // ----------------------------------------------------------------------
+    /// 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>>,
+        Carrier = Self::Carrier,
+    >
+    where
+        Self: Sized,
+    {
+        components::JoinComponent::<AD::RemoteEntity, Self::EntityOutput, _, Self>::new(self, part)
+    }
+
+    /// Follow a foreign key.
+    fn foreign<EP: EntityPart<Entity = Self::EntityOutput>>(
+        self,
+        part: EP,
+    ) -> impl Queryable<
+        EntityOutput = <EP::Datum as EntityID>::Entity,
+        OutputContainer = <Self::OutputContainer as OutputContainer<
+            Self::EntityOutput,
+        >>::ReplacedEntity<<EP::Datum as EntityID>::Entity>,
+        Carrier = Self::Carrier,
+    >
+    where
+        Self: Sized,
+        EP::Datum: EntityID,
+    {
+        components::ForeignComponent::<_, EP, Self>::new(self, part)
+    }
+}
+
+// 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::StaticVersion;
+    type Carrier = ();
+
+    const IS_UNIQUE: bool = false;
+
+    fn carried(&mut self) -> &mut Self::Carrier {
+        unreachable!()
+    }
+
+    fn build(&self) -> Query {
+        let anames = RelationNames::collect(*self).unwrap();
+        let relation_name = anames.relation_name();
+        Query::new()
+            .attach(QueryPart::Root, "SELECT DISTINCT")
+            .attach(QueryPart::Columns, format!("`{}`.*", anames.remote_name))
+            .attach(QueryPart::From, format!("`{}`", relation_name))
+            .attach(
+                QueryPart::Join,
+                format!(
+                    "`{}` ON `{}`.`id` = `{}`.`{}`",
+                    anames.remote_name, anames.remote_name, relation_name, anames.remote_field
+                ),
+            )
+            .attach(
+                QueryPart::Where,
+                format!("`{}`.`{}` = ?", relation_name, anames.local_field),
+            )
+    }
+    fn bind(&self, ctx: &mut StatementContext, index: &mut i32) {
+        let rdata = self
+            .get_data()
+            .expect("binding query for relation with no data");
+
+        ctx.bind(*index, rdata.local_id)
+            .expect("couldn't bind relation id");
+        *index += 1;
+    }
+}
+
+// ----------------------------------------------------------------------
+// Verb traits
+// ----------------------------------------------------------------------
+
+/// Completion verbs for a [`Queryable`] object.
+pub trait QueryVerbs: Queryable<Carrier = ()> {
     /// Count all entities in the current context.
     ///
     /// Returns the number of entities.
@@ -483,8 +679,7 @@ pub trait Queryable: Clone {
         )?;
         txn.commit()
     }
-
-    /// Delete all entities in the current context and return them
+    /// Delete all entities in the current context and return them.
     fn remove(self, lease: &mut ConnectionLease) -> DBResult<Self::OutputContainer>
     where
         Self: Sized,
@@ -517,156 +712,73 @@ pub trait Queryable: Clone {
         txn.commit()?;
         Ok(out)
     }
+}
 
-    // ----------------------------------------------------------------------
-    // Filtering methods
-    // ----------------------------------------------------------------------
-    /// Filter using the keying index on the entity.
-    fn keyed(
-        self,
-        values: impl QueryEquivalentList<
-            <<Self::EntityOutput as Entity>::Keys as EntityPartList>::DatumList,
-        >,
-    ) -> impl Queryable<
-        EntityOutput = Self::EntityOutput,
-        OutputContainer = Option<Stored<Self::EntityOutput>>,
-    >
-    where
-        Self: Sized,
-    {
-        components::IndexComponent::<_, _, <Self::EntityOutput as Entity>::Keys, _>::new(
-            self, values,
-        )
-    }
-
-    /// Filter using an arbitrary index on the entity.
-    fn indexed<EPL: EntityPartList<Entity = Self::EntityOutput>>(
-        self,
-        _index: &Index<Self::EntityOutput, EPL>,
-        values: impl QueryEquivalentList<EPL::DatumList>,
-    ) -> impl Queryable<
-        EntityOutput = Self::EntityOutput,
-        OutputContainer = Option<Stored<Self::EntityOutput>>,
-    >
-    where
-        Self: Sized,
-    {
-        components::IndexComponent::<_, _, EPL, _>::new(self, values)
-    }
+impl<T: Queryable<Carrier = ()>> QueryVerbs for T {}
 
-    /// Filter using an arbitrary column on the entity.
-    fn with<EP: EntityPart<Entity = Self::EntityOutput>>(
-        self,
-        part: EP,
-        value: impl QueryEquivalent<EP::Datum>,
-    ) -> impl Queryable<EntityOutput = Self::EntityOutput, OutputContainer = Self::OutputContainer>
+/// Completion verbs for a [`Queryable`] object that carries a [`ConnectionLease`].
+pub trait CarrierQueryVerbs<'r, 'l: 'r>:
+    Queryable<Carrier = components::LeaseCarrier<'r, 'l>>
+{
+    /// Count all entities in the current context.
+    ///
+    /// Returns the number of entities.
+    fn count(self) -> DBResult<usize>
     where
         Self: Sized,
     {
-        components::WithComponent::new(self, part, value)
+        let (query, lease) = components::ExtractLeaseComponent::new(self);
+        query.count(lease)
     }
 
-    /// Filter exactly on an entity ID.
-    fn with_id(
-        self,
-        id: <Self::EntityOutput as Entity>::ID,
-    ) -> impl Queryable<
-        EntityOutput = Self::EntityOutput,
-        OutputContainer = Option<Stored<Self::EntityOutput>>,
-    >
+    /// Get all entities in the current context.
+    fn get(self) -> DBResult<Self::OutputContainer>
     where
         Self: Sized,
     {
-        self.with(<Self::EntityOutput as Entity>::IDPart::default(), id)
-            .first()
+        let (query, lease) = components::ExtractLeaseComponent::new(self);
+        query.get(lease)
     }
 
-    /// Ask to return at most a single result.
-    fn first(
+    /// Get IDs of all entities in the current context.
+    fn get_ids(
         self,
-    ) -> impl Queryable<
-        EntityOutput = Self::EntityOutput,
-        OutputContainer = Option<Stored<Self::EntityOutput>>,
-    >
+    ) -> DBResult<<Self::OutputContainer as OutputContainer<Self::EntityOutput>>::IDContainer>
     where
         Self: Sized,
     {
-        components::SingleComponent::new(self)
+        let (query, lease) = components::ExtractLeaseComponent::new(self);
+        query.get_ids(lease)
     }
 
-    // ----------------------------------------------------------------------
-    // Relation-following and joining methods
-    // ----------------------------------------------------------------------
-    /// 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>>>
+    /// Delete all entities in the current context.
+    fn delete(self) -> DBResult<()>
     where
         Self: Sized,
     {
-        components::JoinComponent::<AD::RemoteEntity, Self::EntityOutput, _, Self>::new(self, part)
+        let (query, lease) = components::ExtractLeaseComponent::new(self);
+        query.delete(lease)
     }
 
-    /// Follow a foreign key.
-    fn foreign<EP: EntityPart<Entity = Self::EntityOutput>>(
-        self,
-        part: EP,
-    ) -> impl Queryable<
-        EntityOutput = <EP::Datum as EntityID>::Entity,
-        OutputContainer = <Self::OutputContainer as OutputContainer<
-            Self::EntityOutput,
-        >>::ReplacedEntity<<EP::Datum as EntityID>::Entity>,
-    >
+    /// Delete all entities in the current context and return them.
+    fn remove(self) -> DBResult<Self::OutputContainer>
     where
         Self: Sized,
-        EP::Datum: EntityID,
     {
-        components::ForeignComponent::<_, EP, Self>::new(self, part)
+        let (query, lease) = components::ExtractLeaseComponent::new(self);
+        query.remove(lease)
     }
 }
 
-// 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::StaticVersion;
-
-    const IS_UNIQUE: bool = false;
-
-    fn build(&self) -> Query {
-        let anames = RelationNames::collect(*self).unwrap();
-        let relation_name = anames.relation_name();
-        Query::new()
-            .attach(QueryPart::Root, "SELECT DISTINCT")
-            .attach(QueryPart::Columns, format!("`{}`.*", anames.remote_name))
-            .attach(QueryPart::From, format!("`{}`", relation_name))
-            .attach(
-                QueryPart::Join,
-                format!(
-                    "`{}` ON `{}`.`id` = `{}`.`{}`",
-                    anames.remote_name, anames.remote_name, relation_name, anames.remote_field
-                ),
-            )
-            .attach(
-                QueryPart::Where,
-                format!("`{}`.`{}` = ?", relation_name, anames.local_field),
-            )
-    }
-    fn bind(&self, ctx: &mut StatementContext, index: &mut i32) {
-        let rdata = self
-            .get_data()
-            .expect("binding query for relation with no data");
-
-        ctx.bind(*index, rdata.local_id)
-            .expect("couldn't bind relation id");
-        *index += 1;
-    }
+impl<'r, 'l: 'r, T: Queryable<Carrier = components::LeaseCarrier<'r, 'l>>> CarrierQueryVerbs<'r, 'l>
+    for T
+{
 }
 
+// ----------------------------------------------------------------------
+// Implementations on Index
+// ----------------------------------------------------------------------
+
 impl<E: Entity, EPL: EntityPartList<Entity = E>> Index<E, EPL> {
     /// Perform a search through this index
     ///
@@ -683,9 +795,14 @@ impl<'a, E: Entity, EPL: EntityPartList<Entity = E>> Queryable for &'a Index<E,
     type EntityOutput = E;
     type OutputContainer = Vec<Stored<E>>;
     type StaticVersion = &'static Index<E, EPL>;
+    type Carrier = ();
 
     const IS_UNIQUE: bool = false;
 
+    fn carried(&mut self) -> &mut Self::Carrier {
+        unreachable!()
+    }
+
     fn build(&self) -> Query {
         Query::new()
             .attach(QueryPart::Root, "SELECT DISTINCT")

+ 123 - 55
microrm/src/query/components.rs

@@ -9,23 +9,100 @@ use crate::{
         relation::{LocalSide, Relation},
         Stored,
     },
+    ConnectionLease,
 };
 
 use super::{OutputContainer, Query};
 
-/// Allow manipulation of an entire table.
-pub(crate) struct TableComponent<E: Entity> {
-    _ghost: std::marker::PhantomData<E>,
+pub(crate) type LeaseCarrier<'r, 'l> = Option<&'r mut ConnectionLease<'l>>;
+
+pub(crate) struct LeaseCarrierComponent<'r, 'l: 'r, Parent: Queryable> {
+    parent: Parent,
+    lease: LeaseCarrier<'r, 'l>,
 }
 
-impl<E: Entity> Clone for TableComponent<E> {
-    fn clone(&self) -> Self {
+impl<'r, 'l: 'r, Parent: Queryable> LeaseCarrierComponent<'r, 'l, Parent> {
+    pub(crate) fn new(parent: Parent, lease: &'r mut ConnectionLease<'l>) -> Self {
         Self {
-            _ghost: Default::default(),
+            parent,
+            lease: Some(lease),
         }
     }
 }
 
+impl<'r, 'l: 'r, Parent: Queryable> Queryable for LeaseCarrierComponent<'r, 'l, Parent> {
+    type EntityOutput = Parent::EntityOutput;
+    type OutputContainer = Parent::OutputContainer;
+    // lease carrying does not affect the query at all, so for caching purposes, drop it
+    type StaticVersion = Parent::StaticVersion;
+
+    type Carrier = LeaseCarrier<'r, 'l>;
+
+    const IS_UNIQUE: bool = Parent::IS_UNIQUE;
+
+    fn carried(&mut self) -> &mut Self::Carrier {
+        &mut self.lease
+    }
+
+    fn build(&self) -> Query {
+        self.parent.build()
+    }
+
+    fn bind(&self, stmt: &mut StatementContext, index: &mut i32) {
+        self.parent.bind(stmt, index)
+    }
+}
+
+pub(crate) struct ExtractLeaseComponent<
+    'r,
+    'l: 'r,
+    Parent: Queryable<Carrier = LeaseCarrier<'r, 'l>>,
+> {
+    parent: Parent,
+}
+
+impl<'r, 'l: 'r, Parent: Queryable<Carrier = LeaseCarrier<'r, 'l>>>
+    ExtractLeaseComponent<'r, 'l, Parent>
+{
+    pub(crate) fn new(mut parent: Parent) -> (Self, &'r mut ConnectionLease<'l>) {
+        let lease = parent
+            .carried()
+            .take()
+            .expect("extracting lease a second time?");
+        (Self { parent }, lease)
+    }
+}
+
+impl<'r, 'l: 'r, Parent: Queryable<Carrier = LeaseCarrier<'r, 'l>>> Queryable
+    for ExtractLeaseComponent<'r, 'l, Parent>
+{
+    type EntityOutput = Parent::EntityOutput;
+    type OutputContainer = Parent::OutputContainer;
+    // lease extraction does not affect the query at all, so for caching purposes, drop it
+    type StaticVersion = Parent::StaticVersion;
+
+    type Carrier = ();
+
+    const IS_UNIQUE: bool = Parent::IS_UNIQUE;
+
+    fn carried(&mut self) -> &mut Self::Carrier {
+        unreachable!()
+    }
+
+    fn build(&self) -> Query {
+        self.parent.build()
+    }
+
+    fn bind(&self, stmt: &mut StatementContext, index: &mut i32) {
+        self.parent.bind(stmt, index)
+    }
+}
+
+/// Allow manipulation of an entire table.
+pub(crate) struct TableComponent<E: Entity> {
+    _ghost: std::marker::PhantomData<E>,
+}
+
 impl<E: Entity> TableComponent<E> {
     pub fn new() -> Self {
         Self {
@@ -38,9 +115,14 @@ impl<E: Entity> Queryable for TableComponent<E> {
     type EntityOutput = E;
     type OutputContainer = Vec<Stored<E>>;
     type StaticVersion = Self;
+    type Carrier = ();
 
     const IS_UNIQUE: bool = false;
 
+    fn carried(&mut self) -> &mut Self::Carrier {
+        unreachable!()
+    }
+
     fn build(&self) -> Query {
         Query::new()
             .attach(QueryPart::Root, "SELECT DISTINCT")
@@ -51,7 +133,6 @@ impl<E: Entity> Queryable for TableComponent<E> {
 }
 
 /// Filter on a Datum
-#[derive(Clone)]
 pub(crate) struct WithComponent<WEP: EntityPart, Parent: Queryable, QE: QueryEquivalent<WEP::Datum>>
 {
     datum: QE,
@@ -74,7 +155,6 @@ impl<WEP: EntityPart, Parent: Queryable, QE: QueryEquivalent<WEP::Datum>>
 /// this workaround is needed because we very explicitly would like QE to not be a
 /// 'static-restricted type, and it doesn't matter for the purposes of actually distinguishing
 /// between queries.
-#[derive(Clone)]
 pub(crate) struct CanonicalWithComponent<WEP: EntityPart, Parent: Queryable> {
     _ghost: std::marker::PhantomData<(WEP, Parent)>,
 }
@@ -85,9 +165,14 @@ impl<WEP: EntityPart, Parent: Queryable + 'static> Queryable
     type EntityOutput = WEP::Entity;
     type OutputContainer = Option<Stored<WEP::Entity>>;
     type StaticVersion = Self;
+    type Carrier = Parent::Carrier;
 
     const IS_UNIQUE: bool = Parent::IS_UNIQUE;
 
+    fn carried(&mut self) -> &mut Self::Carrier {
+        unreachable!()
+    }
+
     fn build(&self) -> Query {
         unreachable!()
     }
@@ -105,9 +190,14 @@ impl<
     type EntityOutput = WEP::Entity;
     type OutputContainer = Parent::OutputContainer;
     type StaticVersion = CanonicalWithComponent<WEP, Parent::StaticVersion>;
+    type Carrier = Parent::Carrier;
 
     const IS_UNIQUE: bool = Parent::IS_UNIQUE;
 
+    fn carried(&mut self) -> &mut Self::Carrier {
+        self.parent.carried()
+    }
+
     fn build(&self) -> Query {
         self.parent.build().attach(
             QueryPart::Where,
@@ -153,22 +243,6 @@ impl<
     }
 }
 
-impl<
-        E: Entity,
-        Parent: Queryable,
-        EPL: EntityPartList<Entity = E>,
-        EL: QueryEquivalentList<EPL::DatumList>,
-    > Clone for IndexComponent<E, Parent, EPL, EL>
-{
-    fn clone(&self) -> Self {
-        Self {
-            datum: self.datum.clone(),
-            parent: self.parent.clone(),
-            _ghost: Default::default(),
-        }
-    }
-}
-
 /// this workaround is needed because we very explicitly would like EL to not be a
 /// 'static-restricted type, and it doesn't matter for the purposes of actually distinguishing
 /// between queries.
@@ -186,9 +260,14 @@ impl<E: Entity, Parent: Queryable + 'static, EPL: EntityPartList<Entity = E>> Qu
     type EntityOutput = E;
     type OutputContainer = Option<Stored<E>>;
     type StaticVersion = Self;
+    type Carrier = Parent::Carrier;
 
     const IS_UNIQUE: bool = Parent::IS_UNIQUE;
 
+    fn carried(&mut self) -> &mut Self::Carrier {
+        unreachable!()
+    }
+
     fn build(&self) -> Query {
         unreachable!()
     }
@@ -197,16 +276,6 @@ impl<E: Entity, Parent: Queryable + 'static, EPL: EntityPartList<Entity = E>> Qu
     }
 }
 
-impl<E: Entity, Parent: Queryable + 'static, EPL: EntityPartList<Entity = E>> Clone
-    for CanonicalIndexComponent<E, Parent, EPL>
-{
-    fn clone(&self) -> Self {
-        Self {
-            _ghost: Default::default(),
-        }
-    }
-}
-
 impl<
         E: Entity,
         Parent: Queryable,
@@ -217,9 +286,14 @@ impl<
     type EntityOutput = E;
     type OutputContainer = Option<Stored<E>>;
     type StaticVersion = CanonicalIndexComponent<E, Parent::StaticVersion, EPL>;
+    type Carrier = Parent::Carrier;
 
     const IS_UNIQUE: bool = true;
 
+    fn carried(&mut self) -> &mut Self::Carrier {
+        self.parent.carried()
+    }
+
     fn build(&self) -> Query {
         let mut query = self.parent.build();
 
@@ -258,7 +332,6 @@ impl<
     }
 }
 
-#[derive(Clone)]
 pub(crate) struct SingleComponent<Parent: Queryable> {
     parent: Parent,
 }
@@ -273,9 +346,14 @@ impl<Parent: Queryable> Queryable for SingleComponent<Parent> {
     type EntityOutput = Parent::EntityOutput;
     type OutputContainer = Option<Stored<Self::EntityOutput>>;
     type StaticVersion = SingleComponent<Parent::StaticVersion>;
+    type Carrier = Parent::Carrier;
 
     const IS_UNIQUE: bool = true;
 
+    fn carried(&mut self) -> &mut Self::Carrier {
+        self.parent.carried()
+    }
+
     fn build(&self) -> Query {
         // it's not a crime to ask a second time, but it's not valid SQL to repeat the LIMIT 1, either
         if Parent::IS_UNIQUE {
@@ -308,17 +386,6 @@ impl<R: Entity, L: Entity, EP: EntityPart<Entity = L>, Parent: Queryable>
     }
 }
 
-impl<R: Entity, L: Entity, EP: EntityPart<Entity = L>, Parent: Queryable> Clone
-    for JoinComponent<R, L, EP, Parent>
-{
-    fn clone(&self) -> Self {
-        Self {
-            parent: self.parent.clone(),
-            _ghost: Default::default(),
-        }
-    }
-}
-
 impl<
         R: Entity,
         L: Entity,
@@ -330,9 +397,14 @@ impl<
     type EntityOutput = R;
     type OutputContainer = Vec<Stored<R>>;
     type StaticVersion = JoinComponent<R, L, EP, Parent::StaticVersion>;
+    type Carrier = Parent::Carrier;
 
     const IS_UNIQUE: bool = Parent::IS_UNIQUE;
 
+    fn carried(&mut self) -> &mut Self::Carrier {
+        self.parent.carried()
+    }
+
     fn build(&self) -> Query {
         let remote_name = R::entity_name();
         let local_name = L::entity_name();
@@ -419,23 +491,19 @@ impl<FE: Entity, EP: EntityPart, Parent: Queryable> ForeignComponent<FE, EP, Par
     }
 }
 
-impl<FE: Entity, EP: EntityPart, Parent: Queryable> Clone for ForeignComponent<FE, EP, Parent> {
-    fn clone(&self) -> Self {
-        Self {
-            parent: self.parent.clone(),
-            _ghost: Default::default(),
-        }
-    }
-}
-
 impl<FE: Entity, EP: EntityPart, Parent: Queryable> Queryable for ForeignComponent<FE, EP, Parent> {
     type EntityOutput = FE;
     type StaticVersion = ForeignComponent<FE, EP, Parent::StaticVersion>;
     type OutputContainer =
         <Parent::OutputContainer as OutputContainer<Parent::EntityOutput>>::ReplacedEntity<FE>;
+    type Carrier = Parent::Carrier;
 
     const IS_UNIQUE: bool = Parent::IS_UNIQUE;
 
+    fn carried(&mut self) -> &mut Self::Carrier {
+        self.parent.carried()
+    }
+
     fn build(&self) -> Query {
         let subquery = self.parent.build().replace(
             QueryPart::Columns,

+ 5 - 2
microrm/src/schema/build.rs

@@ -2,7 +2,7 @@ use std::collections::HashMap;
 
 use crate::{
     db::{ConnectionLease, Transaction},
-    query::{Insertable, Queryable},
+    query::{Insertable, QueryVerbs, Queryable},
     schema::{
         collect::{EntityStateContainer, PartType},
         entity::{Entity, EntityPart, EntityPartList, EntityPartVisitor},
@@ -338,7 +338,10 @@ pub(crate) fn collect_from_database<DB: Schema>(schema: &DB) -> DatabaseSchema {
     let digest = hasher.finalize();
 
     DatabaseSchema {
-        signature: digest.into_iter().fold(String::new(), |mut a, v| { a += &format!("{:02x}", v); a }),
+        signature: digest.into_iter().fold(String::new(), |mut a, v| {
+            a += &format!("{:02x}", v);
+            a
+        }),
         table_queries: HashMap::from_iter(table_queries),
         index_queries: HashMap::from_iter(index_queries),
     }

+ 3 - 3
microrm/src/schema/datum/datum_list.rs

@@ -14,7 +14,7 @@ impl DatumList for () {
     fn list_head(&self) -> &Self::ListHead {
         unreachable!()
     }
-    fn list_tail(&self) -> Self::ListTail { }
+    fn list_tail(&self) -> Self::ListTail {}
 
     fn accept(&self, _: &mut impl DatumVisitor) {}
 }
@@ -36,7 +36,7 @@ impl<T: Datum> DatumList for T {
     fn list_head(&self) -> &Self::ListHead {
         self
     }
-    fn list_tail(&self) -> Self::ListTail { }
+    fn list_tail(&self) -> Self::ListTail {}
 
     fn accept(&self, visitor: &mut impl DatumVisitor) {
         visitor.visit(self);
@@ -61,7 +61,7 @@ impl<T0: Datum> DatumList for (T0,) {
     fn list_head(&self) -> &Self::ListHead {
         &self.0
     }
-    fn list_tail(&self) -> Self::ListTail { }
+    fn list_tail(&self) -> Self::ListTail {}
 
     fn accept(&self, visitor: &mut impl DatumVisitor) {
         visitor.visit(&self.0);

+ 2 - 0
microrm/tests/common/mod.rs

@@ -1,3 +1,5 @@
+#![allow(unused)]
+
 use microrm::prelude::*;
 
 pub fn open_test_db_helper<DB: Schema + Default>(

+ 25 - 0
microrm/tests/lease_carrier.rs

@@ -0,0 +1,25 @@
+use microrm::prelude::*;
+use test_log::test;
+
+mod common;
+
+#[derive(Entity)]
+struct Entry {
+    #[key]
+    name: String,
+    value: String,
+}
+
+#[derive(Default, Schema)]
+struct KVStore {
+    entries: microrm::IDMap<Entry>,
+}
+
+#[test]
+fn test_carry() {
+    let (pool, db): (_, KVStore) = common::open_test_db!();
+    let mut lease = pool.acquire().unwrap();
+
+    assert_eq!(db.entries.count(&mut lease).unwrap(), 0);
+    assert_eq!(db.entries.carry_lease(&mut lease).count().unwrap(), 0);
+}

+ 108 - 0
microrm/tests/value_proxy.rs

@@ -0,0 +1,108 @@
+use microrm::prelude::*;
+use test_log::test;
+
+mod common;
+
+#[derive(PartialEq, Clone, Debug, Value, serde::Serialize, serde::Deserialize)]
+enum PersonState {
+    Unborn,
+    Alive,
+    Dead,
+    Undead,
+}
+
+#[derive(Entity)]
+struct Person {
+    #[key]
+    name: String,
+    state: PersonState,
+}
+
+#[derive(Default, Schema)]
+struct Census {
+    people: microrm::IDMap<Person>,
+}
+
+#[test]
+fn test_insertion() {
+    let (pool, db): (_, Census) = common::open_test_db!();
+    let mut lease = pool.acquire().unwrap();
+
+    db.people
+        .insert(
+            &mut lease,
+            Person {
+                name: String::from("name 1"),
+                state: PersonState::Alive,
+            },
+        )
+        .unwrap();
+
+    db.people
+        .insert(
+            &mut lease,
+            Person {
+                name: String::from("name 2"),
+                state: PersonState::Dead,
+            },
+        )
+        .unwrap();
+}
+
+#[test]
+fn test_retrieval() {
+    let (pool, db): (_, Census) = common::open_test_db!();
+    let mut lease = pool.acquire().unwrap();
+
+    let id = db
+        .people
+        .insert(
+            &mut lease,
+            Person {
+                name: String::from("name 1"),
+                state: PersonState::Alive,
+            },
+        )
+        .unwrap();
+
+    assert_eq!(
+        db.people
+            .by_id(&mut lease, id)
+            .ok()
+            .flatten()
+            .unwrap()
+            .state,
+        PersonState::Alive
+    );
+}
+
+#[test]
+fn test_search() {
+    let (pool, db): (_, Census) = common::open_test_db!();
+    let mut lease = pool.acquire().unwrap();
+
+    db.people
+        .insert(
+            &mut lease,
+            Person {
+                name: String::from("name 1"),
+                state: PersonState::Alive,
+            },
+        )
+        .unwrap();
+
+    assert_eq!(
+        db.people
+            .with(Person::State, PersonState::Undead)
+            .count(&mut lease)
+            .unwrap(),
+        0
+    );
+    assert_eq!(
+        db.people
+            .with(Person::State, PersonState::Alive)
+            .count(&mut lease)
+            .unwrap(),
+        1
+    );
+}