summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2018-10-01 14:00:39 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2018-10-01 14:00:39 -0400
commit313879d6adedd4fd2a07a4ec30530766f3016885 (patch)
treed4188aa5665712ead3a97c75737c766f258a9474
parent29d54ab69b689c2bc4b9be8273f4c0a96e37153f (diff)
downloadsqlalchemy-313879d6adedd4fd2a07a4ec30530766f3016885.tar.gz
Add test and retroactive changelog for issue 4040
Fixes: #4040 Change-Id: I707c1cd2708a37102ad8184bec21be35cb6242d7
-rw-r--r--doc/build/changelog/changelog_12.rst14
-rw-r--r--lib/sqlalchemy/orm/session.py3
-rw-r--r--lib/sqlalchemy/orm/unitofwork.py4
-rw-r--r--test/orm/test_cascade.py24
4 files changed, 44 insertions, 1 deletions
diff --git a/doc/build/changelog/changelog_12.rst b/doc/build/changelog/changelog_12.rst
index 8e471dfc3..3b6838938 100644
--- a/doc/build/changelog/changelog_12.rst
+++ b/doc/build/changelog/changelog_12.rst
@@ -1471,6 +1471,20 @@
.. 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
Fixed bug in :ref:`change_3948` which prevented "selectin" and
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 2c7fd86d2..6c5553939 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 669fed618..ae5cee8d2 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 55467db4b..fecf3dcc3 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,