diff options
-rw-r--r-- | Lib/test/test_sqlite3/test_dbapi.py | 65 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2025-02-03-01-43-16.gh-issue-129603.xge9Tx.rst | 3 | ||||
-rw-r--r-- | Modules/_sqlite/row.c | 25 |
3 files changed, 83 insertions, 10 deletions
diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index f5ffe242743..c3aa3bf2d7b 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -1924,5 +1924,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.assertIsSubclass(sqlite.Row, Sequence) + self.assertIsInstance(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 index 00000000000..0d0ec21bddd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-02-03-01-43-16.gh-issue-129603.xge9Tx.rst @@ -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. diff --git a/Modules/_sqlite/row.c b/Modules/_sqlite/row.c index 79660008b18..94565a01d18 100644 --- a/Modules/_sqlite/row.c +++ b/Modules/_sqlite/row.c @@ -138,7 +138,6 @@ static PyObject * pysqlite_row_subscript(PyObject *op, PyObject *idx) { Py_ssize_t _idx; - Py_ssize_t nitems, i; pysqlite_Row *self = _pysqlite_Row_CAST(op); if (PyLong_Check(idx)) { @@ -151,9 +150,13 @@ pysqlite_row_subscript(PyObject *op, 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); @@ -197,17 +200,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; } |