mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-27 19:15:05 -04:00
Check for object was expunged before restoring after pk switch + rollback
Fixed bug where an object that is expunged during a rollback of
a nested or subtransaction which also had its primary key mutated
would not be correctly removed from the session, causing subsequent
issues in using the session.
Change-Id: I57e2888902015d67ee11857e44382818f1d2f8bc
Fixes: #4151
(cherry picked from commit 5811276bb7)
This commit is contained in:
+9
@@ -0,0 +1,9 @@
|
||||
.. change::
|
||||
:tags: bug, orm
|
||||
:tickets: 4151
|
||||
:versions: 1.2.1
|
||||
|
||||
Fixed bug where an object that is expunged during a rollback of
|
||||
a nested or subtransaction which also had its primary key mutated
|
||||
would not be correctly removed from the session, causing subsequent
|
||||
issues in using the session.
|
||||
@@ -339,14 +339,20 @@ class SessionTransaction(object):
|
||||
"""
|
||||
assert self._is_transaction_boundary
|
||||
|
||||
self.session._expunge_states(
|
||||
set(self._new).union(self.session._new),
|
||||
to_transient=True)
|
||||
to_expunge = set(self._new).union(self.session._new)
|
||||
self.session._expunge_states(to_expunge, to_transient=True)
|
||||
|
||||
for s, (oldkey, newkey) in self._key_switches.items():
|
||||
# we probably can do this conditionally based on
|
||||
# if we expunged or not, but safe_discard does that anyway
|
||||
self.session.identity_map.safe_discard(s)
|
||||
|
||||
# restore the old key
|
||||
s.key = oldkey
|
||||
self.session.identity_map.replace(s)
|
||||
|
||||
# now restore the object, but only if we didn't expunge
|
||||
if s not in to_expunge:
|
||||
self.session.identity_map.replace(s)
|
||||
|
||||
for s in set(self._deleted).union(self.session._deleted):
|
||||
self.session._update_impl(s, revert_deletion=True)
|
||||
|
||||
@@ -1600,6 +1600,34 @@ class NaturalPKRollbackTest(fixtures.MappedTest):
|
||||
assert s.identity_map[(User, ('u1',))] is u1
|
||||
assert s.identity_map[(User, ('u2',))] is u2
|
||||
|
||||
@testing.requires.savepoints
|
||||
def test_key_replaced_by_update_nested(self):
|
||||
users, User = self.tables.users, self.classes.User
|
||||
|
||||
mapper(User, users)
|
||||
|
||||
u1 = User(name='u1')
|
||||
|
||||
s = Session()
|
||||
s.add(u1)
|
||||
s.commit()
|
||||
|
||||
with s.begin_nested():
|
||||
u2 = User(name='u2')
|
||||
s.add(u2)
|
||||
s.flush()
|
||||
|
||||
u2.name = 'u3'
|
||||
|
||||
s.rollback()
|
||||
|
||||
assert u1 in s
|
||||
assert u2 not in s
|
||||
|
||||
u1.name = 'u5'
|
||||
|
||||
s.commit()
|
||||
|
||||
def test_multiple_key_replaced_by_update(self):
|
||||
users, User = self.tables.users, self.classes.User
|
||||
|
||||
|
||||
Reference in New Issue
Block a user