From 61e9937224fc15f2374502edcb50d8702d173af7 Mon Sep 17 00:00:00 2001 From: Charles Leifer Date: Tue, 10 Jan 2023 08:26:44 -0600 Subject: [PATCH] Add column decltype to cursor description. --- src/cursor.c | 23 +++++++++++++++++++++-- test/dbapi.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/cursor.c b/src/cursor.c index 553b56c..2ede578 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -219,6 +219,15 @@ _pysqlite_build_column_name(pysqlite_Cursor *self, const char *colname) return PyUnicode_FromStringAndSize(colname, len); } +static PyObject * +_pysqlite_build_column_decltype(pysqlite_Cursor *self, const char *decltype) +{ + if (!decltype) { + Py_RETURN_NONE; + } + return PyUnicode_FromStringAndSize(decltype, strlen(decltype)); +} + /* * Returns a row from the currently active SQLite statement * @@ -378,6 +387,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* args) PyObject* result; int numcols; PyObject* column_name; + PyObject* column_decltype; PyObject* second_argument = NULL; sqlite_int64 lastrowid; @@ -541,6 +551,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* args) } for (i = 0; i < numcols; i++) { const char *colname; + const char *decltype; colname = sqlite3_column_name(self->statement->st, i); if (colname == NULL) { PyErr_NoMemory(); @@ -550,10 +561,18 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* args) if (!column_name) { goto error; } - PyObject *descriptor = PyTuple_Pack(7, column_name, + decltype = sqlite3_column_decltype(self->statement->st, i); + column_decltype = _pysqlite_build_column_decltype(self, decltype); + if (!column_decltype) { + Py_DECREF(column_name); + goto error; + } + + PyObject *descriptor = PyTuple_Pack(7, column_name, column_decltype, Py_None, Py_None, Py_None, - Py_None, Py_None, Py_None); + Py_None, Py_None); Py_DECREF(column_name); + Py_DECREF(column_decltype); if (descriptor == NULL) { goto error; } diff --git a/test/dbapi.py b/test/dbapi.py index 68fe46d..6febf85 100644 --- a/test/dbapi.py +++ b/test/dbapi.py @@ -1017,6 +1017,32 @@ class ClosedCurTests(unittest.TestCase): method(*params) +class SqliteColumnTypeTests(unittest.TestCase): + def setUp(self): + self.cx = sqlite.connect(':memory:') + self.cx.execute('create table test(a text, b datetime)') + self.cx.execute('create view test_view as select * from test') + + def CheckDeclTypes(self): + curs = self.cx.execute('select * from test') + self.assertEqual(curs.description, ( + ('a', 'TEXT', None, None, None, None, None), + ('b', 'datetime', None, None, None, None, None), + )) + + curs = self.cx.execute('select * from test_view') + self.assertEqual(curs.description, ( + ('a', 'TEXT', None, None, None, None, None), + ('b', 'datetime', None, None, None, None, None), + )) + + # Expressions return NULL decltype, reported as None. + curs = self.cx.execute('select b + b as c from test_view') + self.assertEqual(curs.description, ( + ('c', None, None, None, None, None, None), + )) + + class SqliteOnConflictTests(unittest.TestCase): """ Tests for SQLite's "insert on conflict" feature. @@ -1181,6 +1207,7 @@ def suite(): ext_suite = unittest.makeSuite(ExtensionTests, "Check") closed_con_suite = unittest.makeSuite(ClosedConTests, "Check") closed_cur_suite = unittest.makeSuite(ClosedCurTests, "Check") + columntypes_suite = unittest.makeSuite(SqliteColumnTypeTests, "Check") on_conflict_suite = unittest.makeSuite(SqliteOnConflictTests, "Check") blob_suite = unittest.makeSuite(BlobTests, "Check") closed_blob_suite = unittest.makeSuite(ClosedBlobTests, "Check") @@ -1188,7 +1215,7 @@ def suite(): return unittest.TestSuite(( module_suite, connection_suite, cursor_suite, thread_suite, constructor_suite, ext_suite, closed_con_suite, closed_cur_suite, - on_conflict_suite, blob_suite, closed_blob_suite, + columntypes_suite, on_conflict_suite, blob_suite, closed_blob_suite, blob_context_manager_suite, ))