diff options
-rw-r--r-- | Doc/whatsnew/3.11.rst | 5 | ||||
-rw-r--r-- | Lib/test/test_sqlite3/test_regression.py | 22 | ||||
-rw-r--r-- | Lib/test/test_sqlite3/test_transactions.py | 45 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2021-05-19-12-35-49.bpo-44092.hiSlI5.rst | 3 | ||||
-rw-r--r-- | Modules/_sqlite/connection.c | 24 |
5 files changed, 47 insertions, 52 deletions
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index faa63a9..4ddca74 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -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 --------- diff --git a/Lib/test/test_sqlite3/test_regression.py b/Lib/test/test_sqlite3/test_regression.py index eb34069..b527053 100644 --- a/Lib/test/test_sqlite3/test_regression.py +++ b/Lib/test/test_sqlite3/test_regression.py @@ -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. diff --git a/Lib/test/test_sqlite3/test_transactions.py b/Lib/test/test_sqlite3/test_transactions.py index 3efa2c1..55cf8f1 100644 --- a/Lib/test/test_sqlite3/test_transactions.py +++ b/Lib/test/test_sqlite3/test_transactions.py @@ -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 index 0000000..6777781 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-05-19-12-35-49.bpo-44092.hiSlI5.rst @@ -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. diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 4f0baa6..02f4ac4 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -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 |