- A rare case which occurs when a :meth:.Session.rollback fails in the

scope of a :meth:`.Session.flush` operation that's raising an
exception, as has been observed in some MySQL SAVEPOINT cases, prevents
the original  database exception from being observed when it was
emitted during  flush, but only on Py2K because Py2K does not support
exception  chaining; on Py3K the originating exception is chained.  As
a workaround, a warning is emitted in this specific case showing at
least the string message of the original database error before we
proceed to raise  the rollback-originating exception.
fixes #2696
This commit is contained in:
Mike Bayer
2015-11-19 15:45:17 -05:00
parent 1dc805dd4d
commit a6fe4dc0c8
4 changed files with 55 additions and 3 deletions
+15
View File
@@ -18,6 +18,21 @@
.. changelog::
:version: 1.0.10
.. change::
:tags: bug, orm
:versions: 1.1.0b1
:tickets: 2696
A rare case which occurs when a :meth:`.Session.rollback` fails in the
scope of a :meth:`.Session.flush` operation that's raising an
exception, as has been observed in some MySQL SAVEPOINT cases, prevents
the original database exception from being observed when it was
emitted during flush, but only on Py2K because Py2K does not support
exception chaining; on Py3K the originating exception is chained. As
a workaround, a warning is emitted in this specific case showing at
least the string message of the original database error before we
proceed to raise the rollback-originating exception.
.. change::
:tags: bug, postgresql
:versions: 1.1.0b1
+14 -2
View File
@@ -408,11 +408,23 @@ class SessionTransaction(object):
for subtransaction in stx._iterate_parents(upto=self):
subtransaction.close()
if _capture_exception:
captured_exception = sys.exc_info()[1]
boundary = self
if self._state in (ACTIVE, PREPARED):
for transaction in self._iterate_parents():
if transaction._parent is None or transaction.nested:
transaction._rollback_impl()
try:
transaction._rollback_impl()
except Exception:
if _capture_exception:
util.warn(
"An exception raised during a Session "
"persistence operation cannot be raised "
"due to an additional ROLLBACK exception; "
"the exception is: %s" % captured_exception)
raise
transaction._state = DEACTIVE
boundary = transaction
break
@@ -434,7 +446,7 @@ class SessionTransaction(object):
self.close()
if self._parent and _capture_exception:
self._parent._rollback_exception = sys.exc_info()[1]
self._parent._rollback_exception = captured_exception
sess.dispatch.after_soft_rollback(sess, self)
+1 -1
View File
@@ -121,7 +121,7 @@ def uses_deprecated(*messages):
def _expect_warnings(exc_cls, messages, regex=True, assert_=True):
if regex:
filters = [re.compile(msg, re.I) for msg in messages]
filters = [re.compile(msg, re.I | re.S) for msg in messages]
else:
filters = messages
+25
View File
@@ -657,6 +657,31 @@ class SessionTransactionTest(FixtureTest):
assert session.transaction is not None, \
'autocommit=False should start a new transaction'
@testing.requires.savepoints
def test_report_primary_error_when_rollback_fails(self):
User, users = self.classes.User, self.tables.users
mapper(User, users)
session = Session(testing.db)
with expect_warnings(".*due to an additional ROLLBACK.*INSERT INTO"):
session.begin_nested()
savepoint = session.\
connection()._Connection__transaction._savepoint
# force the savepoint to disappear
session.execute("RELEASE SAVEPOINT %s" % savepoint)
# now do a broken flush
session.add_all([User(id=1), User(id=1)])
assert_raises_message(
sa_exc.DBAPIError,
"ROLLBACK TO SAVEPOINT ",
session.flush
)
class _LocalFixture(FixtureTest):
run_setup_mappers = 'once'