]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- A rare case which occurs when a :meth:`.Session.rollback` fails in the
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 19 Nov 2015 20:45:17 +0000 (15:45 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 19 Nov 2015 20:45:17 +0000 (15:45 -0500)
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

doc/build/changelog/changelog_10.rst
lib/sqlalchemy/orm/session.py
lib/sqlalchemy/testing/assertions.py
test/orm/test_transaction.py

index c800647a6972a6bb8a3492911f62598819d8b631..40a251a222cc77d1c439486a1e4cec605833b200 100644 (file)
 .. 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
index c726443f65e2983630deecc905affef11c99f197..4272d7d785bdb06abfd5c92043d045c82be63af6 100644 (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)
 
index 21dc3e71aca609c383f1d84448e9be5f51c49acd..63667654d44f020bac610ea0426b8c9e4adc8abf 100644 (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
 
index 73c6b977ac681fd40287d408937fc8d21af49f05..3ec6b9c09371f055e5f8d0e619117e283bb6c067 100644 (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'