.. 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
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
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)
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
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'