Avoid to update inlined relation column to NULL when deleting entities 3.26
authorPhilippe Pepiot <philippe.pepiot@logilab.fr>
Wed, 11 Sep 2019 11:56:43 +0200
branch3.26
changeset 12726 1a2c7d6397ec
parent 12725 0481ece35cb2
child 12727 1853c5e13154
Avoid to update inlined relation column to NULL when deleting entities When deleting entities, cubicweb run a rql DELETE on all relations to trigger hooks. For an inlined relation this also mean set the column to NULL. This operation may fail if there's additional constraints on the column. Also this is a weird and useless behavior since deleting the entity row will by definition delete the relation. We still doesn't handle the case where both subject and object are going to be deleted because rows need to be deleted in a particular order that cubicweb doesn't handle. Add a test checking UPDATE does not occur but hooks are correctly called. Closes #17236690
cubicweb/server/sources/native.py
cubicweb/server/test/unittest_repository.py
--- a/cubicweb/server/sources/native.py	Wed Sep 11 11:37:05 2019 +0200
+++ b/cubicweb/server/sources/native.py	Wed Sep 11 11:56:43 2019 +0200
@@ -653,6 +653,18 @@
     def delete_relation(self, cnx, subject, rtype, object):
         """delete a relation from the source"""
         rschema = self.schema.rschema(rtype)
+        # we should not issue UPDATE on inlined relations when subject is going
+        # to be deleted.
+        # Unless object is also going to be deleted and is not equal to the
+        # subject, in this case DELETE should occur in a order which cubicweb
+        # doesn't handle (yet). For example
+        # cubicweb/server/test/unittest_migractions.py::MigrationCommandsTC::test_add_drop_entity_type
+        # trigger such case.
+        if (
+            rschema.inlined and cnx.deleted_in_transaction(subject)
+            and (subject == object or not cnx.deleted_in_transaction(object))
+        ):
+            return
         self._delete_relation(cnx, subject, rtype, object, rschema.inlined)
         if cnx.ertype_supports_undo(rtype):
             self._record_tx_action(cnx, 'tx_relation_actions', u'R',
--- a/cubicweb/server/test/unittest_repository.py	Wed Sep 11 11:37:05 2019 +0200
+++ b/cubicweb/server/test/unittest_repository.py	Wed Sep 11 11:56:43 2019 +0200
@@ -322,6 +322,58 @@
             cnx.commit()
             self.assertEqual(bk.title, 'root')
 
+    def test_delete_entity_with_inlined_relation(self):
+        """Test deletion of entity with inlined relations.
+
+        In this case, inlined relation column should not be deleted (= e.g. not
+        updated to NULL), but hooks before_delete_relation and
+        afer_delete_relation must be called.
+        """
+        class OnDeleteInlined(Hook):
+            __regid__ = 'on_delete_inlined'
+            __select__ = Hook.__select__ & hook.match_rtype('personne_inlined')
+            events = ('before_delete_relation', 'after_delete_relation')
+            CALLED = []
+
+            def __call__(self):
+                OnDeleteInlined.CALLED.append(self.event)
+
+        with self.admin_access.cnx() as cnx:
+            # allow only one null on cw_personne_inlined column
+            cnx.system_sql(
+                'CREATE UNIQUE INDEX test_composite_idx ON cw_personne(true) '
+                'WHERE cw_personne_inlined is NULL')
+            cnx.commit()
+
+            # deletion of p1 should not set personne_inlined to NULL (otherwise
+            # the unique index will raise)
+            p0 = cnx.create_entity('Personne', nom=u'p0')
+            p1 = cnx.create_entity('Personne', nom=u'p1', personne_inlined=p0)
+            cnx.commit()
+            with self.temporary_appobjects(OnDeleteInlined):
+                cnx.entity_from_eid(p1.eid).cw_delete()
+            assert OnDeleteInlined.CALLED == [
+                'before_delete_relation', 'after_delete_relation']
+            cnx.commit()
+
+            # XXX: This case is not handled because entities need to be deleted
+            # in a specific order
+            # p1 = cnx.create_entity('Personne', nom=u'p1', personne_inlined=p0)
+            # cnx.commit()
+            # cnx.execute('DELETE Personne X WHERE X eid IN ({}, {})'.format(p1.eid, p0.eid))
+            # cnx.commit()
+            cnx.entity_from_eid(p0.eid).cw_delete()
+            cnx.commit()
+
+            # deletion of p1 should not set personne_inlined to NULL
+            p1 = cnx.create_entity('Personne', nom=u'p1')
+            p1.cw_set(personne_inlined=p1)
+            p0 = cnx.create_entity('Personne', nom=u'p0')
+            cnx.commit()
+            cnx.entity_from_eid(p1.eid).cw_delete()
+            cnx.commit()
+
+
 class SchemaDeserialTC(CubicWebTC):
 
     appid = 'data-schemaserial'