]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-44092: Don't reset statements/cursors before rollback (GH-26026)
authorErlend Egeberg Aasland <erlend.aasland@innova.no>
Mon, 3 Jan 2022 19:02:39 +0000 (20:02 +0100)
committerGitHub <noreply@github.com>
Mon, 3 Jan 2022 19:02:39 +0000 (19:02 +0000)
In SQLite versions pre 3.7.11, pending statements would block a rollback.  This is no longer the case, so remove the workaround.

Doc/whatsnew/3.11.rst
Lib/test/test_sqlite3/test_regression.py
Lib/test/test_sqlite3/test_transactions.py
Misc/NEWS.d/next/Library/2021-05-19-12-35-49.bpo-44092.hiSlI5.rst [new file with mode: 0644]
Modules/_sqlite/connection.c

index faa63a93895a20648a5ec10f581731b328c7a65d..4ddca744720f5a2901b0d949ed0e90aebf4c282e 100644 (file)
@@ -302,6 +302,11 @@ sys
   (Contributed by Irit Katriel in :issue:`45711`.)
 
 
+* Fetch across rollback no longer raises :exc:`~sqlite3.InterfaceError`.
+  Instead we leave it to the SQLite library to handle these cases.
+  (Contributed by Erlend E. Aasland in :issue:`44092`.)
+
+
 threading
 ---------
 
index eb34069d3e5544bff3e0aa335a1a25966c585cd1..b527053039b8a1b7bcd231cc561e414a66abe8ff 100644 (file)
@@ -231,28 +231,6 @@ class RegressionTests(unittest.TestCase):
         with self.assertRaises(sqlite.ProgrammingError):
             cur = con.cursor()
 
-    def test_cursor_registration(self):
-        """
-        Verifies that subclassed cursor classes are correctly registered with
-        the connection object, too.  (fetch-across-rollback problem)
-        """
-        class Connection(sqlite.Connection):
-            def cursor(self):
-                return Cursor(self)
-
-        class Cursor(sqlite.Cursor):
-            def __init__(self, con):
-                sqlite.Cursor.__init__(self, con)
-
-        con = Connection(":memory:")
-        cur = con.cursor()
-        cur.execute("create table foo(x)")
-        cur.executemany("insert into foo(x) values (?)", [(3,), (4,), (5,)])
-        cur.execute("select x from foo")
-        con.rollback()
-        with self.assertRaises(sqlite.InterfaceError):
-            cur.fetchall()
-
     def test_auto_commit(self):
         """
         Verifies that creating a connection in autocommit mode works.
index 3efa2c1e604ff7ac39578ceec93df43ec69e331b..55cf8f1fdfce38cbfe4a7ef9f68f577f1d849ce1 100644 (file)
@@ -131,10 +131,7 @@ class TransactionTests(unittest.TestCase):
         self.con1.commit()
 
     def test_rollback_cursor_consistency(self):
-        """
-        Checks if cursors on the connection are set into a "reset" state
-        when a rollback is done on the connection.
-        """
+        """Check that cursors behave correctly after rollback."""
         con = sqlite.connect(":memory:")
         cur = con.cursor()
         cur.execute("create table test(x)")
@@ -142,8 +139,44 @@ class TransactionTests(unittest.TestCase):
         cur.execute("select 1 union select 2 union select 3")
 
         con.rollback()
-        with self.assertRaises(sqlite.InterfaceError):
-            cur.fetchall()
+        self.assertEqual(cur.fetchall(), [(1,), (2,), (3,)])
+
+
+class RollbackTests(unittest.TestCase):
+    """bpo-44092: sqlite3 now leaves it to SQLite to resolve rollback issues"""
+
+    def setUp(self):
+        self.con = sqlite.connect(":memory:")
+        self.cur1 = self.con.cursor()
+        self.cur2 = self.con.cursor()
+        with self.con:
+            self.con.execute("create table t(c)");
+            self.con.executemany("insert into t values(?)", [(0,), (1,), (2,)])
+        self.cur1.execute("begin transaction")
+        select = "select c from t"
+        self.cur1.execute(select)
+        self.con.rollback()
+        self.res = self.cur2.execute(select)  # Reusing stmt from cache
+
+    def tearDown(self):
+        self.con.close()
+
+    def _check_rows(self):
+        for i, row in enumerate(self.res):
+            self.assertEqual(row[0], i)
+
+    def test_no_duplicate_rows_after_rollback_del_cursor(self):
+        del self.cur1
+        self._check_rows()
+
+    def test_no_duplicate_rows_after_rollback_close_cursor(self):
+        self.cur1.close()
+        self._check_rows()
+
+    def test_no_duplicate_rows_after_rollback_new_query(self):
+        self.cur1.execute("select c from t where c = 1")
+        self._check_rows()
+
 
 
 class SpecialCommandTests(unittest.TestCase):
diff --git a/Misc/NEWS.d/next/Library/2021-05-19-12-35-49.bpo-44092.hiSlI5.rst b/Misc/NEWS.d/next/Library/2021-05-19-12-35-49.bpo-44092.hiSlI5.rst
new file mode 100644 (file)
index 0000000..6777781
--- /dev/null
@@ -0,0 +1,3 @@
+Fetch across rollback no longer raises :exc:`~sqlite3.InterfaceError`. Instead
+we leave it to the SQLite library to handle these cases.
+Patch by Erlend E. Aasland.
index 4f0baa649e1d0535b74d2b9cccb2fd2019b9d063..02f4ac46b7c356ba1d0d9bc9e171a06fc548a4c1 100644 (file)
@@ -274,28 +274,6 @@ pysqlite_connection_init_impl(pysqlite_Connection *self,
     return 0;
 }
 
-static void
-pysqlite_do_all_statements(pysqlite_Connection *self)
-{
-    // Reset all statements
-    sqlite3_stmt *stmt = NULL;
-    while ((stmt = sqlite3_next_stmt(self->db, stmt))) {
-        if (sqlite3_stmt_busy(stmt)) {
-            (void)sqlite3_reset(stmt);
-        }
-    }
-
-    // Reset all cursors
-    for (int i = 0; i < PyList_Size(self->cursors); i++) {
-        PyObject *weakref = PyList_GetItem(self->cursors, i);
-        PyObject *object = PyWeakref_GetObject(weakref);
-        if (object != Py_None) {
-            pysqlite_Cursor *cursor = (pysqlite_Cursor *)object;
-            cursor->reset = 1;
-        }
-    }
-}
-
 #define VISIT_CALLBACK_CONTEXT(ctx) \
 do {                                \
     if (ctx) {                      \
@@ -549,8 +527,6 @@ pysqlite_connection_rollback_impl(pysqlite_Connection *self)
     }
 
     if (!sqlite3_get_autocommit(self->db)) {
-        pysqlite_do_all_statements(self);
-
         int rc;
 
         Py_BEGIN_ALLOW_THREADS