From: Mike Bayer Date: Sun, 26 Mar 2023 16:30:35 +0000 (-0400) Subject: disable raise sql for the delete cascade X-Git-Tag: rel_2_0_8~12^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9e85abb221af13057621ea066af4e15b2f1dfb03;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git disable raise sql for the delete cascade 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 --- diff --git a/doc/build/changelog/unreleased_20/9549.rst b/doc/build/changelog/unreleased_20/9549.rst new file mode 100644 index 0000000000..3bb866a489 --- /dev/null +++ b/doc/build/changelog/unreleased_20/9549.rst @@ -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. diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index 05fa17a968..8d9f3c644a 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -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_) diff --git a/test/orm/test_cascade.py b/test/orm/test_cascade.py index a461647037..8a545b03b1 100644 --- a/test/orm/test_cascade.py +++ b/test/orm/test_cascade.py @@ -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