From: Mike Bayer Date: Mon, 1 Oct 2018 18:00:39 +0000 (-0400) Subject: Add test and retroactive changelog for issue 4040 X-Git-Tag: rel_1_3_0b1~59 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=313879d6adedd4fd2a07a4ec30530766f3016885;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Add test and retroactive changelog for issue 4040 Fixes: #4040 Change-Id: I707c1cd2708a37102ad8184bec21be35cb6242d7 --- diff --git a/doc/build/changelog/changelog_12.rst b/doc/build/changelog/changelog_12.rst index 8e471dfc3c..3b6838938d 100644 --- a/doc/build/changelog/changelog_12.rst +++ b/doc/build/changelog/changelog_12.rst @@ -1469,6 +1469,20 @@ attributes, which has the effect of the attribute being undeferred for the next refesh, causing an unexpected load of the attribute. + .. change:: + :tags: bug, orm + :tickets: 4040 + + Fixed bug involving delete-orphan cascade where a related item + that becomes an orphan before the parent object is part of a + session is still tracked as moving into orphan status, which results + in it being expunged from the session rather than being flushed. + + .. note:: This fix was inadvertently merged during the 1.2.0b3 + release and was **not added to the changelog** at that time. + This changelog note was added to the release retroactively as of + version 1.2.13. + .. change:: :tags: bug, orm :tickets: 4026 diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index 2c7fd86d2c..6c55539398 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -2316,7 +2316,8 @@ class Session(_SessionClassMethods): is_persistent_orphan = is_orphan and state.has_identity - if is_orphan and not is_persistent_orphan and state._orphaned_outside_of_session: + if is_orphan and not is_persistent_orphan and \ + state._orphaned_outside_of_session: self._expunge_states([state]) else: _reg = flush_context.register_object( diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index 669fed6180..ae5cee8d2e 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -69,6 +69,10 @@ def track_cascade_events(descriptor, prop): if sess and item_state in sess._new: sess.expunge(item) else: + # the related item may or may not itself be in a + # Session, however the parent for which we are catching + # the event is not in a session, so memoize this on the + # item item_state._orphaned_outside_of_session = True def set_(state, newvalue, oldvalue, initiator): diff --git a/test/orm/test_cascade.py b/test/orm/test_cascade.py index 55467db4b9..fecf3dcc3c 100644 --- a/test/orm/test_cascade.py +++ b/test/orm/test_cascade.py @@ -272,6 +272,30 @@ class O2MCascadeDeleteOrphanTest(fixtures.MappedTest): u.orders.remove(o1) assert o1 not in sess + def test_remove_pending_from_pending_parent(self): + # test issue #4040 + + User, Order = self.classes.User, self.classes.Order + + sess = Session() + + u = User(name='jack') + + o1 = Order() + sess.add(o1) + + # object becomes an orphan, but parent is not in session + u.orders.append(o1) + u.orders.remove(o1) + + sess.add(u) + + assert o1 in sess + + sess.flush() + + assert o1 not in sess + def test_delete(self): User, users, orders, Order = (self.classes.User, self.tables.users,