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

serhiy-storchaka <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/8288442bb61b644330780e1beb84a7c4e94fc93d
commit: 8288442bb61b644330780e1beb84a7c4e94fc93d
branch: 3.14
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-21T08:54:06Z
summary:

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

(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 e62891635e2fad..c64e109f9aa2d9 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 a4b5769040282a..4e6f46261b10bc 100644
--- a/Modules/_sqlite/cursor.c
+++ b/Modules/_sqlite/cursor.c
@@ -1348,13 +1348,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_CAST(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_CAST(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.