]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-129603: Don't segfault if sqlite3.Row description is None (#129604) (#129923)
authorErlend E. Aasland <erlend@python.org>
Mon, 10 Feb 2025 00:21:33 +0000 (01:21 +0100)
committerGitHub <noreply@github.com>
Mon, 10 Feb 2025 00:21:33 +0000 (00:21 +0000)
(cherry picked from commit 7e6ee50b6b8c760bcefb92ab4ddbc3d85d37a834)

Lib/test/test_sqlite3/test_dbapi.py
Misc/NEWS.d/next/Library/2025-02-03-01-43-16.gh-issue-129603.xge9Tx.rst [new file with mode: 0644]
Modules/_sqlite/row.c

index 51ce095df41fc1e2f8dc9e6c1676b4362c6e55d2..83134020c031c09d0052066eac92740a6e6635fd 100644 (file)
@@ -1947,5 +1947,70 @@ class MultiprocessTests(unittest.TestCase):
         self.assertEqual(proc.returncode, 0)
 
 
+class RowTests(unittest.TestCase):
+
+    def setUp(self):
+        self.cx = sqlite.connect(":memory:")
+        self.cx.row_factory = sqlite.Row
+
+    def tearDown(self):
+        self.cx.close()
+
+    def test_row_keys(self):
+        cu = self.cx.execute("SELECT 1 as first, 2 as second")
+        row = cu.fetchone()
+        self.assertEqual(row.keys(), ["first", "second"])
+
+    def test_row_length(self):
+        cu = self.cx.execute("SELECT 1, 2, 3")
+        row = cu.fetchone()
+        self.assertEqual(len(row), 3)
+
+    def test_row_getitem(self):
+        cu = self.cx.execute("SELECT 1 as a, 2 as b")
+        row = cu.fetchone()
+        self.assertEqual(row[0], 1)
+        self.assertEqual(row[1], 2)
+        self.assertEqual(row["a"], 1)
+        self.assertEqual(row["b"], 2)
+        for key in "nokey", 4, 1.2:
+            with self.subTest(key=key):
+                with self.assertRaises(IndexError):
+                    row[key]
+
+    def test_row_equality(self):
+        c1 = self.cx.execute("SELECT 1 as a")
+        r1 = c1.fetchone()
+
+        c2 = self.cx.execute("SELECT 1 as a")
+        r2 = c2.fetchone()
+
+        self.assertIsNot(r1, r2)
+        self.assertEqual(r1, r2)
+
+        c3 = self.cx.execute("SELECT 1 as b")
+        r3 = c3.fetchone()
+
+        self.assertNotEqual(r1, r3)
+
+    def test_row_no_description(self):
+        cu = self.cx.cursor()
+        self.assertIsNone(cu.description)
+
+        row = sqlite.Row(cu, ())
+        self.assertEqual(row.keys(), [])
+        with self.assertRaisesRegex(IndexError, "nokey"):
+            row["nokey"]
+
+    def test_row_is_a_sequence(self):
+        from collections.abc import Sequence
+
+        cu = self.cx.execute("SELECT 1")
+        row = cu.fetchone()
+
+        self.assertTrue(issubclass(sqlite.Row, Sequence))
+        self.assertTrue(isinstance(row, Sequence))
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2025-02-03-01-43-16.gh-issue-129603.xge9Tx.rst b/Misc/NEWS.d/next/Library/2025-02-03-01-43-16.gh-issue-129603.xge9Tx.rst
new file mode 100644 (file)
index 0000000..0d0ec21
--- /dev/null
@@ -0,0 +1,3 @@
+Fix bugs where :class:`sqlite3.Row` objects could segfault if their
+inherited :attr:`~sqlite3.Cursor.description` was set to ``None``. Patch by
+Erlend Aasland.
index 14555076a7e79a0875687b807602368b17143ebf..3427fae373d4fc8619edf8b6545984d057ff9480 100644 (file)
@@ -132,7 +132,6 @@ static PyObject *
 pysqlite_row_subscript(pysqlite_Row *self, PyObject *idx)
 {
     Py_ssize_t _idx;
-    Py_ssize_t nitems, i;
 
     if (PyLong_Check(idx)) {
         _idx = PyNumber_AsSsize_t(idx, PyExc_IndexError);
@@ -144,9 +143,13 @@ pysqlite_row_subscript(pysqlite_Row *self, PyObject *idx)
         PyObject *item = PyTuple_GetItem(self->data, _idx);
         return Py_XNewRef(item);
     } else if (PyUnicode_Check(idx)) {
-        nitems = PyTuple_Size(self->description);
+        if (Py_IsNone(self->description)) {
+            PyErr_Format(PyExc_IndexError, "No item with key %R", idx);
+            return NULL;
+        }
+        Py_ssize_t nitems = PyTuple_GET_SIZE(self->description);
 
-        for (i = 0; i < nitems; i++) {
+        for (Py_ssize_t i = 0; i < nitems; i++) {
             PyObject *obj;
             obj = PyTuple_GET_ITEM(self->description, i);
             obj = PyTuple_GET_ITEM(obj, 0);
@@ -189,17 +192,19 @@ static PyObject *
 pysqlite_row_keys_impl(pysqlite_Row *self)
 /*[clinic end generated code: output=efe3dfb3af6edc07 input=7549a122827c5563]*/
 {
-    PyObject* list;
-    Py_ssize_t nitems, i;
-
-    list = PyList_New(0);
+    PyObject *list = PyList_New(0);
     if (!list) {
         return NULL;
     }
-    nitems = PyTuple_Size(self->description);
+    if (Py_IsNone(self->description)) {
+        return list;
+    }
 
-    for (i = 0; i < nitems; i++) {
-        if (PyList_Append(list, PyTuple_GET_ITEM(PyTuple_GET_ITEM(self->description, i), 0)) != 0) {
+    Py_ssize_t nitems = PyTuple_GET_SIZE(self->description);
+    for (Py_ssize_t i = 0; i < nitems; i++) {
+        PyObject *descr = PyTuple_GET_ITEM(self->description, i);
+        PyObject *name = PyTuple_GET_ITEM(descr, 0);
+        if (PyList_Append(list, name) < 0) {
             Py_DECREF(list);
             return NULL;
         }