6 次代碼提交 7e0d1124ec ... 2d3b2178af

作者 SHA1 備註 提交日期
  Kestrel 2d3b2178af Bump version number to 0.5.0-rc.2. 2 周之前
  Kestrel de1d675e4e Merge branch 'v0.5' into v0.5.0-rc 2 周之前
  Kestrel cf86d6ebfc Update cli module to follow ConnectionPool/ConnectionLease changes. 2 周之前
  Kestrel 43638d3bb6 Revert "Add lease-carrier support to Queryable." 2 周之前
  Kestrel d5f05ba334 Add lease-carrier support to Queryable. 2 周之前
  Kestrel 469efbee52 Add test for Value derivation macro/DatumProxy trait. 2 周之前

+ 1 - 1
microrm-macros/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "microrm-macros"
-version = "0.5.0-rc.1"
+version = "0.5.0-rc.2"
 edition = "2021"
 license = "BSD-4-Clause"
 authors = ["Kestrel <kestrel@flying-kestrel.ca>"]

+ 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)

+ 2 - 2
microrm/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "microrm"
-version = "0.5.0-rc.1"
+version = "0.5.0-rc.2"
 edition = "2021"
 license = "BSD-4-Clause"
 authors = ["Kestrel <kestrel@flying-kestrel.ca>"]
@@ -27,7 +27,7 @@ thread_local = "1.1"
 sha2 = "0.10"
 lazy_static = "1.5"
 
-microrm-macros = { path = "../microrm-macros", version = "0.5.0-rc.1" }
+microrm-macros = { path = "../microrm-macros", version = "0.5.0-rc.2" }
 log = "0.4.17"
 
 clap = { version = "4", optional = true }

+ 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!()
     }

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

@@ -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>(

+ 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
+    );
+}