]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-93421: Update sqlite3 cursor.rowcount only after SQLITE_DONE (#93526)
authorErlend Egeberg Aasland <erlend.aasland@protonmail.com>
Wed, 8 Jun 2022 10:43:54 +0000 (12:43 +0200)
committerGitHub <noreply@github.com>
Wed, 8 Jun 2022 10:43:54 +0000 (12:43 +0200)
Lib/test/test_sqlite3/test_dbapi.py
Misc/NEWS.d/next/Library/2022-06-05-22-22-42.gh-issue-93421.43UO_8.rst [new file with mode: 0644]
Modules/_sqlite/cursor.c

index b958bf112a93095c539729ba689e344ec4f98376..05180a3616c5d4ba6dabba0d5cd1893bcc78f914 100644 (file)
@@ -898,6 +898,14 @@ class CursorTests(unittest.TestCase):
         self.cu.executemany("insert into test(name) values (?)", [(1,), (2,), (3,)])
         self.assertEqual(self.cu.rowcount, 3)
 
+    @unittest.skipIf(sqlite.sqlite_version_info < (3, 35, 0),
+                     "Requires SQLite 3.35.0 or newer")
+    def test_rowcount_update_returning(self):
+        # gh-93421: rowcount is updated correctly for UPDATE...RETURNING queries
+        self.cu.execute("update test set name='bar' where name='foo' returning 1")
+        self.assertEqual(self.cu.fetchone()[0], 1)
+        self.assertEqual(self.cu.rowcount, 1)
+
     def test_total_changes(self):
         self.cu.execute("insert into test(name) values ('foo')")
         self.cu.execute("insert into test(name) values ('foo')")
diff --git a/Misc/NEWS.d/next/Library/2022-06-05-22-22-42.gh-issue-93421.43UO_8.rst b/Misc/NEWS.d/next/Library/2022-06-05-22-22-42.gh-issue-93421.43UO_8.rst
new file mode 100644 (file)
index 0000000..9e1d655
--- /dev/null
@@ -0,0 +1,3 @@
+Update :data:`sqlite3.Cursor.rowcount` when a DML statement has run to
+completion. This fixes the row count for SQL queries like
+``UPDATE ... RETURNING``. Patch by Erlend E. Aasland.
index c58def5f0362f15f940e7e05af6a97266e5018c8..414584d8ee30e92adf1b2c0a6d971c2a447ea0be 100644 (file)
@@ -835,10 +835,9 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
         stmt_reset(self->statement);
     }
 
-    /* reset description and rowcount */
+    /* reset description */
     Py_INCREF(Py_None);
     Py_SETREF(self->description, Py_None);
-    self->rowcount = 0L;
 
     if (self->statement) {
         (void)stmt_reset(self->statement);
@@ -867,6 +866,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
 
     stmt_reset(self->statement);
     stmt_mark_dirty(self->statement);
+    self->rowcount = self->statement->is_dml ? 0L : -1L;
 
     /* We start a transaction implicitly before a DML statement.
        SELECT is the only exception. See #9924. */
@@ -944,18 +944,18 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
             }
         }
 
-        if (self->statement->is_dml) {
-            self->rowcount += (long)sqlite3_changes(self->connection->db);
-        } else {
-            self->rowcount= -1L;
-        }
-
         if (rc == SQLITE_DONE && !multiple) {
+            if (self->statement->is_dml) {
+                self->rowcount = (long)sqlite3_changes(self->connection->db);
+            }
             stmt_reset(self->statement);
             Py_CLEAR(self->statement);
         }
 
         if (multiple) {
+            if (self->statement->is_dml && rc == SQLITE_DONE) {
+                self->rowcount += (long)sqlite3_changes(self->connection->db);
+            }
             stmt_reset(self->statement);
         }
         Py_XDECREF(parameters);
@@ -1125,6 +1125,9 @@ pysqlite_cursor_iternext(pysqlite_Cursor *self)
     }
     int rc = stmt_step(stmt);
     if (rc == SQLITE_DONE) {
+        if (self->statement->is_dml) {
+            self->rowcount = (long)sqlite3_changes(self->connection->db);
+        }
         (void)stmt_reset(self->statement);
     }
     else if (rc != SQLITE_ROW) {