Skip to content

Commit 9cc0afc

Browse files
[3.10] gh-93421: Update sqlite3 cursor.rowcount only after SQLITE_DONE (GH-93526) (GH-93599)
(cherry picked from commit 875de61)
1 parent 6b91224 commit 9cc0afc

File tree

3 files changed

+25
-9
lines changed

3 files changed

+25
-9
lines changed

Lib/sqlite3/test/dbapi.py

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

384+
@unittest.skipIf(sqlite.sqlite_version_info < (3, 35, 0),
385+
"Requires SQLite 3.35.0 or newer")
386+
def test_rowcount_update_returning(self):
387+
# gh-93421: rowcount is updated correctly for UPDATE...RETURNING queries
388+
self.cu.execute("update test set name='bar' where name='foo' returning 1")
389+
self.assertEqual(self.cu.fetchone()[0], 1)
390+
self.assertEqual(self.cu.rowcount, 1)
391+
384392
def test_total_changes(self):
385393
self.cu.execute("insert into test(name) values ('foo')")
386394
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

+14-9
Original file line numberDiff line numberDiff line change
@@ -492,10 +492,9 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
492492
pysqlite_statement_reset(self->statement);
493493
}
494494

495-
/* reset description and rowcount */
495+
/* reset description */
496496
Py_INCREF(Py_None);
497497
Py_SETREF(self->description, Py_None);
498-
self->rowcount = 0L;
499498

500499
func_args = PyTuple_New(1);
501500
if (!func_args) {
@@ -527,6 +526,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
527526

528527
pysqlite_statement_reset(self->statement);
529528
pysqlite_statement_mark_dirty(self->statement);
529+
self->rowcount = self->statement->is_dml ? 0L : -1L;
530530

531531
/* We start a transaction implicitly before a DML statement.
532532
SELECT is the only exception. See #9924. */
@@ -604,12 +604,6 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
604604
}
605605
}
606606

607-
if (self->statement->is_dml) {
608-
self->rowcount += (long)sqlite3_changes(self->connection->db);
609-
} else {
610-
self->rowcount= -1L;
611-
}
612-
613607
if (!multiple) {
614608
Py_BEGIN_ALLOW_THREADS
615609
lastrowid = sqlite3_last_insert_rowid(self->connection->db);
@@ -630,11 +624,17 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
630624
if (self->next_row == NULL)
631625
goto error;
632626
} else if (rc == SQLITE_DONE && !multiple) {
627+
if (self->statement->is_dml) {
628+
self->rowcount = (long)sqlite3_changes(self->connection->db);
629+
}
633630
pysqlite_statement_reset(self->statement);
634631
Py_CLEAR(self->statement);
635632
}
636633

637634
if (multiple) {
635+
if (self->statement->is_dml && rc == SQLITE_DONE) {
636+
self->rowcount += (long)sqlite3_changes(self->connection->db);
637+
}
638638
pysqlite_statement_reset(self->statement);
639639
}
640640
Py_XDECREF(parameters);
@@ -824,7 +824,12 @@ pysqlite_cursor_iternext(pysqlite_Cursor *self)
824824
if (PyErr_Occurred()) {
825825
goto error;
826826
}
827-
if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
827+
if (rc == SQLITE_DONE) {
828+
if (self->statement->is_dml) {
829+
self->rowcount = (long)sqlite3_changes(self->connection->db);
830+
}
831+
}
832+
else if (rc != SQLITE_ROW) {
828833
_pysqlite_seterror(self->connection->db, NULL);
829834
goto error;
830835
}

0 commit comments

Comments
 (0)