From fb68617ea84847825f707b732c69341f3c37d958 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Wed, 1 Jul 2026 19:25:59 +0100 Subject: [PATCH 1/6] Add test showing del cursor.row_factory then executing query causes segfault --- Lib/test/test_sqlite3/test_factory.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_sqlite3/test_factory.py b/Lib/test/test_sqlite3/test_factory.py index a9abeab3193688..b7f48614faf10c 100644 --- a/Lib/test/test_sqlite3/test_factory.py +++ b/Lib/test/test_sqlite3/test_factory.py @@ -156,6 +156,13 @@ 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() + 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) From b5c7224c59f0f7c7c9c6c3cddfeeff62d4a094ec Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Wed, 1 Jul 2026 19:29:11 +0100 Subject: [PATCH 2/6] Add __delattr__ guard to sqlite3 `cursor.row_factory` attr to prevent segfault and bring behaviour in line with docs --- Lib/test/test_sqlite3/test_factory.py | 3 ++- Modules/_sqlite/cursor.c | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_sqlite3/test_factory.py b/Lib/test/test_sqlite3/test_factory.py index b7f48614faf10c..26217763489e0f 100644 --- a/Lib/test/test_sqlite3/test_factory.py +++ b/Lib/test/test_sqlite3/test_factory.py @@ -159,7 +159,8 @@ def test_delete_connection_text_factory(self): def test_delete_cursor_row_factory(self): # gh-149738: deleting row_factory should raise an exception cur = self.con.cursor() - del cur.row_factory + with self.assertRaises(AttributeError): + del cur.row_factory # Executing a query here should succeed. self.assertEqual(tuple(cur.execute("select 1").fetchone()), (1,)) diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index 5a61e43617984d..e3a40ce7395430 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -1400,13 +1400,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 *closure) +{ + pysqlite_Cursor *self = (pysqlite_Cursor *)op; + return Py_NewRef(self->row_factory); +} + +static int +cursor_set_row_factory(PyObject *op, PyObject *value, void *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}, }; From f5604675a8ac8c3bb9ff6459b132f882f5f1ea9d Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Wed, 1 Jul 2026 19:31:04 +0100 Subject: [PATCH 3/6] Amend the existing NEWS blurb --- .../2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst index e62b681d716650..e1935555b09174 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst @@ -1,2 +1,2 @@ :mod:`sqlite3`: Disallow removing ``row_factory`` and ``text_factory`` attributes -of a connection to prevent a crash on a query. +of a connection or cursor to prevent a crash on a query. From 4f52cd72f8fcbcf855183bda0affe6561a65a193 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Fri, 3 Jul 2026 10:48:20 +0100 Subject: [PATCH 4/6] Defensive mode: Initialize factories in tp_new, re-initialize in tp_init (because of tp_clear calls) and also guard each use of row_factory and text_factory with an explicit NULL check. Keep the previous delattr guards, because who would be calling __delattr__ on these anyway. I'm imagining someone will suggest removing some of these changes, but something something seek forgiveness something. --- Lib/test/test_sqlite3/test_factory.py | 29 +++++++++++++++++++ ...-05-13-06-54-41.gh-issue-149738.4BLFoH.rst | 4 +-- Modules/_sqlite/connection.c | 28 ++++++++++++++---- Modules/_sqlite/cursor.c | 28 ++++++++++++++---- 4 files changed, 76 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_sqlite3/test_factory.py b/Lib/test/test_sqlite3/test_factory.py index 26217763489e0f..0e88ccc388ca6c 100644 --- a/Lib/test/test_sqlite3/test_factory.py +++ b/Lib/test/test_sqlite3/test_factory.py @@ -164,6 +164,35 @@ def test_delete_cursor_row_factory(self): # Executing a query here should succeed. self.assertEqual(tuple(cur.execute("select 1").fetchone()), (1,)) + def test_uninitialized_connection_factories(self): + # gh-152817: skipping __init__() should still result in initialized factories (None not Null) + con = sqlite.Connection.__new__(sqlite.Connection) + self.assertIsNone(con.row_factory) + self.assertIs(con.text_factory, str) + + def test_uninitialized_cursor_row_factory(self): + # gh-152817: skipping __init__() should still result in initialized factories (None not Null) + # __init__ must not crash. + cur = sqlite.Cursor.__new__(sqlite.Cursor) + self.assertIsNone(cur.row_factory) + + def test_subclass_skipping_super_init(self): + # gh-152817: forgetting to call super().__init__() shouldn't leave a NULL {row,text}_factory + class Connection(sqlite.Connection): + def __init__(self, *args, **kwargs): + pass + + class Cursor(sqlite.Cursor): + def __init__(self, *args, **kwargs): + pass + + con = Connection(":memory:") + self.assertIsNone(con.row_factory) + self.assertIs(con.text_factory, str) + + cur = Cursor(self.con) + self.assertIsNone(cur.row_factory) + 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/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst index e1935555b09174..8c9d1f37eb400c 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst @@ -1,2 +1,2 @@ -:mod:`sqlite3`: Disallow removing ``row_factory`` and ``text_factory`` attributes -of a connection or cursor to prevent a crash on a query. +:mod:`sqlite3`: Prevent crashes caused by row_factory or text_factory being uninitialized, either +by skipping __init__ or by deleting the attributes. diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 892740b05e55c9..476e88103d203a 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -300,8 +300,9 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, self->thread_ident = PyThread_get_thread_ident(); self->statement_cache = statement_cache; self->blobs = blobs; - self->row_factory = Py_NewRef(Py_None); - self->text_factory = Py_NewRef(&PyUnicode_Type); + // re-initialize the factory members here, as tp_clear() is called above in some cases + Py_XSETREF(self->row_factory, Py_NewRef(Py_None)); + Py_XSETREF(self->text_factory, Py_NewRef((PyObject *)&PyUnicode_Type)); self->trace_ctx = NULL; self->progress_ctx = NULL; self->authorizer_ctx = NULL; @@ -339,6 +340,19 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, return -1; } +static PyObject * +pysqlite_connection_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + pysqlite_Connection *self = (pysqlite_Connection *)type->tp_alloc(type, 0); + if (self == NULL) { + return NULL; + } + // row_factory and text_factory should never be uninitialized, even if tp_init is bypassed. + self->row_factory = Py_NewRef(Py_None); + self->text_factory = Py_NewRef((PyObject *)&PyUnicode_Type); + return (PyObject *)self; +} + /*[clinic input] # Create a new destination 'connect' for the docstring and methoddef only. # This makes it possible to keep the signatures for Connection.__init__ and @@ -549,7 +563,7 @@ pysqlite_connection_cursor_impl(pysqlite_Connection *self, PyObject *factory) return NULL; } - if (cursor && self->row_factory != Py_None) { + if (cursor && self->row_factory != NULL && !Py_IsNone(self->row_factory)) { Py_INCREF(self->row_factory); Py_XSETREF(((pysqlite_Cursor *)cursor)->row_factory, self->row_factory); } @@ -561,7 +575,8 @@ static PyObject * connection_get_row_factory(PyObject *op, void *closure) { pysqlite_Connection *self = (pysqlite_Connection *)op; - return Py_NewRef(self->row_factory); + PyObject *row_factory = self->row_factory; + return Py_NewRef(row_factory != NULL ? row_factory : Py_None); } static int @@ -581,7 +596,9 @@ static PyObject * connection_get_text_factory(PyObject *op, void *closure) { pysqlite_Connection *self = (pysqlite_Connection *)op; - return Py_NewRef(self->text_factory); + PyObject *text_factory = self->text_factory; + return Py_NewRef(text_factory != NULL ? text_factory + : (PyObject *)&PyUnicode_Type); } static int @@ -2722,6 +2739,7 @@ static PyType_Slot connection_slots[] = { {Py_tp_methods, connection_methods}, {Py_tp_members, connection_members}, {Py_tp_getset, connection_getset}, + {Py_tp_new, pysqlite_connection_new}, {Py_tp_init, pysqlite_connection_init}, {Py_tp_call, pysqlite_connection_call}, {Py_tp_traverse, connection_traverse}, diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index e3a40ce7395430..7c2f620248a54a 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -143,6 +143,18 @@ pysqlite_cursor_init_impl(pysqlite_Cursor *self, return 0; } +static PyObject * +pysqlite_cursor_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + pysqlite_Cursor *self = (pysqlite_Cursor *)type->tp_alloc(type, 0); + if (self == NULL) { + return NULL; + } + // row_factory should never be uninitialized, even if tp_init is bypassed. + self->row_factory = Py_NewRef(Py_None); + return (PyObject *)self; +} + static inline int stmt_reset(pysqlite_Statement *self) { @@ -413,7 +425,9 @@ _pysqlite_fetch_one_row(pysqlite_Cursor* self) } nbytes = sqlite3_column_bytes(self->statement->st, i); - if (self->connection->text_factory == (PyObject*)&PyUnicode_Type) { + PyObject *text_factory = self->connection->text_factory; + if (text_factory == NULL || + text_factory == (PyObject*)&PyUnicode_Type) { converted = PyUnicode_FromStringAndSize(text, nbytes); if (!converted && PyErr_ExceptionMatches(PyExc_UnicodeDecodeError)) { PyErr_Clear(); @@ -434,12 +448,12 @@ _pysqlite_fetch_one_row(pysqlite_Cursor* self) Py_DECREF(error_msg); } } - } else if (self->connection->text_factory == (PyObject*)&PyBytes_Type) { + } else if (text_factory == (PyObject*)&PyBytes_Type) { converted = PyBytes_FromStringAndSize(text, nbytes); - } else if (self->connection->text_factory == (PyObject*)&PyByteArray_Type) { + } else if (text_factory == (PyObject*)&PyByteArray_Type) { converted = PyByteArray_FromStringAndSize(text, nbytes); } else { - converted = PyObject_CallFunction(self->connection->text_factory, "y#", text, nbytes); + converted = PyObject_CallFunction(text_factory, "y#", text, nbytes); } } else { /* coltype == SQLITE_BLOB */ @@ -1176,7 +1190,7 @@ pysqlite_cursor_iternext(PyObject *op) } return NULL; } - if (!Py_IsNone(self->row_factory)) { + if (self->row_factory != NULL && !Py_IsNone(self->row_factory)) { PyObject *factory = self->row_factory; PyObject *args[] = { op, row, }; PyObject *new_row = PyObject_Vectorcall(factory, args, 2, NULL); @@ -1408,7 +1422,8 @@ static PyObject * cursor_get_row_factory(PyObject *op, void *closure) { pysqlite_Cursor *self = (pysqlite_Cursor *)op; - return Py_NewRef(self->row_factory); + PyObject *row_factory = self->row_factory; + return Py_NewRef(row_factory != NULL ? row_factory : Py_None); } static int @@ -1441,6 +1456,7 @@ static PyType_Slot cursor_slots[] = { {Py_tp_methods, cursor_methods}, {Py_tp_members, cursor_members}, {Py_tp_getset, cursor_getsets}, + {Py_tp_new, pysqlite_cursor_new}, {Py_tp_init, pysqlite_cursor_init}, {Py_tp_traverse, cursor_traverse}, {Py_tp_clear, cursor_clear}, From 3bab8e83c9e343f8a5e85e9c6d86c215da91658a Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Fri, 3 Jul 2026 10:56:11 +0100 Subject: [PATCH 5/6] Put back the quoting around attribute mentions --- .../2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst index 8c9d1f37eb400c..92bb0804ad05b8 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst @@ -1,2 +1,2 @@ -:mod:`sqlite3`: Prevent crashes caused by row_factory or text_factory being uninitialized, either -by skipping __init__ or by deleting the attributes. +:mod:`sqlite3`: Prevent crashes caused by ``row_factory`` or ``text_factory`` being uninitialized, either +by skipping ``__init__`` or by deleting the attributes. From a00cb59d921ecb127b504020a4a5600fa8e86e53 Mon Sep 17 00:00:00 2001 From: Steve Stagg Date: Fri, 3 Jul 2026 12:40:12 +0100 Subject: [PATCH 6/6] Revert the fix for code that doesn't call __init__on Cursor/Connection, keeping the underlying delattr guards --- Lib/test/test_sqlite3/test_factory.py | 29 ------------------- ...-05-13-06-54-41.gh-issue-149738.4BLFoH.rst | 4 +-- Modules/_sqlite/connection.c | 28 ++++-------------- Modules/_sqlite/cursor.c | 28 ++++-------------- 4 files changed, 13 insertions(+), 76 deletions(-) diff --git a/Lib/test/test_sqlite3/test_factory.py b/Lib/test/test_sqlite3/test_factory.py index 0e88ccc388ca6c..26217763489e0f 100644 --- a/Lib/test/test_sqlite3/test_factory.py +++ b/Lib/test/test_sqlite3/test_factory.py @@ -164,35 +164,6 @@ def test_delete_cursor_row_factory(self): # Executing a query here should succeed. self.assertEqual(tuple(cur.execute("select 1").fetchone()), (1,)) - def test_uninitialized_connection_factories(self): - # gh-152817: skipping __init__() should still result in initialized factories (None not Null) - con = sqlite.Connection.__new__(sqlite.Connection) - self.assertIsNone(con.row_factory) - self.assertIs(con.text_factory, str) - - def test_uninitialized_cursor_row_factory(self): - # gh-152817: skipping __init__() should still result in initialized factories (None not Null) - # __init__ must not crash. - cur = sqlite.Cursor.__new__(sqlite.Cursor) - self.assertIsNone(cur.row_factory) - - def test_subclass_skipping_super_init(self): - # gh-152817: forgetting to call super().__init__() shouldn't leave a NULL {row,text}_factory - class Connection(sqlite.Connection): - def __init__(self, *args, **kwargs): - pass - - class Cursor(sqlite.Cursor): - def __init__(self, *args, **kwargs): - pass - - con = Connection(":memory:") - self.assertIsNone(con.row_factory) - self.assertIs(con.text_factory, str) - - cur = Cursor(self.con) - self.assertIsNone(cur.row_factory) - 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/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst index 92bb0804ad05b8..e1935555b09174 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst @@ -1,2 +1,2 @@ -:mod:`sqlite3`: Prevent crashes caused by ``row_factory`` or ``text_factory`` being uninitialized, either -by skipping ``__init__`` or by deleting the attributes. +:mod:`sqlite3`: Disallow removing ``row_factory`` and ``text_factory`` attributes +of a connection or cursor to prevent a crash on a query. diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 476e88103d203a..892740b05e55c9 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -300,9 +300,8 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, self->thread_ident = PyThread_get_thread_ident(); self->statement_cache = statement_cache; self->blobs = blobs; - // re-initialize the factory members here, as tp_clear() is called above in some cases - Py_XSETREF(self->row_factory, Py_NewRef(Py_None)); - Py_XSETREF(self->text_factory, Py_NewRef((PyObject *)&PyUnicode_Type)); + self->row_factory = Py_NewRef(Py_None); + self->text_factory = Py_NewRef(&PyUnicode_Type); self->trace_ctx = NULL; self->progress_ctx = NULL; self->authorizer_ctx = NULL; @@ -340,19 +339,6 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, return -1; } -static PyObject * -pysqlite_connection_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - pysqlite_Connection *self = (pysqlite_Connection *)type->tp_alloc(type, 0); - if (self == NULL) { - return NULL; - } - // row_factory and text_factory should never be uninitialized, even if tp_init is bypassed. - self->row_factory = Py_NewRef(Py_None); - self->text_factory = Py_NewRef((PyObject *)&PyUnicode_Type); - return (PyObject *)self; -} - /*[clinic input] # Create a new destination 'connect' for the docstring and methoddef only. # This makes it possible to keep the signatures for Connection.__init__ and @@ -563,7 +549,7 @@ pysqlite_connection_cursor_impl(pysqlite_Connection *self, PyObject *factory) return NULL; } - if (cursor && self->row_factory != NULL && !Py_IsNone(self->row_factory)) { + if (cursor && self->row_factory != Py_None) { Py_INCREF(self->row_factory); Py_XSETREF(((pysqlite_Cursor *)cursor)->row_factory, self->row_factory); } @@ -575,8 +561,7 @@ static PyObject * connection_get_row_factory(PyObject *op, void *closure) { pysqlite_Connection *self = (pysqlite_Connection *)op; - PyObject *row_factory = self->row_factory; - return Py_NewRef(row_factory != NULL ? row_factory : Py_None); + return Py_NewRef(self->row_factory); } static int @@ -596,9 +581,7 @@ static PyObject * connection_get_text_factory(PyObject *op, void *closure) { pysqlite_Connection *self = (pysqlite_Connection *)op; - PyObject *text_factory = self->text_factory; - return Py_NewRef(text_factory != NULL ? text_factory - : (PyObject *)&PyUnicode_Type); + return Py_NewRef(self->text_factory); } static int @@ -2739,7 +2722,6 @@ static PyType_Slot connection_slots[] = { {Py_tp_methods, connection_methods}, {Py_tp_members, connection_members}, {Py_tp_getset, connection_getset}, - {Py_tp_new, pysqlite_connection_new}, {Py_tp_init, pysqlite_connection_init}, {Py_tp_call, pysqlite_connection_call}, {Py_tp_traverse, connection_traverse}, diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index 7c2f620248a54a..e3a40ce7395430 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -143,18 +143,6 @@ pysqlite_cursor_init_impl(pysqlite_Cursor *self, return 0; } -static PyObject * -pysqlite_cursor_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - pysqlite_Cursor *self = (pysqlite_Cursor *)type->tp_alloc(type, 0); - if (self == NULL) { - return NULL; - } - // row_factory should never be uninitialized, even if tp_init is bypassed. - self->row_factory = Py_NewRef(Py_None); - return (PyObject *)self; -} - static inline int stmt_reset(pysqlite_Statement *self) { @@ -425,9 +413,7 @@ _pysqlite_fetch_one_row(pysqlite_Cursor* self) } nbytes = sqlite3_column_bytes(self->statement->st, i); - PyObject *text_factory = self->connection->text_factory; - if (text_factory == NULL || - text_factory == (PyObject*)&PyUnicode_Type) { + if (self->connection->text_factory == (PyObject*)&PyUnicode_Type) { converted = PyUnicode_FromStringAndSize(text, nbytes); if (!converted && PyErr_ExceptionMatches(PyExc_UnicodeDecodeError)) { PyErr_Clear(); @@ -448,12 +434,12 @@ _pysqlite_fetch_one_row(pysqlite_Cursor* self) Py_DECREF(error_msg); } } - } else if (text_factory == (PyObject*)&PyBytes_Type) { + } else if (self->connection->text_factory == (PyObject*)&PyBytes_Type) { converted = PyBytes_FromStringAndSize(text, nbytes); - } else if (text_factory == (PyObject*)&PyByteArray_Type) { + } else if (self->connection->text_factory == (PyObject*)&PyByteArray_Type) { converted = PyByteArray_FromStringAndSize(text, nbytes); } else { - converted = PyObject_CallFunction(text_factory, "y#", text, nbytes); + converted = PyObject_CallFunction(self->connection->text_factory, "y#", text, nbytes); } } else { /* coltype == SQLITE_BLOB */ @@ -1190,7 +1176,7 @@ pysqlite_cursor_iternext(PyObject *op) } return NULL; } - if (self->row_factory != NULL && !Py_IsNone(self->row_factory)) { + if (!Py_IsNone(self->row_factory)) { PyObject *factory = self->row_factory; PyObject *args[] = { op, row, }; PyObject *new_row = PyObject_Vectorcall(factory, args, 2, NULL); @@ -1422,8 +1408,7 @@ static PyObject * cursor_get_row_factory(PyObject *op, void *closure) { pysqlite_Cursor *self = (pysqlite_Cursor *)op; - PyObject *row_factory = self->row_factory; - return Py_NewRef(row_factory != NULL ? row_factory : Py_None); + return Py_NewRef(self->row_factory); } static int @@ -1456,7 +1441,6 @@ static PyType_Slot cursor_slots[] = { {Py_tp_methods, cursor_methods}, {Py_tp_members, cursor_members}, {Py_tp_getset, cursor_getsets}, - {Py_tp_new, pysqlite_cursor_new}, {Py_tp_init, pysqlite_cursor_init}, {Py_tp_traverse, cursor_traverse}, {Py_tp_clear, cursor_clear},