Browse Source

Detect IDMap collisions and complain.

Kestrel 1 month ago
parent
commit
5b3d037694

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

@@ -132,7 +132,7 @@ impl DatabaseSchema {
         db.execute_raw_sql("COMMIT")?;
 
         // store signature
-        metadb.metastore.insert(meta::Meta {
+        metadb.metastore.insert(meta::MicrormMeta {
             key: Self::SCHEMA_SIGNATURE_KEY.into(),
             value: format!("{}", self.signature),
         })?;
@@ -149,7 +149,7 @@ pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
         where
             Self: Sized,
         {
-            self.0.visit::<E>();
+            self.0.visit_idmap::<E>();
         }
 
         fn visit_index<T: Entity, PL: EntityPartList<Entity = T>>(&mut self)

+ 30 - 9
microrm/src/schema/collect.rs

@@ -117,6 +117,7 @@ pub struct EntityState {
     typeid: std::any::TypeId,
 
     pub parts: Vec<PartState>,
+    pub has_idmap: bool,
 }
 
 impl EntityState {
@@ -153,6 +154,7 @@ impl EntityState {
             name: E::entity_name(),
             typeid: std::any::TypeId::of::<E>(),
             parts: pv.0,
+            has_idmap: false,
         }
     }
 }
@@ -166,21 +168,34 @@ impl EntityStateContainer {
     pub fn iter_states(&self) -> impl Iterator<Item = &EntityState> {
         self.states.values()
     }
-}
 
-impl EntityVisitor for EntityStateContainer {
-    fn visit<E: Entity>(&mut self) {
+    pub fn visit_idmap<E: Entity>(&mut self) {
+        self.handle_visit::<E>(true);
+    }
+
+    fn handle_visit<E: Entity>(&mut self, is_idmap: bool) {
+        let entry = self.states.entry(E::entity_name());
         // cases:
         // 1. we haven't seen this entity
         // 2. we've seen this entity before
-
-        if self.states.contains_key(E::entity_name()) {
-            return;
-        }
-
-        let entry = self.states.entry(E::entity_name());
+        //  2a. this entity has no idmap yet
+        //  2b. this entity has an idmap already
+
+        use std::collections::hash_map::Entry;
+        match entry {
+            Entry::Occupied(mut entry) => {
+                if entry.get().has_idmap && is_idmap {
+                    panic!("duplicate IDMap for entity {}", E::entity_name())
+                } else if is_idmap {
+                    entry.get_mut().has_idmap = true;
+                }
+                return;
+            },
+            _ => (),
+        };
 
         let entry = entry.or_insert_with(EntityState::build::<E>);
+        entry.has_idmap = is_idmap;
         // sanity-check
         if entry.typeid != std::any::TypeId::of::<E>() {
             panic!("Identical entity name but different typeid!");
@@ -200,3 +215,9 @@ impl EntityVisitor for EntityStateContainer {
         E::accept_part_visitor(&mut RecursiveVisitor(self, Default::default()));
     }
 }
+
+impl EntityVisitor for EntityStateContainer {
+    fn visit<E: Entity>(&mut self) {
+        self.handle_visit::<E>(false);
+    }
+}

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

@@ -1,7 +1,7 @@
 use crate::schema::IDMap;
 
 #[derive(Clone, microrm_macros::Entity)]
-pub struct Meta {
+pub struct MicrormMeta {
     /// Metadata key-value key
     #[key]
     pub key: String,
@@ -11,5 +11,5 @@ pub struct Meta {
 
 #[derive(microrm_macros::Database)]
 pub struct MetadataDB {
-    pub metastore: IDMap<Meta>,
+    pub metastore: IDMap<MicrormMeta>,
 }

+ 43 - 0
microrm/tests/conflicts.rs

@@ -0,0 +1,43 @@
+use microrm::prelude::*;
+use test_log::test;
+
+mod common;
+
+// this used to conflict with the microrm metadata table
+#[derive(Entity)]
+struct Meta {}
+
+#[derive(Database)]
+struct MetaConflictDB {
+    pub meta_list: microrm::IDMap<Meta>,
+}
+
+#[test]
+fn builtin_name_conflict() {
+    let _db: MetaConflictDB = common::open_test_db!();
+}
+
+#[derive(Entity)]
+struct OrdinaryEntity {
+    v: usize,
+}
+
+#[derive(Database)]
+struct ConflictingDB {
+    pub map1: microrm::IDMap<OrdinaryEntity>,
+    pub map2: microrm::IDMap<OrdinaryEntity>,
+}
+
+#[test]
+#[should_panic(expected = "duplicate IDMap for entity ordinary_entity")]
+fn multimap_conflict() {
+    let db: ConflictingDB = common::open_test_db!();
+
+    assert_eq!(db.map1.count().unwrap(), 0);
+    assert_eq!(db.map2.count().unwrap(), 0);
+
+    db.map1.insert(OrdinaryEntity { v: 0 }).unwrap();
+
+    assert_eq!(db.map1.count().unwrap(), 1);
+    assert_eq!(db.map2.count().unwrap(), 0);
+}