From fb68617ea84847825f707b732c69341f3c37d958 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Wed, 1 Jul 2026 19:25:59 +0100 Subject: [PATCH 1/3] Add test showing del cursor.row_factory then executing query causes segfault --- Lib/test/test_sqlite3/test_factory.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_sqlite3/test_factory.py b/Lib/test/test_sqlite3/test_factory.py index a9abeab31936880..b7f48614faf10c8 100644 --- a/Lib/test/test_sqlite3/test_factory.py +++ b/Lib/test/test_sqlite3/test_factory.py @@ -156,6 +156,13 @@ def test_delete_connection_text_factory(self): with self.assertRaises(AttributeError): del self.con.text_factory + def test_delete_cursor_row_factory(self): + # gh-149738: deleting row_factory should raise an exception + cur = self.con.cursor() + del cur.row_factory + # Executing a query here should succeed. + self.assertEqual(tuple(cur.execute("select 1").fetchone()), (1,)) + def test_sqlite_row_index_unicode(self): row = self.con.execute("select 1 as \xff").fetchone() self.assertEqual(row["\xff"], 1) From b5c7224c59f0f7c7c9c6c3cddfeeff62d4a094ec Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Wed, 1 Jul 2026 19:29:11 +0100 Subject: [PATCH 2/3] Add __delattr__ guard to sqlite3 `cursor.row_factory` attr to prevent segfault and bring behaviour in line with docs --- Lib/test/test_sqlite3/test_factory.py | 3 ++- Modules/_sqlite/cursor.c | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_sqlite3/test_factory.py b/Lib/test/test_sqlite3/test_factory.py index b7f48614faf10c8..26217763489e0f4 100644 --- a/Lib/test/test_sqlite3/test_factory.py +++ b/Lib/test/test_sqlite3/test_factory.py @@ -159,7 +159,8 @@ def test_delete_connection_text_factory(self): def test_delete_cursor_row_factory(self): # gh-149738: deleting row_factory should raise an exception cur = self.con.cursor() - del cur.row_factory + with self.assertRaises(AttributeError): + del cur.row_factory # Executing a query here should succeed. self.assertEqual(tuple(cur.execute("select 1").fetchone()), (1,)) diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index 5a61e43617984d9..e3a40ce73954307 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -1400,13 +1400,33 @@ static struct PyMemberDef cursor_members[] = {"description", _Py_T_OBJECT, offsetof(pysqlite_Cursor, description), Py_READONLY}, {"lastrowid", _Py_T_OBJECT, offsetof(pysqlite_Cursor, lastrowid), Py_READONLY}, {"rowcount", Py_T_LONG, offsetof(pysqlite_Cursor, rowcount), Py_READONLY}, - {"row_factory", _Py_T_OBJECT, offsetof(pysqlite_Cursor, row_factory), 0}, {"__weaklistoffset__", Py_T_PYSSIZET, offsetof(pysqlite_Cursor, in_weakreflist), Py_READONLY}, {NULL} }; +static PyObject * +cursor_get_row_factory(PyObject *op, void *closure) +{ + pysqlite_Cursor *self = (pysqlite_Cursor *)op; + return Py_NewRef(self->row_factory); +} + +static int +cursor_set_row_factory(PyObject *op, PyObject *value, void *closure) +{ + pysqlite_Cursor *self = (pysqlite_Cursor *)op; + if (value == NULL) { + PyErr_SetString(PyExc_AttributeError, + "cannot delete row_factory attribute"); + return -1; + } + Py_XSETREF(self->row_factory, Py_NewRef(value)); + return 0; +} + static struct PyGetSetDef cursor_getsets[] = { _SQLITE3_CURSOR_ARRAYSIZE_GETSETDEF + {"row_factory", cursor_get_row_factory, cursor_set_row_factory}, {NULL}, }; From f5604675a8ac8c3bb9ff6459b132f882f5f1ea9d Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Wed, 1 Jul 2026 19:31:04 +0100 Subject: [PATCH 3/3] Amend the existing NEWS blurb --- .../2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst index e62b681d716650b..e1935555b091742 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst @@ -1,2 +1,2 @@ :mod:`sqlite3`: Disallow removing ``row_factory`` and ``text_factory`` attributes -of a connection to prevent a crash on a query. +of a connection or cursor to prevent a crash on a query.