From 61d41f5d0f3ce21ae1f9176b39a51db191da5c78 Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Tue, 14 Jul 2026 19:45:55 +0800 Subject: [PATCH] gh-153695: Fix sqlite3.Row.__hash__ with an unhashable value --- Lib/test/test_sqlite3/test_factory.py | 11 +++++++++++ .../2026-07-14-19-45-21.gh-issue-153695.UYdZJZ.rst | 2 ++ Modules/_sqlite/row.c | 10 +++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-14-19-45-21.gh-issue-153695.UYdZJZ.rst diff --git a/Lib/test/test_sqlite3/test_factory.py b/Lib/test/test_sqlite3/test_factory.py index a9abeab3193688..b9b18fdee87226 100644 --- a/Lib/test/test_sqlite3/test_factory.py +++ b/Lib/test/test_sqlite3/test_factory.py @@ -239,6 +239,17 @@ def test_sqlite_row_hash_cmp(self): self.assertEqual(hash(row_1), hash(row_2)) + def test_sqlite_row_hash_unhashable(self): + # An unhashable value must raise TypeError, not SystemError. + sqlite.register_converter("LST", lambda b: [1, 2, 3]) + self.addCleanup(sqlite.converters.pop, "LST", None) + with memory_database(detect_types=sqlite.PARSE_DECLTYPES) as con: + con.row_factory = sqlite.Row + con.execute("create table t(x LST)") + con.execute("insert into t values(?)", (b"x",)) + row = con.execute("select x from t").fetchone() + self.assertRaisesRegex(TypeError, "unhashable", hash, row) + def test_sqlite_row_as_sequence(self): # Checks if the row object can act like a sequence. row = self.con.execute("select 1 as a, 2 as b").fetchone() diff --git a/Misc/NEWS.d/next/Library/2026-07-14-19-45-21.gh-issue-153695.UYdZJZ.rst b/Misc/NEWS.d/next/Library/2026-07-14-19-45-21.gh-issue-153695.UYdZJZ.rst new file mode 100644 index 00000000000000..43d7feebf97d6f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-14-19-45-21.gh-issue-153695.UYdZJZ.rst @@ -0,0 +1,2 @@ +Hashing a :class:`sqlite3.Row` that contains an unhashable value now raises +:exc:`TypeError` instead of :exc:`SystemError`. Patch by tonghuaroot. diff --git a/Modules/_sqlite/row.c b/Modules/_sqlite/row.c index 94565a01d18f24..8646f591ef5169 100644 --- a/Modules/_sqlite/row.c +++ b/Modules/_sqlite/row.c @@ -232,7 +232,15 @@ static Py_hash_t pysqlite_row_hash(PyObject *op) { pysqlite_Row *self = _pysqlite_Row_CAST(op); - return PyObject_Hash(self->description) ^ PyObject_Hash(self->data); + Py_hash_t hash_description = PyObject_Hash(self->description); + if (hash_description == -1) { + return -1; + } + Py_hash_t hash_data = PyObject_Hash(self->data); + if (hash_data == -1) { + return -1; + } + return hash_description ^ hash_data; } static PyObject *