[migration] don't handle data deletion anymore on schema changes
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 24 Sep 2015 11:26:11 +0200
changeset 10921 977def81780a
parent 10920 4b0443afbb3d
child 10922 7d01c8c675a0
[migration] don't handle data deletion anymore on schema changes In most cases when we want to drop some entity/relation type, we don't care whether hooks are called on their deletion. There is even low chances that some hooks still exists, based on an old version of the schema. Last but not least, this is horribly inefficient. So this should be clearly documented and handled by application's programmer if desired. This patch removes unnecessary deletion (because table or column will be later dropped) and reimplements the case of partial deletion (only one relation definition among several, hence the database structure isn't modified) using sql. Only one test regarding deletion of inlined relation def is added as other cases seem to be covered by existing tests. Closes #7023315
hooks/syncschema.py
server/migractions.py
server/test/unittest_migractions.py
--- a/hooks/syncschema.py	Tue Nov 24 16:01:43 2015 +0100
+++ b/hooks/syncschema.py	Thu Sep 24 11:26:11 2015 +0200
@@ -332,7 +332,7 @@
         self.oldvalues = dict( (attr, getattr(rschema, attr)) for attr in self.values)
         self.rschema.__dict__.update(self.values)
         # then make necessary changes to the system source database
-        if not 'inlined' in self.values:
+        if 'inlined' not in self.values:
             return # nothing to do
         inlined = self.values['inlined']
         # check in-lining is possible when inlined
@@ -608,8 +608,23 @@
                 ptypes = cnx.transaction_data.setdefault('pendingrtypes', set())
                 ptypes.add(rschema.type)
                 DropColumn.get_instance(cnx).add_data((str(rdef.subject), str(rschema)))
+            elif rschema.inlined:
+                cnx.system_sql('UPDATE %s%s SET %s%s=NULL WHERE '
+                               'EXISTS(SELECT 1 FROM entities '
+                               '       WHERE eid=%s%s AND type=%%(to_etype)s)'
+                               % (SQL_PREFIX, rdef.subject, SQL_PREFIX, rdef.rtype,
+                                  SQL_PREFIX, rdef.rtype),
+                               {'to_etype': rdef.object.type})
         elif lastrel:
             DropRelationTable(cnx, str(rschema))
+        else:
+            cnx.system_sql('DELETE FROM %s_relation WHERE '
+                           'EXISTS(SELECT 1 FROM entities '
+                           '       WHERE eid=eid_from AND type=%%(from_etype)s)'
+                           ' AND EXISTS(SELECT 1 FROM entities '
+                           '       WHERE eid=eid_to AND type=%%(to_etype)s)'
+                           % rschema,
+                           {'from_etype': rdef.subject.type, 'to_etype': rdef.object.type})
         # then update the in-memory schema
         if rdef.subject not in ETYPE_NAME_MAP and rdef.object not in ETYPE_NAME_MAP:
             rschema.del_relation_def(rdef.subject, rdef.object)
@@ -969,7 +984,6 @@
             raise validation_error(self.entity, {None: _("can't be deleted")})
         # delete every entities of this type
         if name not in ETYPE_NAME_MAP:
-            self._cw.execute('DELETE %s X' % name)
             MemSchemaCWETypeDel(self._cw, etype=name)
         DropTable(self._cw, table=SQL_PREFIX + name)
 
@@ -1152,10 +1166,6 @@
         else:
             rdeftype = 'CWRelation'
             pendingrdefs.add((subjschema, rschema, objschema))
-            if not (cnx.deleted_in_transaction(subjschema.eid) or
-                    cnx.deleted_in_transaction(objschema.eid)):
-                cnx.execute('DELETE X %s Y WHERE X is %s, Y is %s'
-                            % (rschema, subjschema, objschema))
         RDefDelOp(cnx, rdef=rdef)
 
 
--- a/server/migractions.py	Tue Nov 24 16:01:43 2015 +0100
+++ b/server/migractions.py	Thu Sep 24 11:26:11 2015 +0200
@@ -901,9 +901,11 @@
             self.commit()
 
     def cmd_drop_entity_type(self, etype, commit=True):
-        """unregister an existing entity type
+        """Drop an existing entity type.
 
-        This will trigger deletion of necessary relation types and definitions
+        This will trigger deletion of necessary relation types and definitions.
+        Note that existing entities of the given type will be deleted without
+        any hooks called.
         """
         # XXX what if we delete an entity type which is specialized by other types
         # unregister the entity from CWEType
@@ -1062,7 +1064,11 @@
             self.commit()
 
     def cmd_drop_relation_type(self, rtype, commit=True):
-        """unregister an existing relation type"""
+        """Drop an existing relation type.
+
+        Note that existing relations of the given type will be deleted without
+        any hooks called.
+        """
         self.rqlexec('DELETE CWRType X WHERE X name %r' % rtype,
                      ask_confirm=self.verbosity>=2)
         self.rqlexec('DELETE CWComputedRType X WHERE X name %r' % rtype,
@@ -1122,7 +1128,11 @@
         return rdef
 
     def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True):
-        """unregister an existing relation definition"""
+        """Drop an existing relation definition.
+
+        Note that existing relations of the given definition will be deleted
+        without any hooks called.
+        """
         rschema = self.repo.schema.rschema(rtype)
         if rschema.rule:
             raise ExecutionError('Cannot drop a relation definition for a '
--- a/server/test/unittest_migractions.py	Tue Nov 24 16:01:43 2015 +0100
+++ b/server/test/unittest_migractions.py	Thu Sep 24 11:26:11 2015 +0200
@@ -733,6 +733,12 @@
             self.assertNotIn('%secrit_par' % SQL_PREFIX,
                              self.table_schema(mh, '%sPersonne' % SQL_PREFIX))
 
+    def test_drop_inlined_rdef_delete_data(self):
+        with self.mh() as (cnx, mh):
+            note = mh.cmd_create_entity('Note', ecrit_par=cnx.user.eid)
+            mh.commit()
+            mh.drop_relation_definition('Note', 'ecrit_par', 'CWUser')
+            self.assertFalse(mh.sqlexec('SELECT * FROM cw_Note WHERE cw_ecrit_par IS NOT NULL'))
 
 class MigrationCommandsComputedTC(MigrationTC):
     """ Unit tests for computed relations and attributes