mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-19 07:02:05 -04:00
- 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:
Vendored
+15
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user