[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
--- 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