Jelajahi Sumber

Implement simple entity keying mechanism to support multiple fields in a unique key.

Kestrel 7 bulan lalu
induk
melakukan
3a79e8a7a1

+ 8 - 4
microrm-macros/src/entity.rs

@@ -29,6 +29,10 @@ fn is_unique(attrs: &Vec<syn::Attribute>) -> bool {
     attrs.iter().filter(|a| a.path.is_ident("unique")).count() > 0
 }
 
+fn is_key(attrs: &Vec<syn::Attribute>) -> bool {
+    attrs.iter().filter(|a| a.path.is_ident("key")).count() > 0
+}
+
 pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let input: syn::DeriveInput = syn::parse_macro_input!(tokens);
 
@@ -70,9 +74,9 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let vis = input.vis;
 
     // collect list of unique parts
-    let unique_parts = parts
+    let key_parts = parts
         .iter()
-        .filter(|part| is_unique(&part.2))
+        .filter(|part| is_key(&part.2))
         .cloned()
         .collect::<Vec<_>>();
 
@@ -180,7 +184,7 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
         .collect::<Vec<_>>();
 
     let parts_list = make_part_list(&parts);
-    let uniques_list = make_part_list(&unique_parts);
+    let key_list = make_part_list(&key_parts);
 
     let entity_ident_str = entity_ident.to_string();
     let entity_name = entity_ident.to_string().to_case(Case::Snake);
@@ -261,7 +265,7 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
 
         impl ::microrm::schema::entity::Entity for #entity_ident {
             type Parts = #parts_list;
-            type Uniques = #uniques_list;
+            type Keys = #key_list;
             type ID = #id_ident;
 
             fn build(values: <Self::Parts as ::microrm::schema::entity::EntityPartList>::DatumList) -> Self {

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

@@ -3,7 +3,7 @@ use proc_macro::TokenStream;
 mod database;
 mod entity;
 
-#[proc_macro_derive(Entity, attributes(unique, elide))]
+#[proc_macro_derive(Entity, attributes(unique, elide, key))]
 pub fn derive_entity(tokens: TokenStream) -> TokenStream {
     entity::derive(tokens)
 }

+ 4 - 4
microrm/src/query.rs

@@ -592,11 +592,11 @@ pub trait Queryable: Clone {
     // ----------------------------------------------------------------------
     // Filtering methods
     // ----------------------------------------------------------------------
-    /// Filter using the unique index on the entity.
-    fn unique(
+    /// Filter using the keying index on the entity.
+    fn keyed(
         self,
         values: impl QueryEquivalentList<
-            <<Self::EntityOutput as Entity>::Uniques as EntityPartList>::DatumList,
+            <<Self::EntityOutput as Entity>::Keys as EntityPartList>::DatumList,
         >,
     ) -> impl Queryable<
         EntityOutput = Self::EntityOutput,
@@ -605,7 +605,7 @@ pub trait Queryable: Clone {
     where
         Self: Sized,
     {
-        components::UniqueComponent::new(self, values)
+        components::KeyComponent::new(self, values)
     }
     /// Filter using an arbitrary column on the entity.
     fn with<EP: EntityPart<Entity = Self::EntityOutput>>(

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

@@ -132,11 +132,11 @@ impl<
     }
 }
 
-/// Filter on the unique index
-pub(crate) struct UniqueComponent<
+/// Filter on the keying index
+pub(crate) struct KeyComponent<
     E: Entity,
     Parent: Queryable,
-    EL: QueryEquivalentList<<E::Uniques as EntityPartList>::DatumList>,
+    EL: QueryEquivalentList<<E::Keys as EntityPartList>::DatumList>,
 > {
     datum: EL,
     parent: Parent,
@@ -146,8 +146,8 @@ pub(crate) struct UniqueComponent<
 impl<
         E: Entity,
         Parent: Queryable,
-        EL: QueryEquivalentList<<E::Uniques as EntityPartList>::DatumList>,
-    > UniqueComponent<E, Parent, EL>
+        EL: QueryEquivalentList<<E::Keys as EntityPartList>::DatumList>,
+    > KeyComponent<E, Parent, EL>
 {
     pub fn new(parent: Parent, datum: EL) -> Self {
         Self {
@@ -161,8 +161,8 @@ impl<
 impl<
         E: Entity,
         Parent: Queryable,
-        EL: QueryEquivalentList<<E::Uniques as EntityPartList>::DatumList>,
-    > Clone for UniqueComponent<E, Parent, EL>
+        EL: QueryEquivalentList<<E::Keys as EntityPartList>::DatumList>,
+    > Clone for KeyComponent<E, Parent, EL>
 {
     fn clone(&self) -> Self {
         Self {
@@ -207,8 +207,8 @@ impl<E: Entity, Parent: Queryable + 'static> Clone for CanonicalUniqueComponent<
 impl<
         E: Entity,
         Parent: Queryable,
-        EL: QueryEquivalentList<<E::Uniques as EntityPartList>::DatumList>,
-    > Queryable for UniqueComponent<E, Parent, EL>
+        EL: QueryEquivalentList<<E::Keys as EntityPartList>::DatumList>,
+    > Queryable for KeyComponent<E, Parent, EL>
 {
     type EntityOutput = E;
     type OutputContainer = Option<Stored<E>>;
@@ -231,7 +231,7 @@ impl<
             }
         }
 
-        <E::Uniques>::accept_part_visitor(&mut PartVisitor(&mut query));
+        <E::Keys>::accept_part_visitor(&mut PartVisitor(&mut query));
 
         query
     }

+ 16 - 3
microrm/src/schema/build.rs

@@ -1,3 +1,5 @@
+use topological_sort::TopologicalSort;
+
 use crate::{
     prelude::*,
     schema::{
@@ -81,7 +83,7 @@ impl DatabaseSchema {
         // check to see if the signature exists and matches
         metadb
             .metastore
-            .unique(&Self::SCHEMA_SIGNATURE_KEY.to_string())
+            .keyed(&Self::SCHEMA_SIGNATURE_KEY.to_string())
             .get()
             .ok()
             .flatten()
@@ -139,6 +141,7 @@ pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
         if tables.contains_key(&table_name) {
             continue;
         }
+
         let mut table = TableInfo::new(table_name.clone());
         for part in state.parts.iter() {
             match &part.ty {
@@ -206,11 +209,21 @@ pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
             }
         }
 
+        let key = state.parts.iter().filter(|p| p.key).collect::<Vec<_>>();
+        if !key.is_empty() {
+            table.constraints.push(format!(
+                "/* keying index */ unique({})",
+                key.into_iter()
+                    .map(|s| s.name.to_string())
+                    .reduce(|a, b| format!("{},{}", a, b))
+                    .unwrap()
+            ));
+        }
+
         tables.insert(table_name, table);
     }
 
-    let mut tsort: topological_sort::TopologicalSort<&str> =
-        topological_sort::TopologicalSort::new();
+    let mut tsort: TopologicalSort<&str> = TopologicalSort::new();
     for table in tables.values() {
         tsort.insert(table.table_name.as_str());
 

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

@@ -1,7 +1,7 @@
 use std::collections::HashMap;
 
 use crate::schema::datum::Datum;
-use crate::schema::entity::{Entity, EntityPart, EntityPartVisitor, EntityVisitor};
+use crate::schema::entity::{Entity, EntityPart, EntityPartList, EntityPartVisitor, EntityVisitor};
 use crate::schema::{DatumDiscriminator, Relation};
 
 #[derive(Debug)]
@@ -22,20 +22,11 @@ pub struct PartState {
     pub name: &'static str,
     pub ty: PartType,
     pub unique: bool,
+    pub key: bool,
 }
 
 impl PartState {
     fn build<EP: EntityPart>() -> Self {
-        struct AssocCheck(Option<&'static str>);
-        impl EntityVisitor for AssocCheck {
-            fn visit<E: Entity>(&mut self) {
-                self.0 = Some(E::entity_name());
-            }
-        }
-
-        let mut acheck = AssocCheck(None);
-        EP::Datum::accept_entity_visitor(&mut acheck);
-
         struct Discriminator<EP: EntityPart> {
             ty: Option<PartType>,
             _ghost: std::marker::PhantomData<EP>,
@@ -103,6 +94,7 @@ impl PartState {
                 name: EP::part_name(),
                 ty,
                 unique: EP::unique(),
+                key: false,
             }
         } else {
             unreachable!("no PartType extracted from EntityPart")
@@ -131,6 +123,19 @@ impl EntityState {
         let mut pv = PartVisitor::default();
         E::accept_part_visitor(&mut pv);
 
+        struct KeyVisitor<'l>(&'l mut Vec<PartState>);
+        impl<'l> EntityPartVisitor for KeyVisitor<'l> {
+            fn visit<EP: EntityPart>(&mut self) {
+                for part in self.0.iter_mut() {
+                    if part.name == EP::part_name() {
+                        part.key = true;
+                    }
+                }
+            }
+        }
+
+        <E::Keys as EntityPartList>::accept_part_visitor(&mut KeyVisitor(&mut pv.0));
+
         Self {
             name: E::entity_name(),
             typeid: std::any::TypeId::of::<E>(),

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

@@ -65,7 +65,7 @@ mod part_list;
 /// A single database entity, aka an object type that gets its own table.
 pub trait Entity: 'static + std::fmt::Debug {
     type Parts: EntityPartList;
-    type Uniques: EntityPartList;
+    type Keys: EntityPartList;
     type ID: EntityID<Entity = Self> + EntityPart<Datum = Self::ID, Entity = Self>;
 
     fn build(values: <Self::Parts as EntityPartList>::DatumList) -> Self;

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

@@ -3,7 +3,7 @@ use crate::schema::IDMap;
 #[derive(Clone, microrm_macros::Entity)]
 pub struct Meta {
     /// Metadata key-value key
-    #[unique]
+    #[key]
     pub key: String,
     /// Metadata key-value value
     pub value: String,

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

@@ -110,7 +110,7 @@ mod manual_test_db {
 
     impl Entity for SimpleEntity {
         type Parts = SimpleEntityName;
-        type Uniques = SimpleEntityName;
+        type Keys = SimpleEntityName;
         type ID = SimpleEntityID;
 
         fn build(name: String) -> Self {
@@ -203,7 +203,7 @@ mod derive_tests {
 
     #[derive(Entity)]
     struct Person {
-        #[unique]
+        #[key]
         name: String,
         roles: AssocMap<Role>,
     }
@@ -230,13 +230,7 @@ mod derive_tests {
         // let db = PeopleDB::open_path(":memory:").expect("couldn't open database");
         let name_string = "name_here".to_string();
         // check that it isn't in the database before we insert it
-        assert!(db
-            .people
-            .unique(&name_string)
-            .get()
-            .ok()
-            .flatten()
-            .is_none());
+        assert!(db.people.keyed(&name_string).get().ok().flatten().is_none());
 
         db.people
             .insert(Person {
@@ -246,13 +240,7 @@ mod derive_tests {
             .expect("failed to insert");
 
         // check that it is in the database after we insert it
-        assert!(db
-            .people
-            .unique(&name_string)
-            .get()
-            .ok()
-            .flatten()
-            .is_some());
+        assert!(db.people.keyed(&name_string).get().ok().flatten().is_some());
     }
 
     #[test]
@@ -269,7 +257,7 @@ mod derive_tests {
 
         let person = db
             .people
-            .unique(&name_string)
+            .keyed(&name_string)
             .get()
             .ok()
             .flatten()
@@ -297,7 +285,7 @@ mod derive_tests {
 
         let person = db
             .people
-            .unique(&name_string)
+            .keyed(&name_string)
             .get()
             .ok()
             .flatten()
@@ -673,16 +661,16 @@ mod query_equivalence {
     use test_log::test;
     #[derive(Entity)]
     struct SingleItem {
-        #[unique]
+        #[key]
         s: String,
         v: Vec<u8>,
     }
 
     #[derive(Entity)]
     struct DoubleItem {
-        #[unique]
+        #[key]
         s: String,
-        #[unique]
+        #[key]
         t: String,
         v: usize,
     }
@@ -713,26 +701,26 @@ mod query_equivalence {
 
         assert!(db
             .single_items
-            .unique("string 2")
+            .keyed("string 2")
             .get()
             .expect("couldn't query db")
             .is_some());
         assert!(db
             .single_items
-            .unique("string 3")
+            .keyed("string 3")
             .get()
             .expect("couldn't query db")
             .is_none());
 
         assert!(db
             .single_items
-            .unique(String::from("string 2"))
+            .keyed(String::from("string 2"))
             .get()
             .expect("couldn't query db")
             .is_some());
         assert!(db
             .single_items
-            .unique(String::from("string 3"))
+            .keyed(String::from("string 3"))
             .get()
             .expect("couldn't query db")
             .is_none());
@@ -760,26 +748,26 @@ mod query_equivalence {
 
         assert!(db
             .double_items
-            .unique(("string 2", "second string 2"))
+            .keyed(("string 2", "second string 2"))
             .get()
             .expect("couldn't query db")
             .is_some());
         assert!(db
             .double_items
-            .unique(("string 3", "second string 3"))
+            .keyed(("string 3", "second string 3"))
             .get()
             .expect("couldn't query db")
             .is_none());
 
         assert!(db
             .double_items
-            .unique((String::from("string 2"), String::from("second string 2")))
+            .keyed((String::from("string 2"), String::from("second string 2")))
             .get()
             .expect("couldn't query db")
             .is_some());
         assert!(db
             .double_items
-            .unique((String::from("string 3"), String::from("second string 3")))
+            .keyed((String::from("string 3"), String::from("second string 3")))
             .get()
             .expect("couldn't query db")
             .is_none());