]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Ensure all cursor self.handle_exception() calls are covered
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 12 Oct 2020 14:16:25 +0000 (10:16 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 12 Oct 2020 16:31:10 +0000 (12:31 -0400)
Fixes: #5642
Change-Id: I07a77483e6e2ec593d87d3d3467a4339c5f77a26

lib/sqlalchemy/engine/cursor.py
test/sql/test_resultset.py

index 6c4a756c9a87b6b8734b81fbf04ab56ca120f8eb..b06c622e060c9ab4220570a7d71b82309e034a07 100644 (file)
@@ -1079,7 +1079,7 @@ class BufferedRowCursorFetchStrategy(CursorFetchStrategy):
                 try:
                     result._soft_close(hard=hard_close)
                 except BaseException as e:
-                    self.handle_exception(result, e)
+                    self.handle_exception(result, dbapi_cursor, e)
                 return None
         return self._rowbuffer.popleft()
 
@@ -1093,7 +1093,7 @@ class BufferedRowCursorFetchStrategy(CursorFetchStrategy):
             try:
                 buf.extend(dbapi_cursor.fetchmany(size - lb))
             except BaseException as e:
-                self.handle_exception(result, e)
+                self.handle_exception(result, dbapi_cursor, e)
 
         result = buf[0:size]
         self._rowbuffer = collections.deque(buf[size:])
index 7d626654170a0407001ad57983ca62d12b68b591..2deec3f8296095546b5180b9c6a5e70e0f8eb66c 100644 (file)
@@ -44,6 +44,7 @@ from sqlalchemy.testing import in_
 from sqlalchemy.testing import is_
 from sqlalchemy.testing import is_true
 from sqlalchemy.testing import le_
+from sqlalchemy.testing import mock
 from sqlalchemy.testing import ne_
 from sqlalchemy.testing import not_in
 from sqlalchemy.testing.mock import Mock
@@ -2486,6 +2487,73 @@ class AlternateCursorResultTest(fixtures.TablesTest):
         result.fetchmany(5)
         eq_(len(result.cursor_strategy._rowbuffer), 296)
 
+        self._test_result_processor(
+            _cursor.BufferedRowCursorFetchStrategy, False
+        )
+
+    @testing.combinations(
+        _cursor.CursorFetchStrategy,
+        _cursor.BufferedRowCursorFetchStrategy,
+        # does not handle error in fetch
+        # _cursor.FullyBufferedCursorFetchStrategy,
+        argnames="strategy_cls",
+    )
+    @testing.combinations(
+        "fetchone",
+        "fetchmany",
+        "fetchmany_w_num",
+        "fetchall",
+        argnames="method_name",
+    )
+    def test_handle_error_in_fetch(self, strategy_cls, method_name):
+        class cursor(object):
+            def raise_(self):
+                raise IOError("random non-DBAPI error during cursor operation")
+
+            def fetchone(self):
+                self.raise_()
+
+            def fetchmany(self, num=None):
+                self.raise_()
+
+            def fetchall(self):
+                self.raise_()
+
+            def close(self):
+                self.raise_()
+
+        with self._proxy_fixture(strategy_cls):
+            with self.engine.connect() as conn:
+                r = conn.execute(select(self.table))
+                assert isinstance(r.cursor_strategy, strategy_cls)
+                with mock.patch.object(r, "cursor", cursor()):
+
+                    with testing.expect_raises_message(
+                        IOError, "random non-DBAPI"
+                    ):
+                        if method_name == "fetchmany_w_num":
+                            r.fetchmany(10)
+                        else:
+                            getattr(r, method_name)()
+                            getattr(r, method_name)()
+
+                r.close()
+
+    def test_buffered_row_close_error_during_fetchone(self):
+        def raise_(**kw):
+            raise IOError("random non-DBAPI error during cursor operation")
+
+        with self._proxy_fixture(_cursor.BufferedRowCursorFetchStrategy):
+            with self.engine.connect() as conn:
+                r = conn.execute(select(self.table).limit(1))
+
+                r.fetchone()
+                with mock.patch.object(
+                    r, "_soft_close", raise_
+                ), testing.expect_raises_message(IOError, "random non-DBAPI"):
+                    r.first()
+                r.close()
+
 
 class MergeCursorResultTest(fixtures.TablesTest):
     __backend__ = True