Skip to content

[3.10] gh-93421: Update sqlite3 cursor.rowcount only after SQLITE_DONE (GH-93526) #93599

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Lib/sqlite3/test/dbapi.py
Original file line number Diff line number Diff line change
@@ -381,6 +381,14 @@ def test_rowcount_executemany(self):
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')")
Original file line number Diff line number Diff line change
@@ -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.
23 changes: 14 additions & 9 deletions Modules/_sqlite/cursor.c
Original file line number Diff line number Diff line change
@@ -492,10 +492,9 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
pysqlite_statement_reset(self->statement);
}

/* reset description and rowcount */
/* reset description */
Py_INCREF(Py_None);
Py_SETREF(self->description, Py_None);
self->rowcount = 0L;

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

pysqlite_statement_reset(self->statement);
pysqlite_statement_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. */
@@ -604,12 +604,6 @@ _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 (!multiple) {
Py_BEGIN_ALLOW_THREADS
lastrowid = sqlite3_last_insert_rowid(self->connection->db);
@@ -630,11 +624,17 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
if (self->next_row == NULL)
goto error;
} else if (rc == SQLITE_DONE && !multiple) {
if (self->statement->is_dml) {
self->rowcount = (long)sqlite3_changes(self->connection->db);
}
pysqlite_statement_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);
}
pysqlite_statement_reset(self->statement);
}
Py_XDECREF(parameters);
@@ -824,7 +824,12 @@ pysqlite_cursor_iternext(pysqlite_Cursor *self)
if (PyErr_Occurred()) {
goto error;
}
if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
if (rc == SQLITE_DONE) {
if (self->statement->is_dml) {
self->rowcount = (long)sqlite3_changes(self->connection->db);
}
}
else if (rc != SQLITE_ROW) {
_pysqlite_seterror(self->connection->db, NULL);
goto error;
}