Skip to content

Commit 875de61

Browse files
gh-93421: Update sqlite3 cursor.rowcount only after SQLITE_DONE (#93526)
1 parent 5849af7 commit 875de61

File tree

3 files changed

+22
-8
lines changed

3 files changed

+22
-8
lines changed

Lib/test/test_sqlite3/test_dbapi.py

+8
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,14 @@ def test_rowcount_executemany(self):
898898
self.cu.executemany("insert into test(name) values (?)", [(1,), (2,), (3,)])
899899
self.assertEqual(self.cu.rowcount, 3)
900900

901+
@unittest.skipIf(sqlite.sqlite_version_info < (3, 35, 0),
902+
"Requires SQLite 3.35.0 or newer")
903+
def test_rowcount_update_returning(self):
904+
# gh-93421: rowcount is updated correctly for UPDATE...RETURNING queries
905+
self.cu.execute("update test set name='bar' where name='foo' returning 1")
906+
self.assertEqual(self.cu.fetchone()[0], 1)
907+
self.assertEqual(self.cu.rowcount, 1)
908+
901909
def test_total_changes(self):
902910
self.cu.execute("insert into test(name) values ('foo')")
903911
self.cu.execute("insert into test(name) values ('foo')")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Update :data:`sqlite3.Cursor.rowcount` when a DML statement has run to
2+
completion. This fixes the row count for SQL queries like
3+
``UPDATE ... RETURNING``. Patch by Erlend E. Aasland.

Modules/_sqlite/cursor.c

+11-8
Original file line numberDiff line numberDiff line change
@@ -835,10 +835,9 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
835835
stmt_reset(self->statement);
836836
}
837837

838-
/* reset description and rowcount */
838+
/* reset description */
839839
Py_INCREF(Py_None);
840840
Py_SETREF(self->description, Py_None);
841-
self->rowcount = 0L;
842841

843842
if (self->statement) {
844843
(void)stmt_reset(self->statement);
@@ -867,6 +866,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
867866

868867
stmt_reset(self->statement);
869868
stmt_mark_dirty(self->statement);
869+
self->rowcount = self->statement->is_dml ? 0L : -1L;
870870

871871
/* We start a transaction implicitly before a DML statement.
872872
SELECT is the only exception. See #9924. */
@@ -944,18 +944,18 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
944944
}
945945
}
946946

947-
if (self->statement->is_dml) {
948-
self->rowcount += (long)sqlite3_changes(self->connection->db);
949-
} else {
950-
self->rowcount= -1L;
951-
}
952-
953947
if (rc == SQLITE_DONE && !multiple) {
948+
if (self->statement->is_dml) {
949+
self->rowcount = (long)sqlite3_changes(self->connection->db);
950+
}
954951
stmt_reset(self->statement);
955952
Py_CLEAR(self->statement);
956953
}
957954

958955
if (multiple) {
956+
if (self->statement->is_dml && rc == SQLITE_DONE) {
957+
self->rowcount += (long)sqlite3_changes(self->connection->db);
958+
}
959959
stmt_reset(self->statement);
960960
}
961961
Py_XDECREF(parameters);
@@ -1125,6 +1125,9 @@ pysqlite_cursor_iternext(pysqlite_Cursor *self)
11251125
}
11261126
int rc = stmt_step(stmt);
11271127
if (rc == SQLITE_DONE) {
1128+
if (self->statement->is_dml) {
1129+
self->rowcount = (long)sqlite3_changes(self->connection->db);
1130+
}
11281131
(void)stmt_reset(self->statement);
11291132
}
11301133
else if (rc != SQLITE_ROW) {

0 commit comments

Comments
 (0)