浏览代码

Add support for injective relations.

Kestrel 7 月之前
父节点
当前提交
5ce6dd89cf
共有 3 个文件被更改,包括 91 次插入2 次删除
  1. 4 2
      microrm/src/schema/build.rs
  2. 5 0
      microrm/src/schema/collect.rs
  3. 82 0
      microrm/src/schema/tests.rs

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

@@ -156,6 +156,7 @@ pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
                 PartType::AssocDomain {
                     table_name: assoc_table_name,
                     range_name,
+                    injective
                 } => {
                     let mut assoc_table = TableInfo::new(assoc_table_name.clone());
 
@@ -170,7 +171,7 @@ pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
                         name: "range",
                         ty: "int".into(),
                         fkey: Some(format!("`{}`(`id`)", range_name)),
-                        unique: false,
+                        unique: *injective,
                     });
 
                     assoc_table
@@ -182,6 +183,7 @@ pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
                 PartType::AssocRange {
                     table_name: assoc_table_name,
                     domain_name,
+                    injective
                 } => {
                     let mut assoc_table = TableInfo::new(assoc_table_name.clone());
 
@@ -196,7 +198,7 @@ pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
                         name: "range",
                         ty: "int".into(),
                         fkey: Some(format!("`{}`(`id`)", table_name)),
-                        unique: false,
+                        unique: *injective,
                     });
 
                     assoc_table

+ 5 - 0
microrm/src/schema/collect.rs

@@ -13,10 +13,12 @@ pub enum PartType {
     AssocDomain {
         table_name: String,
         range_name: &'static str,
+        injective: bool,
     },
     AssocRange {
         table_name: String,
         domain_name: &'static str,
+        injective: bool,
     },
 }
 
@@ -56,6 +58,7 @@ impl PartState {
                         EP::part_name()
                     ),
                     range_name: E::entity_name(),
+                    injective: false,
                 });
             }
 
@@ -68,6 +71,7 @@ impl PartState {
                         R::NAME
                     ),
                     range_name: R::Range::entity_name(),
+                    injective: R::INJECTIVE,
                 });
             }
 
@@ -80,6 +84,7 @@ impl PartState {
                         R::NAME
                     ),
                     domain_name: R::Domain::entity_name(),
+                    injective: R::INJECTIVE,
                 });
             }
         }

+ 82 - 0
microrm/src/schema/tests.rs

@@ -803,3 +803,85 @@ mod query_equivalence {
         );
     }
 }
+
+mod injective_test {
+    use microrm::prelude::*;
+    use test_log::test;
+
+    struct AuthorBookRelation;
+    impl microrm::Relation for AuthorBookRelation {
+        type Domain = Author;
+        type Range = Book;
+
+        const NAME: &'static str = "AuthorBook";
+        const INJECTIVE: bool = true;
+    }
+
+    #[derive(Entity)]
+    struct Author {
+        #[key]
+        name: String,
+
+        works: microrm::AssocDomain<AuthorBookRelation>,
+    }
+
+    #[derive(Entity)]
+    struct Book {
+        #[key]
+        title: String,
+
+        creator: microrm::AssocRange<AuthorBookRelation>,
+    }
+
+    #[derive(Database)]
+    struct ABDB {
+        authors: microrm::IDMap<Author>,
+        books: microrm::IDMap<Book>,
+    }
+
+    #[test]
+    fn schema_creation_test() {
+        let db = ABDB::open_path(":memory:").expect("couldn't open in-memory database");
+    }
+
+    #[test]
+    fn single_connection() {
+        let db = ABDB::open_path(":memory:").expect("couldn't open in-memory database");
+
+        let a1 = db.authors.insert_and_return(Author {
+            name: "Homer".to_string(),
+            works: Default::default(),
+        }).expect("couldn't insert author");
+
+        let a2 = db.authors.insert_and_return(Author {
+            name: "Virgil".to_string(),
+            works: Default::default(),
+        }).expect("couldn't insert author");
+
+        let b1_id = db.books.insert(Book {
+            title: "Odyssey".to_string(),
+            creator: Default::default(),
+        }).expect("couldn't insert book");
+        let b2_id = db.books.insert(Book {
+            title: "Aeneid".to_string(),
+            creator: Default::default(),
+        }).expect("couldn't insert book");
+
+        a1.works.connect_to(b1_id).expect("couldn't connect a1 and b1");
+        a2.works.connect_to(b2_id).expect("couldn't connect a2 and b2");
+
+        // we can't claim that Homer wrote the Aeneid because it's an injective relationship and
+        // only one Author can claim the Book as their work
+        match a1.works.connect_to(b2_id) {
+            Err(microrm::Error::ConstraintViolation(_)) => {
+                // all good
+            },
+            Err(_) => {
+                panic!("Unexpected error while testing injective connection");
+            },
+            Ok(_) => {
+                panic!("Unexpected success while testing injective connection");
+            },
+        }
+    }
+}