[pypy-commit] pypy default: Found another way to fix http://bugs.python.org/issue29006 in PyPy:

arigo pypy.commits at gmail.com
Fri Jan 13 06:10:56 EST 2017


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r89537:235e8a388979
Date: 2017-01-13 12:10 +0100
http://bitbucket.org/pypy/pypy/changeset/235e8a388979/

Log:	Found another way to fix http://bugs.python.org/issue29006 in PyPy:
	remember which exact statements were open during the previous
	commit, and if we get SQLITE_LOCKED we close these ones

diff --git a/lib-python/2.7/sqlite3/test/regression.py b/lib-python/2.7/sqlite3/test/regression.py
--- a/lib-python/2.7/sqlite3/test/regression.py
+++ b/lib-python/2.7/sqlite3/test/regression.py
@@ -351,10 +351,7 @@
         self.assertRaises(ValueError, cur.execute, " \0select 2")
         self.assertRaises(ValueError, cur.execute, "select 2\0")
 
-    @test_support.impl_detail(pypy=False)
     def CheckCommitCursorReset(self):
-        # This test is for logic added in 2.7.13 which PyPy doesn't
-        # implement.  See http://bugs.python.org/issue29006
         """
         Connection.commit() did reset cursors, which made sqlite3
         to return rows multiple times when fetched from cursors
diff --git a/lib_pypy/_sqlite3.py b/lib_pypy/_sqlite3.py
--- a/lib_pypy/_sqlite3.py
+++ b/lib_pypy/_sqlite3.py
@@ -220,6 +220,7 @@
         self.__statements_counter = 0
         self.__rawstatements = set()
         self._statement_cache = _StatementCache(self, cached_statements)
+        self.__statements_already_committed = weakref.WeakSet()
 
         self.__func_cache = {}
         self.__aggregates = {}
@@ -363,6 +364,12 @@
                 if cursor is not None:
                     cursor._reset = True
 
+    def _reset_already_committed_statements(self):
+        lst = list(self.__statements_already_committed)
+        self.__statements_already_committed.clear()
+        for statement in lst:
+            statement._reset()
+
     @_check_thread_wrap
     @_check_closed_wrap
     def __call__(self, sql):
@@ -418,15 +425,21 @@
         if not self._in_transaction:
             return
 
-        # The following line is a KNOWN DIFFERENCE with CPython 2.7.13.
-        # More precisely, the corresponding line was removed in the
-        # version 2.7.13 of CPython, but this is causing troubles for
-        # PyPy (and potentially for CPython too):
-        #
-        #     http://bugs.python.org/issue29006
-        #
-        # So for now, we keep this line.
-        self.__do_all_statements(Statement._reset, False)
+        # PyPy fix for non-refcounting semantics: since 2.7.13 (and in
+        # <= 2.6.x), the statements are not automatically reset upon
+        # commit.  However, if this is followed by some specific SQL
+        # operations like "drop table", these open statements come in
+        # the way and cause the "drop table" to fail.  On CPython the
+        # problem is much less important because typically all the old
+        # statements are freed already by reference counting.  So here,
+        # we add all the still-alive statements to a WeakSet which is
+        # usually ignored, except if we get SQLITE_LOCKED
+        # afterwards---at which point we reset all statements in this
+        # WeakSet.
+        for weakref in self.__statements:
+            statement = weakref()
+            if statement is not None:
+                self.__statements_already_committed.add(statement)
 
         statement_star = _ffi.new('sqlite3_stmt **')
         ret = _lib.sqlite3_prepare_v2(self._db, b"COMMIT", -1,
@@ -827,8 +840,18 @@
                 self.__statement._set_params(params)
 
                 # Actually execute the SQL statement
+
                 ret = _lib.sqlite3_step(self.__statement._statement)
 
+                # PyPy: if we get SQLITE_LOCKED, it's probably because
+                # one of the cursors created previously is still alive
+                # and not reset and the operation we're trying to do
+                # makes Sqlite unhappy about that.  In that case, we
+                # automatically reset all old cursors and try again.
+                if ret == _lib.SQLITE_LOCKED:
+                    self.__connection._reset_already_committed_statements()
+                    ret = _lib.sqlite3_step(self.__statement._statement)
+
                 if ret == _lib.SQLITE_ROW:
                     if multiple:
                         raise ProgrammingError("executemany() can only execute DML statements.")
diff --git a/pypy/doc/cpython_differences.rst b/pypy/doc/cpython_differences.rst
--- a/pypy/doc/cpython_differences.rst
+++ b/pypy/doc/cpython_differences.rst
@@ -483,11 +483,6 @@
   the rest is kept.  If you return an unexpected string from
   ``__hex__()`` you get an exception (or a crash before CPython 2.7.13).
 
-* The ``sqlite`` module was updated on 2.7.13 to no longer reset all
-  cursors when there is a commit.  This causes troubles for PyPy (and
-  potentially for CPython too), and so for now we didn't port this change:
-  see http://bugs.python.org/issue29006.
-
 .. _`is ignored in PyPy`: http://bugs.python.org/issue14621
 .. _`little point`: http://events.ccc.de/congress/2012/Fahrplan/events/5152.en.html
 .. _`#2072`: https://bitbucket.org/pypy/pypy/issue/2072/


More information about the pypy-commit mailing list