2 Commits d5f05ba334 ... cf86d6ebfc

Autore SHA1 Messaggio Data
  Kestrel cf86d6ebfc Update cli module to follow ConnectionPool/ConnectionLease changes. 1 settimana fa
  Kestrel 43638d3bb6 Revert "Add lease-carrier support to Queryable." 1 settimana fa

+ 1 - 1
microrm-macros/src/lib.rs

@@ -85,7 +85,7 @@ pub fn index_cols(tokens: TokenStream) -> TokenStream {
     index::index_cols(tokens)
 }
 
-/// Value specification
+/// Value specification, for values stored inline as JSON in an `Entity` field.
 #[proc_macro_derive(Value)]
 pub fn value(tokens: TokenStream) -> TokenStream {
     value::derive(tokens)

+ 75 - 31
microrm/src/cli.rs

@@ -17,7 +17,7 @@
 use crate::{
     prelude::{Insertable, Queryable},
     schema::entity::Entity,
-    Error,
+    ConnectionLease, Error,
 };
 
 mod eval;
@@ -62,6 +62,7 @@ pub trait EntityInterface {
     fn run_custom(
         ctx: &Self::Context,
         cmd: Self::CustomCommand,
+        lease: &mut ConnectionLease,
         query_ctx: impl Queryable<EntityOutput = Self::Entity> + Insertable<Self::Entity>,
     ) -> Result<(), Self::Error>;
 
@@ -130,9 +131,12 @@ pub struct Autogenerate<EI: EntityInterface> {
 
 #[cfg(test)]
 mod tests {
+    use crate::ConnectionPool;
+
     use super::{Autogenerate, EntityInterface};
     use clap::Parser;
     use microrm::prelude::*;
+    use microrm::ConnectionLease;
     use test_log::test;
 
     struct CTRelation;
@@ -175,7 +179,7 @@ mod tests {
         employee: microrm::RelationDomain<ETRelation>,
     }
 
-    #[derive(Database)]
+    #[derive(Default, Schema)]
     struct TransactionTestDB {
         customers: microrm::IDMap<Customer>,
         employees: microrm::IDMap<Employee>,
@@ -198,14 +202,18 @@ mod tests {
         fn run_custom(
             _ctx: &Self::Context,
             cmd: Self::CustomCommand,
+            lease: &mut ConnectionLease,
             query_ctx: impl Queryable<EntityOutput = Self::Entity> + Insertable<Self::Entity>,
         ) -> Result<(), Self::Error> {
             match cmd {
                 CCustom::Create { name } => {
-                    query_ctx.insert(Customer {
-                        name,
-                        txs: Default::default(),
-                    })?;
+                    query_ctx.insert(
+                        lease,
+                        Customer {
+                            name,
+                            txs: Default::default(),
+                        },
+                    )?;
                 },
             }
             Ok(())
@@ -228,14 +236,18 @@ mod tests {
         fn run_custom(
             _ctx: &Self::Context,
             cmd: Self::CustomCommand,
+            lease: &mut ConnectionLease,
             query_ctx: impl Queryable<EntityOutput = Self::Entity> + Insertable<Self::Entity>,
         ) -> Result<(), Self::Error> {
             match cmd {
                 ECustom::Create { name } => {
-                    query_ctx.insert(Employee {
-                        name,
-                        txs: Default::default(),
-                    })?;
+                    query_ctx.insert(
+                        lease,
+                        Employee {
+                            name,
+                            txs: Default::default(),
+                        },
+                    )?;
                 },
             }
             Ok(())
@@ -258,16 +270,20 @@ mod tests {
         fn run_custom(
             _ctx: &Self::Context,
             cmd: Self::CustomCommand,
+            lease: &mut ConnectionLease,
             query_ctx: impl Queryable<EntityOutput = Self::Entity> + Insertable<Self::Entity>,
         ) -> Result<(), Self::Error> {
             match cmd {
                 TCustom::Create { title, amount } => {
-                    query_ctx.insert(Transaction {
-                        title,
-                        amount,
-                        customer: Default::default(),
-                        employee: Default::default(),
-                    })?;
+                    query_ctx.insert(
+                        lease,
+                        Transaction {
+                            title,
+                            amount,
+                            customer: Default::default(),
+                            employee: Default::default(),
+                        },
+                    )?;
                 },
             }
             Ok(())
@@ -290,18 +306,18 @@ mod tests {
         },
     }
 
-    fn run_cmd(db: &TransactionTestDB, args: &[&str]) {
+    fn run_cmd(lease: &mut ConnectionLease, db: &TransactionTestDB, args: &[&str]) {
         match <Params as Parser>::try_parse_from(args) {
             Ok(Params::Customer { cmd }) => {
-                cmd.perform(&(), &db.customers)
+                cmd.perform(&(), lease, &db.customers)
                     .expect("couldn't perform command");
             },
             Ok(Params::Employee { cmd }) => {
-                cmd.perform(&(), &db.employees)
+                cmd.perform(&(), lease, &db.employees)
                     .expect("couldn't perform command");
             },
             Ok(Params::Tx { cmd }) => {
-                cmd.perform(&(), &db.transactions)
+                cmd.perform(&(), lease, &db.transactions)
                     .expect("couldn't perform command");
             },
             Err(e) => {
@@ -313,28 +329,39 @@ mod tests {
 
     #[test]
     fn simple_entity_create_delete() {
-        let db = TransactionTestDB::open_path(":memory:").unwrap();
+        let pool = ConnectionPool::new(":memory:").unwrap();
+        let mut lease = pool.acquire().unwrap();
+        let db = TransactionTestDB::default();
+        db.install(&mut lease).unwrap();
 
         assert_eq!(
             db.customers
                 .keyed("a_key")
-                .count()
+                .count(&mut lease)
                 .expect("couldn't count entries"),
             0
         );
-        run_cmd(&db, &["execname", "customer", "create", "a_key"]);
+        run_cmd(
+            &mut lease,
+            &db,
+            &["execname", "customer", "create", "a_key"],
+        );
         assert_eq!(
             db.customers
                 .keyed("a_key")
-                .count()
+                .count(&mut lease)
                 .expect("couldn't count entries"),
             1
         );
-        run_cmd(&db, &["execname", "customer", "delete", "a_key"]);
+        run_cmd(
+            &mut lease,
+            &db,
+            &["execname", "customer", "delete", "a_key"],
+        );
         assert_eq!(
             db.customers
                 .keyed("a_key")
-                .count()
+                .count(&mut lease)
                 .expect("couldn't count entries"),
             0
         );
@@ -342,16 +369,33 @@ mod tests {
 
     #[test]
     fn create_and_attach() {
-        let db = TransactionTestDB::open_path(":memory:").unwrap();
+        let pool = ConnectionPool::new(":memory:").unwrap();
+        let mut lease = pool.acquire().unwrap();
+        let db = TransactionTestDB::default();
+        db.install(&mut lease).unwrap();
 
-        run_cmd(&db, &["execname", "customer", "create", "cname"]);
-        run_cmd(&db, &["execname", "employee", "create", "ename"]);
-        run_cmd(&db, &["execname", "tx", "create", "tname", "100"]);
         run_cmd(
+            &mut lease,
+            &db,
+            &["execname", "customer", "create", "cname"],
+        );
+        run_cmd(
+            &mut lease,
+            &db,
+            &["execname", "employee", "create", "ename"],
+        );
+        run_cmd(
+            &mut lease,
+            &db,
+            &["execname", "tx", "create", "tname", "100"],
+        );
+        run_cmd(
+            &mut lease,
             &db,
             &["execname", "customer", "attach", "cname", "txs", "tname"],
         );
         run_cmd(
+            &mut lease,
             &db,
             &["execname", "employee", "attach", "ename", "txs", "tname"],
         );
@@ -362,7 +406,7 @@ mod tests {
                 .join(Customer::Txs)
                 .join(Transaction::Employee)
                 .first()
-                .get()
+                .get(&mut lease)
                 .expect("couldn't run query")
                 .expect("no such employee")
                 .name,

+ 44 - 27
microrm/src/cli/eval.rs

@@ -9,6 +9,7 @@ use crate::{
         entity::{Entity, EntityID, EntityPart, EntityPartList, EntityPartVisitor},
         relation::{Relation, RelationDomain, RelationMap, RelationRange},
     },
+    ConnectionLease,
 };
 
 // helper alias for later
@@ -19,6 +20,7 @@ impl<EI: EntityInterface> Autogenerate<EI> {
     pub fn perform(
         self,
         ctx: &EI::Context,
+        lease: &mut ConnectionLease,
         query_ctx: impl Queryable<EntityOutput = EI::Entity> + Insertable<EI::Entity>,
     ) -> Result<(), EI::Error> {
         match self.verb {
@@ -36,7 +38,7 @@ impl<EI: EntityInterface> Autogenerate<EI> {
                         )
                         .unwrap(),
                     )
-                    .get()?
+                    .get(lease)?
                     .ok_or(<EI::Error>::no_such_entity(
                         EI::Entity::entity_name(),
                         local_keys
@@ -50,6 +52,7 @@ impl<EI: EntityInterface> Autogenerate<EI> {
                 let mut attacher = Attacher {
                     do_attach: true,
                     relation: relation.as_str(),
+                    lease,
                     remote_keys,
                     err: None,
                     _ghost: Default::default(),
@@ -67,7 +70,7 @@ impl<EI: EntityInterface> Autogenerate<EI> {
                         UniqueList::<EI::Entity>::build_equivalent(keys.iter().map(String::as_str))
                             .unwrap(),
                     )
-                    .delete()?;
+                    .delete(lease)?;
             },
             Verb::Detach {
                 local_keys,
@@ -83,7 +86,7 @@ impl<EI: EntityInterface> Autogenerate<EI> {
                         )
                         .unwrap(),
                     )
-                    .get()?
+                    .get(lease)?
                     .ok_or(<EI::Error>::no_such_entity(
                         EI::Entity::entity_name(),
                         local_keys
@@ -97,6 +100,7 @@ impl<EI: EntityInterface> Autogenerate<EI> {
                 let mut attacher = Attacher {
                     do_attach: false,
                     relation: relation.as_str(),
+                    lease,
                     remote_keys,
                     err: None,
                     _ghost: Default::default(),
@@ -111,9 +115,9 @@ impl<EI: EntityInterface> Autogenerate<EI> {
                 println!(
                     "Listing all {}(s): ({})",
                     EI::Entity::entity_name(),
-                    query_ctx.clone().count()?
+                    query_ctx.clone().count(lease)?
                 );
-                for obj in query_ctx.get()?.into_iter() {
+                for obj in query_ctx.get(lease)?.into_iter() {
                     println!(
                         " - {}",
                         EI::summarize(&obj).unwrap_or_else(|| format!("{:?}", obj))
@@ -127,7 +131,7 @@ impl<EI: EntityInterface> Autogenerate<EI> {
                         UniqueList::<EI::Entity>::build_equivalent(keys.iter().map(String::as_str))
                             .unwrap(),
                     )
-                    .get()?
+                    .get(lease)?
                     .ok_or(<EI::Error>::no_such_entity(
                         EI::Entity::entity_name(),
                         keys.iter()
@@ -138,20 +142,27 @@ impl<EI: EntityInterface> Autogenerate<EI> {
                     ))?;
                 println!("{:#?}", obj.as_ref());
 
-                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 relations") {
+                fn inspect_ai<AI: RelationInterface>(
+                    name: &'static str,
+                    lease: &mut ConnectionLease<'_>,
+                    ai: &AI,
+                ) {
+                    println!("{}: ({})", name, ai.count(lease).unwrap());
+                    for a in ai.get(lease).expect("couldn't get object relations") {
                         println!("[#{:3}]: {:?}", a.id().into_raw(), a.wrapped());
                     }
                 }
 
-                struct RelationFieldWalker<E: Entity>(std::marker::PhantomData<E>);
-                impl<E: Entity> EntityPartVisitor for RelationFieldWalker<E> {
+                struct RelationFieldWalker<'r, 'l: 'r, E: Entity>(
+                    &'r mut ConnectionLease<'l>,
+                    std::marker::PhantomData<E>,
+                );
+                impl<'r, 'l: 'r, E: Entity> EntityPartVisitor for RelationFieldWalker<'r, 'l, E> {
                     type Entity = E;
                     fn visit_datum<EP: EntityPart>(&mut self, datum: &EP::Datum) {
-                        struct Discriminator(&'static str);
+                        struct Discriminator<'r, 'l: 'r>(&'r mut ConnectionLease<'l>, &'static str);
 
-                        impl DatumDiscriminatorRef for Discriminator {
+                        impl<'r, 'l> DatumDiscriminatorRef for Discriminator<'r, 'l> {
                             fn visit_serialized<
                                 T: serde::Serialize + serde::de::DeserializeOwned,
                             >(
@@ -162,30 +173,30 @@ impl<EI: EntityInterface> Autogenerate<EI> {
                             fn visit_bare_field<T: Datum>(&mut self, _: &T) {}
                             fn visit_entity_id<E: Entity>(&mut self, _: &E::ID) {}
                             fn visit_relation_map<E: Entity>(&mut self, amap: &RelationMap<E>) {
-                                inspect_ai(self.0, amap);
+                                inspect_ai(self.1, self.0, amap);
                             }
                             fn visit_relation_domain<R: Relation>(
                                 &mut self,
                                 adomain: &RelationDomain<R>,
                             ) {
-                                inspect_ai(self.0, adomain);
+                                inspect_ai(self.1, self.0, adomain);
                             }
                             fn visit_relation_range<R: Relation>(
                                 &mut self,
                                 arange: &RelationRange<R>,
                             ) {
-                                inspect_ai(self.0, arange);
+                                inspect_ai(self.1, self.0, arange);
                             }
                         }
 
-                        datum.accept_discriminator_ref(&mut Discriminator(EP::part_name()));
+                        datum.accept_discriminator_ref(&mut Discriminator(self.0, EP::part_name()));
                     }
                 }
 
-                obj.accept_part_visitor_ref(&mut RelationFieldWalker(Default::default()));
+                obj.accept_part_visitor_ref(&mut RelationFieldWalker(lease, Default::default()));
             },
             Verb::Custom(custom) => {
-                EI::run_custom(ctx, custom, query_ctx)?;
+                EI::run_custom(ctx, custom, lease, query_ctx)?;
             },
         }
 
@@ -194,15 +205,16 @@ impl<EI: EntityInterface> Autogenerate<EI> {
 }
 
 /// helper type for attach and detach verbs
-struct Attacher<'l, Error: CLIError, E: Entity> {
+struct Attacher<'r, 'l: 'r, Error: CLIError, E: Entity> {
     do_attach: bool,
-    relation: &'l str,
+    relation: &'r str,
+    lease: &'r mut ConnectionLease<'l>,
     remote_keys: Vec<String>,
     err: Option<Error>,
     _ghost: std::marker::PhantomData<E>,
 }
 
-impl<'l, Error: CLIError, OE: Entity> Attacher<'l, Error, OE> {
+impl<'r, 'l: 'r, Error: CLIError, OE: Entity> Attacher<'r, 'l, Error, OE> {
     fn do_operation<E: Entity>(&mut self, map: &impl RelationInterface<RemoteEntity = E>) {
         match map
             .query_all()
@@ -210,13 +222,16 @@ impl<'l, Error: CLIError, OE: Entity> Attacher<'l, Error, OE> {
                 UniqueList::<E>::build_equivalent(self.remote_keys.iter().map(String::as_str))
                     .unwrap(),
             )
-            .get()
+            .get(self.lease)
         {
             Ok(Some(obj)) => {
                 if self.do_attach {
-                    self.err = map.connect_to(obj.id()).err().map(Into::into);
+                    self.err = map.connect_to(self.lease, obj.id()).err().map(Into::into);
                 } else {
-                    self.err = map.disconnect_from(obj.id()).err().map(Into::into);
+                    self.err = map
+                        .disconnect_from(self.lease, obj.id())
+                        .err()
+                        .map(Into::into);
                 }
             },
             Ok(None) => {
@@ -237,7 +252,7 @@ impl<'l, Error: CLIError, OE: Entity> Attacher<'l, Error, OE> {
     }
 }
 
-impl<'l, Error: CLIError, E: Entity> EntityPartVisitor for Attacher<'l, Error, E> {
+impl<'r, 'l: 'r, Error: CLIError, E: Entity> EntityPartVisitor for Attacher<'r, 'l, Error, E> {
     type Entity = E;
     fn visit_datum<EP: EntityPart>(&mut self, datum: &EP::Datum) {
         if EP::part_name() != self.relation {
@@ -248,7 +263,9 @@ impl<'l, Error: CLIError, E: Entity> EntityPartVisitor for Attacher<'l, Error, E
     }
 }
 
-impl<'l, Error: CLIError, OE: Entity> DatumDiscriminatorRef for Attacher<'l, Error, OE> {
+impl<'r, 'l: 'r, Error: CLIError, OE: Entity> DatumDiscriminatorRef
+    for Attacher<'r, 'l, Error, OE>
+{
     fn visit_entity_id<E: Entity>(&mut self, _: &E::ID) {
         unreachable!()
     }

+ 1 - 6
microrm/src/glue.rs

@@ -2,7 +2,7 @@
 
 use crate::{
     db::{ConnectionLease, StatementContext, Transaction},
-    query::{self, Insertable, Query, QueryPart, QueryVerbs, Queryable, RelationInterface},
+    query::{self, Insertable, Query, QueryPart, Queryable, RelationInterface},
     schema::{
         entity::Entity,
         relation::{LocalSide, Relation, RelationData, RelationDomain, RelationMap, RelationRange},
@@ -39,14 +39,9 @@ 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")

+ 1 - 3
microrm/src/lib.rs

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

+ 124 - 241
microrm/src/query.rs

@@ -339,17 +339,14 @@ impl<AI: RelationInterface> Insertable<AI::RemoteEntity> for AI {
     }
 }
 
-/// Represents a searchable context of a given entity. See [`QueryVerbs`] for query verbs.
-pub trait Queryable {
+/// Represents a searchable context of a given entity.
+pub trait Queryable: Clone {
     /// 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;
@@ -361,202 +358,9 @@ pub trait Queryable {
     #[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)
-    }
-
     // ----------------------------------------------------------------------
-    // Relation-following and joining methods
+    // Verbs
     // ----------------------------------------------------------------------
-    /// 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.
@@ -679,7 +483,8 @@ pub trait QueryVerbs: Queryable<Carrier = ()> {
         )?;
         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,
@@ -712,72 +517,155 @@ pub trait QueryVerbs: Queryable<Carrier = ()> {
         txn.commit()?;
         Ok(out)
     }
-}
 
-impl<T: Queryable<Carrier = ()>> QueryVerbs for T {}
+    // ----------------------------------------------------------------------
+    // 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,
+        )
+    }
 
-/// 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>
+    /// 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,
     {
-        let (query, lease) = components::ExtractLeaseComponent::new(self);
-        query.count(lease)
+        components::IndexComponent::<_, _, EPL, _>::new(self, values)
     }
 
-    /// Get all entities in the current context.
-    fn get(self) -> DBResult<Self::OutputContainer>
+    /// 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>
     where
         Self: Sized,
     {
-        let (query, lease) = components::ExtractLeaseComponent::new(self);
-        query.get(lease)
+        components::WithComponent::new(self, part, value)
     }
 
-    /// Get IDs of all entities in the current context.
-    fn get_ids(
+    /// Filter exactly on an entity ID.
+    fn with_id(
         self,
-    ) -> DBResult<<Self::OutputContainer as OutputContainer<Self::EntityOutput>>::IDContainer>
+        id: <Self::EntityOutput as Entity>::ID,
+    ) -> impl Queryable<
+        EntityOutput = Self::EntityOutput,
+        OutputContainer = Option<Stored<Self::EntityOutput>>,
+    >
     where
         Self: Sized,
     {
-        let (query, lease) = components::ExtractLeaseComponent::new(self);
-        query.get_ids(lease)
+        self.with(<Self::EntityOutput as Entity>::IDPart::default(), id)
+            .first()
     }
 
-    /// Delete all entities in the current context.
-    fn delete(self) -> DBResult<()>
+    /// Ask to return at most a single result.
+    fn first(
+        self,
+    ) -> impl Queryable<
+        EntityOutput = Self::EntityOutput,
+        OutputContainer = Option<Stored<Self::EntityOutput>>,
+    >
     where
         Self: Sized,
     {
-        let (query, lease) = components::ExtractLeaseComponent::new(self);
-        query.delete(lease)
+        components::SingleComponent::new(self)
     }
 
-    /// Delete all entities in the current context and return them.
-    fn remove(self) -> DBResult<Self::OutputContainer>
+    // ----------------------------------------------------------------------
+    // 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>>>
     where
         Self: Sized,
     {
-        let (query, lease) = components::ExtractLeaseComponent::new(self);
-        query.remove(lease)
+        components::JoinComponent::<AD::RemoteEntity, Self::EntityOutput, _, Self>::new(self, part)
     }
-}
 
-impl<'r, 'l: 'r, T: Queryable<Carrier = components::LeaseCarrier<'r, 'l>>> CarrierQueryVerbs<'r, 'l>
-    for T
-{
+    /// 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>,
+    >
+    where
+        Self: Sized,
+        EP::Datum: EntityID,
+    {
+        components::ForeignComponent::<_, EP, Self>::new(self, part)
+    }
 }
 
-// ----------------------------------------------------------------------
-// Implementations on Index
-// ----------------------------------------------------------------------
+// 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<E: Entity, EPL: EntityPartList<Entity = E>> Index<E, EPL> {
     /// Perform a search through this index
@@ -795,14 +683,9 @@ 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")

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

@@ -9,100 +9,23 @@ use crate::{
         relation::{LocalSide, Relation},
         Stored,
     },
-    ConnectionLease,
 };
 
 use super::{OutputContainer, Query};
 
-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>,
+/// Allow manipulation of an entire table.
+pub(crate) struct TableComponent<E: Entity> {
+    _ghost: std::marker::PhantomData<E>,
 }
 
-impl<'r, 'l: 'r, Parent: Queryable> LeaseCarrierComponent<'r, 'l, Parent> {
-    pub(crate) fn new(parent: Parent, lease: &'r mut ConnectionLease<'l>) -> Self {
+impl<E: Entity> Clone for TableComponent<E> {
+    fn clone(&self) -> Self {
         Self {
-            parent,
-            lease: Some(lease),
+            _ghost: Default::default(),
         }
     }
 }
 
-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 {
@@ -115,14 +38,9 @@ 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")
@@ -133,6 +51,7 @@ 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,
@@ -155,6 +74,7 @@ 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)>,
 }
@@ -165,14 +85,9 @@ 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!()
     }
@@ -190,14 +105,9 @@ 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,
@@ -243,6 +153,22 @@ 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.
@@ -260,14 +186,9 @@ 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!()
     }
@@ -276,6 +197,16 @@ 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,
@@ -286,14 +217,9 @@ 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();
 
@@ -332,6 +258,7 @@ impl<
     }
 }
 
+#[derive(Clone)]
 pub(crate) struct SingleComponent<Parent: Queryable> {
     parent: Parent,
 }
@@ -346,14 +273,9 @@ 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 {
@@ -386,6 +308,17 @@ 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,
@@ -397,14 +330,9 @@ 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();
@@ -491,19 +419,23 @@ 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,

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

@@ -2,7 +2,7 @@ use std::collections::HashMap;
 
 use crate::{
     db::{ConnectionLease, Transaction},
-    query::{Insertable, QueryVerbs, Queryable},
+    query::{Insertable, Queryable},
     schema::{
         collect::{EntityStateContainer, PartType},
         entity::{Entity, EntityPart, EntityPartList, EntityPartVisitor},

+ 0 - 25
microrm/tests/lease_carrier.rs

@@ -1,25 +0,0 @@
-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);
-}