]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
disable raise sql for the delete cascade
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 26 Mar 2023 16:30:35 +0000 (12:30 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 28 Mar 2023 13:59:51 +0000 (09:59 -0400)
Towards maintaining consistency with unit-of-work changes made for
:ticket:`5984` and :ticket:`8862`, both of which disable "lazy='raise'"
handling within :class:`_orm.Session` processes that aren't triggered by
attribute access, the :meth:`_orm.Session.delete` method will now also
disable "lazy='raise'" handling when it traverses relationship paths in
order to process the "delete" and "delete-orphan" cascade rules.
Previously, there was no easy way to generically call
:meth:`_orm.Session.delete` on an object that had "lazy='raise'" set up
such that only the necessary relationships would be loaded. As
"lazy='raise'" is primarily intended to catch SQL loading that emits on
attribute access, :meth:`_orm.Session.delete` is now made to behave like
other :class:`_orm.Session` methods including :meth:`_orm.Session.merge` as
well as :meth:`_orm.Session.flush` along with autoflush.

Fixes: #9549
Change-Id: Ie049e66ce2bd35900eae4af0e9b795633303ca63

doc/build/changelog/unreleased_20/9549.rst [new file with mode: 0644]
lib/sqlalchemy/orm/relationships.py
test/orm/test_cascade.py

diff --git a/doc/build/changelog/unreleased_20/9549.rst b/doc/build/changelog/unreleased_20/9549.rst
new file mode 100644 (file)
index 0000000..3bb866a
--- /dev/null
@@ -0,0 +1,17 @@
+.. change::
+    :tags: bug, orm
+    :tickets: 9549
+
+    Towards maintaining consistency with unit-of-work changes made for
+    :ticket:`5984` and :ticket:`8862`, both of which disable "lazy='raise'"
+    handling within :class:`_orm.Session` processes that aren't triggered by
+    attribute access, the :meth:`_orm.Session.delete` method will now also
+    disable "lazy='raise'" handling when it traverses relationship paths in
+    order to process the "delete" and "delete-orphan" cascade rules.
+    Previously, there was no easy way to generically call
+    :meth:`_orm.Session.delete` on an object that had "lazy='raise'" set up
+    such that only the necessary relationships would be loaded. As
+    "lazy='raise'" is primarily intended to catch SQL loading that emits on
+    attribute access, :meth:`_orm.Session.delete` is now made to behave like
+    other :class:`_orm.Session` methods including :meth:`_orm.Session.merge` as
+    well as :meth:`_orm.Session.flush` along with autoflush.
index 05fa17a968263e8bf6b6b2744e5196a95c51576d..8d9f3c644ac65f05ee45985c246e8d981cd72bef 100644 (file)
@@ -1495,7 +1495,7 @@ class RelationshipProperty(
         if type_ != "delete" or self.passive_deletes:
             passive = PassiveFlag.PASSIVE_NO_INITIALIZE
         else:
-            passive = PassiveFlag.PASSIVE_OFF
+            passive = PassiveFlag.PASSIVE_OFF | PassiveFlag.NO_RAISE
 
         if type_ == "save-update":
             tuples = state.manager[self.key].impl.get_all_pending(state, dict_)
index a461647037d9d253669ada44be6e7f545942f944..8a545b03b125c19791778c621c4386eb6e4ecc80 100644 (file)
@@ -63,10 +63,10 @@ class CascadeArgTest(fixtures.MappedTest):
 
     @classmethod
     def setup_classes(cls):
-        class User(cls.Basic):
+        class User(cls.Comparable):
             pass
 
-        class Address(cls.Basic):
+        class Address(cls.Comparable):
             pass
 
     def test_delete_with_passive_deletes_all(self):
@@ -173,6 +173,78 @@ class CascadeArgTest(fixtures.MappedTest):
         eq_(rel.cascade, {"save-update", "merge", "expunge"})
 
 
+class CasadeWithRaiseloadTest(fixtures.MappedTest):
+    @classmethod
+    def define_tables(cls, metadata):
+        Table(
+            "users",
+            metadata,
+            Column(
+                "id", Integer, primary_key=True, test_needs_autoincrement=True
+            ),
+            Column("name", String(30), nullable=False),
+        )
+        Table(
+            "addresses",
+            metadata,
+            Column(
+                "id", Integer, primary_key=True, test_needs_autoincrement=True
+            ),
+            Column("user_id", Integer, ForeignKey("users.id")),
+            Column("email_address", String(50), nullable=False),
+        )
+
+    @classmethod
+    def setup_classes(cls):
+        class User(cls.Comparable):
+            pass
+
+        class Address(cls.Comparable):
+            pass
+
+    def test_delete_skips_lazy_raise(self):
+        User, Address = self.classes.User, self.classes.Address
+        users, addresses = self.tables.users, self.tables.addresses
+
+        self.mapper_registry.map_imperatively(
+            User,
+            users,
+            properties={
+                "addresses": relationship(
+                    Address, cascade="all, delete-orphan", lazy="raise"
+                )
+            },
+        )
+        self.mapper_registry.map_imperatively(Address, addresses)
+
+        self.mapper_registry.metadata.create_all(testing.db)
+
+        sess = fixture_session()
+        u1 = User(
+            name="u1",
+            addresses=[
+                Address(email_address="e1"),
+                Address(email_address="e2"),
+            ],
+        )
+        sess.add(u1)
+        sess.commit()
+
+        eq_(
+            sess.scalars(
+                select(Address).order_by(Address.email_address)
+            ).all(),
+            [Address(email_address="e1"), Address(email_address="e2")],
+        )
+
+        sess.close()
+
+        sess.delete(u1)
+        sess.commit()
+
+        eq_(sess.scalars(select(Address)).all(), [])
+
+
 class O2MCascadeDeleteOrphanTest(fixtures.MappedTest):
     run_inserts = None