[3.13] gh-152817: Prevent deletion of sqlite3 cursor.row_factory attr (GH-152818) (GH-156162)

serhiy-storchaka <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/4f3afba4fe1b0f9cd09c892e02484385fe71e0f8
commit: 4f3afba4fe1b0f9cd09c892e02484385fe71e0f8
branch: 3.13
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-21T08:50:42Z
summary:

[3.13] gh-152817: Prevent deletion of sqlite3 cursor.row_factory attr (GH-152818) (GH-156162)

(cherry picked from commit 8e96dd6f25b53f0773d3f3f2a898eaa1501ee92e)

Co-authored-by: Steve Stagg <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-08-21-11-00-00.gh-issue-152817.Rt6mNc.rst
M Lib/test/test_sqlite3/test_factory.py
M Modules/_sqlite/cursor.c

diff --git a/Lib/test/test_sqlite3/test_factory.py b/Lib/test/test_sqlite3/test_factory.py
index d28754ff14729c..c19822bd10780f 100644
--- a/Lib/test/test_sqlite3/test_factory.py
+++ b/Lib/test/test_sqlite3/test_factory.py
@@ -165,6 +165,14 @@ 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()
+        with self.assertRaises(AttributeError):
+            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)
diff --git a/Misc/NEWS.d/next/Library/2026-08-21-11-00-00.gh-issue-152817.Rt6mNc.rst b/Misc/NEWS.d/next/Library/2026-08-21-11-00-00.gh-issue-152817.Rt6mNc.rst
new file mode 100644
index 00000000000000..1cc45cc6846713
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-21-11-00-00.gh-issue-152817.Rt6mNc.rst
@@ -0,0 +1,2 @@
+:mod:`sqlite3`: Disallow removing the ``row_factory`` attribute of a cursor
+to prevent a crash on a query.
diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c
index cc014c37323bbf..d8b7fc939a1d7a 100644
--- a/Modules/_sqlite/cursor.c
+++ b/Modules/_sqlite/cursor.c
@@ -1357,13 +1357,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 *Py_UNUSED(closure))
+{
+    pysqlite_Cursor *self = (pysqlite_Cursor *)op;
+    return Py_NewRef(self->row_factory);
+}
+
+static int
+cursor_set_row_factory(PyObject *op, PyObject *value, void *Py_UNUSED(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},
 };
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.