From 207c321f13cea3fee7f378057864e8c6453f5adf Mon Sep 17 00:00:00 2001 From: Erlend Egeberg Aasland Date: Mon, 7 Sep 2020 23:26:54 +0200 Subject: [PATCH] bpo-40744: Drop support for SQLite pre 3.7.3 (GH-20909) Remove code required to support SQLite pre 3.7.3. Co-written-by: Berker Peksag Co-written-by: Sergey Fedoseev --- Doc/library/sqlite3.rst | 8 +-- Doc/whatsnew/3.10.rst | 5 ++ Lib/sqlite3/test/backup.py | 1 - Lib/sqlite3/test/dbapi.py | 6 -- Lib/sqlite3/test/hooks.py | 6 -- Lib/sqlite3/test/regression.py | 1 - Lib/sqlite3/test/transactions.py | 4 -- Lib/sqlite3/test/types.py | 2 - .../2020-05-30-08-10-23.bpo-40744.jKURVV.rst | 4 ++ Modules/_sqlite/connection.c | 57 +++---------------- Modules/_sqlite/module.c | 25 +++----- setup.py | 4 +- 12 files changed, 29 insertions(+), 94 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-05-30-08-10-23.bpo-40744.jKURVV.rst diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index ccb82278bda..13aa8c512d0 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -18,7 +18,8 @@ application using SQLite and then port the code to a larger database such as PostgreSQL or Oracle. The sqlite3 module was written by Gerhard Häring. It provides a SQL interface -compliant with the DB-API 2.0 specification described by :pep:`249`. +compliant with the DB-API 2.0 specification described by :pep:`249`, and +requires SQLite 3.7.3 or newer. To use the module, you must first create a :class:`Connection` object that represents the database. Here the data will be stored in the @@ -591,8 +592,6 @@ Connection Objects dest = sqlite3.connect(':memory:') source.backup(dest) - Availability: SQLite 3.6.11 or higher - .. versionadded:: 3.7 @@ -701,9 +700,6 @@ Cursor Objects statements because we cannot determine the number of rows a query produced until all rows were fetched. - With SQLite versions before 3.6.5, :attr:`rowcount` is set to 0 if - you make a ``DELETE FROM table`` without any condition. - .. attribute:: lastrowid This read-only attribute provides the rowid of the last modified row. It is diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index eb5ae01a7c0..f6f276a8bfa 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -191,10 +191,15 @@ that may require changes to your code. Build Changes ============= + * The C99 functions :c:func:`snprintf` and :c:func:`vsnprintf` are now required to build Python. (Contributed by Victor Stinner in :issue:`36020`.) +* :mod:`sqlite3` requires SQLite 3.7.3 or higher. + (Contributed by Sergey Fedoseev and Erlend E. Aasland :issue:`40744`.) + + C API Changes ============= diff --git a/Lib/sqlite3/test/backup.py b/Lib/sqlite3/test/backup.py index 903bacf4903..2752a4db337 100644 --- a/Lib/sqlite3/test/backup.py +++ b/Lib/sqlite3/test/backup.py @@ -2,7 +2,6 @@ import sqlite3 as sqlite import unittest -@unittest.skipIf(sqlite.sqlite_version_info < (3, 6, 11), "Backup API not supported") class BackupTests(unittest.TestCase): def setUp(self): cx = self.cx = sqlite.connect(":memory:") diff --git a/Lib/sqlite3/test/dbapi.py b/Lib/sqlite3/test/dbapi.py index 119da121703..a8dfeb9b2d6 100644 --- a/Lib/sqlite3/test/dbapi.py +++ b/Lib/sqlite3/test/dbapi.py @@ -185,12 +185,6 @@ class ConnectionTests(unittest.TestCase): with self.assertRaises(sqlite.OperationalError): cx.execute('insert into test(id) values(1)') - @unittest.skipIf(sqlite.sqlite_version_info >= (3, 3, 1), - 'needs sqlite versions older than 3.3.1') - def CheckSameThreadErrorOnOldVersion(self): - with self.assertRaises(sqlite.NotSupportedError) as cm: - sqlite.connect(':memory:', check_same_thread=False) - self.assertEqual(str(cm.exception), 'shared connections not available') class CursorTests(unittest.TestCase): def setUp(self): diff --git a/Lib/sqlite3/test/hooks.py b/Lib/sqlite3/test/hooks.py index b08adf1d809..2e620ecdf86 100644 --- a/Lib/sqlite3/test/hooks.py +++ b/Lib/sqlite3/test/hooks.py @@ -61,8 +61,6 @@ class CollationTests(unittest.TestCase): self.assertEqual(result[0][0], 'b') self.assertEqual(result[1][0], 'a') - @unittest.skipIf(sqlite.sqlite_version_info < (3, 2, 1), - 'old SQLite versions crash on this test') def CheckCollationIsUsed(self): def mycoll(x, y): # reverse order @@ -240,16 +238,12 @@ class TraceCallbackTests(unittest.TestCase): traced_statements.append(statement) con.set_trace_callback(trace) con.execute("create table foo(x)") - # Can't execute bound parameters as their values don't appear - # in traced statements before SQLite 3.6.21 - # (cf. http://www.sqlite.org/draft/releaselog/3_6_21.html) con.execute('insert into foo(x) values ("%s")' % unicode_value) con.commit() self.assertTrue(any(unicode_value in stmt for stmt in traced_statements), "Unicode data %s garbled in trace callback: %s" % (ascii(unicode_value), ', '.join(map(ascii, traced_statements)))) - @unittest.skipIf(sqlite.sqlite_version_info < (3, 3, 9), "sqlite3_prepare_v2 is not available") def CheckTraceCallbackContent(self): # set_trace_callback() shouldn't produce duplicate content (bpo-26187) traced_statements = [] diff --git a/Lib/sqlite3/test/regression.py b/Lib/sqlite3/test/regression.py index cbd46d4978a..0735a5c1292 100644 --- a/Lib/sqlite3/test/regression.py +++ b/Lib/sqlite3/test/regression.py @@ -87,7 +87,6 @@ class RegressionTests(unittest.TestCase): cur.execute("select 1 x union select " + str(i)) con.close() - @unittest.skipIf(sqlite.sqlite_version_info < (3, 2, 2), 'needs sqlite 3.2.2 or newer') def CheckOnConflictRollback(self): con = sqlite.connect(":memory:") con.execute("create table foo(x, unique(x) on conflict rollback)") diff --git a/Lib/sqlite3/test/transactions.py b/Lib/sqlite3/test/transactions.py index b8a13de55bc..c463f7490da 100644 --- a/Lib/sqlite3/test/transactions.py +++ b/Lib/sqlite3/test/transactions.py @@ -111,16 +111,12 @@ class TransactionTests(unittest.TestCase): res = self.cur2.fetchall() self.assertEqual(len(res), 1) - @unittest.skipIf(sqlite.sqlite_version_info < (3, 2, 2), - 'test hangs on sqlite versions older than 3.2.2') def CheckRaiseTimeout(self): self.cur1.execute("create table test(i)") self.cur1.execute("insert into test(i) values (5)") with self.assertRaises(sqlite.OperationalError): self.cur2.execute("insert into test(i) values (5)") - @unittest.skipIf(sqlite.sqlite_version_info < (3, 2, 2), - 'test hangs on sqlite versions older than 3.2.2') def CheckLocking(self): """ This tests the improved concurrency with pysqlite 2.3.4. You needed diff --git a/Lib/sqlite3/test/types.py b/Lib/sqlite3/test/types.py index d26a9cb93f0..75a9d5601d5 100644 --- a/Lib/sqlite3/test/types.py +++ b/Lib/sqlite3/test/types.py @@ -401,8 +401,6 @@ class DateTimeTests(unittest.TestCase): ts2 = self.cur.fetchone()[0] self.assertEqual(ts, ts2) - @unittest.skipIf(sqlite.sqlite_version_info < (3, 1), - 'the date functions are available on 3.1 or later') def CheckSqlTimestamp(self): now = datetime.datetime.utcnow() self.cur.execute("insert into test(ts) values (current_timestamp)") diff --git a/Misc/NEWS.d/next/Library/2020-05-30-08-10-23.bpo-40744.jKURVV.rst b/Misc/NEWS.d/next/Library/2020-05-30-08-10-23.bpo-40744.jKURVV.rst new file mode 100644 index 00000000000..2d1d1f9a20e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-05-30-08-10-23.bpo-40744.jKURVV.rst @@ -0,0 +1,4 @@ +The :mod:`sqlite3` module uses SQLite API functions that require SQLite +v3.7.3 or higher. This patch removes support for older SQLite versions, and +explicitly requires SQLite 3.7.3 both at build, compile and runtime. Patch by +Sergey Fedoseev and Erlend E. Aasland. diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 1bf9710763a..f765ba1df24 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -33,16 +33,6 @@ #define ACTION_FINALIZE 1 #define ACTION_RESET 2 -#if SQLITE_VERSION_NUMBER >= 3003008 -#ifndef SQLITE_OMIT_LOAD_EXTENSION -#define HAVE_LOAD_EXTENSION -#endif -#endif - -#if SQLITE_VERSION_NUMBER >= 3006011 -#define HAVE_BACKUP_API -#endif - #if SQLITE_VERSION_NUMBER >= 3014000 #define HAVE_TRACE_V2 #endif @@ -61,18 +51,6 @@ static int pysqlite_connection_set_isolation_level(pysqlite_Connection* self, Py static void _pysqlite_drop_unused_cursor_references(pysqlite_Connection* self); -static void _sqlite3_result_error(sqlite3_context* ctx, const char* errmsg, int len) -{ - /* in older SQLite versions, calling sqlite3_result_error in callbacks - * triggers a bug in SQLite that leads either to irritating results or - * segfaults, depending on the SQLite version */ -#if SQLITE_VERSION_NUMBER >= 3003003 - sqlite3_result_error(ctx, errmsg, len); -#else - PyErr_SetString(pysqlite_OperationalError, errmsg); -#endif -} - int pysqlite_connection_init(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) { static char *kwlist[] = { @@ -182,10 +160,6 @@ int pysqlite_connection_init(pysqlite_Connection* self, PyObject* args, PyObject self->timeout = timeout; (void)sqlite3_busy_timeout(self->db, (int)(timeout*1000)); self->thread_ident = PyThread_get_thread_ident(); - if (!check_same_thread && sqlite3_libversion_number() < 3003001) { - PyErr_SetString(pysqlite_NotSupportedError, "shared connections not available"); - return -1; - } self->check_same_thread = check_same_thread; self->function_pinboard_trace_callback = NULL; @@ -620,7 +594,7 @@ void _pysqlite_func_callback(sqlite3_context* context, int argc, sqlite3_value** } else { PyErr_Clear(); } - _sqlite3_result_error(context, "user-defined function raised exception", -1); + sqlite3_result_error(context, "user-defined function raised exception", -1); } PyGILState_Release(threadstate); @@ -652,7 +626,7 @@ static void _pysqlite_step_callback(sqlite3_context *context, int argc, sqlite3_ } else { PyErr_Clear(); } - _sqlite3_result_error(context, "user-defined aggregate's '__init__' method raised error", -1); + sqlite3_result_error(context, "user-defined aggregate's '__init__' method raised error", -1); goto error; } } @@ -676,7 +650,7 @@ static void _pysqlite_step_callback(sqlite3_context *context, int argc, sqlite3_ } else { PyErr_Clear(); } - _sqlite3_result_error(context, "user-defined aggregate's 'step' method raised error", -1); + sqlite3_result_error(context, "user-defined aggregate's 'step' method raised error", -1); } error: @@ -693,7 +667,6 @@ void _pysqlite_final_callback(sqlite3_context* context) _Py_IDENTIFIER(finalize); int ok; PyObject *exception, *value, *tb; - int restore; PyGILState_STATE threadstate; @@ -709,7 +682,6 @@ void _pysqlite_final_callback(sqlite3_context* context) /* Keep the exception (if any) of the last call to step() */ PyErr_Fetch(&exception, &value, &tb); - restore = 1; function_result = _PyObject_CallMethodIdNoArgs(*aggregate_instance, &PyId_finalize); @@ -726,19 +698,12 @@ void _pysqlite_final_callback(sqlite3_context* context) } else { PyErr_Clear(); } - _sqlite3_result_error(context, "user-defined aggregate's 'finalize' method raised error", -1); -#if SQLITE_VERSION_NUMBER < 3003003 - /* with old SQLite versions, _sqlite3_result_error() sets a new Python - exception, so don't restore the previous exception */ - restore = 0; -#endif + sqlite3_result_error(context, "user-defined aggregate's 'finalize' method raised error", -1); } - if (restore) { - /* Restore the exception (if any) of the last call to step(), - but clear also the current exception if finalize() failed */ - PyErr_Restore(exception, value, tb); - } + /* Restore the exception (if any) of the last call to step(), + but clear also the current exception if finalize() failed */ + PyErr_Restore(exception, value, tb); error: PyGILState_Release(threadstate); @@ -1110,7 +1075,7 @@ static PyObject* pysqlite_connection_set_trace_callback(pysqlite_Connection* sel Py_RETURN_NONE; } -#ifdef HAVE_LOAD_EXTENSION +#ifndef SQLITE_OMIT_LOAD_EXTENSION static PyObject* pysqlite_enable_load_extension(pysqlite_Connection* self, PyObject* args) { int rc; @@ -1513,7 +1478,6 @@ finally: return retval; } -#ifdef HAVE_BACKUP_API static PyObject * pysqlite_connection_backup(pysqlite_Connection *self, PyObject *args, PyObject *kwds) { @@ -1664,7 +1628,6 @@ pysqlite_connection_backup(pysqlite_Connection *self, PyObject *args, PyObject * return NULL; } } -#endif static PyObject * pysqlite_connection_create_collation(pysqlite_Connection* self, PyObject* args) @@ -1816,7 +1779,7 @@ static PyMethodDef connection_methods[] = { PyDoc_STR("Creates a new aggregate. Non-standard.")}, {"set_authorizer", (PyCFunction)(void(*)(void))pysqlite_connection_set_authorizer, METH_VARARGS|METH_KEYWORDS, PyDoc_STR("Sets authorizer callback. Non-standard.")}, - #ifdef HAVE_LOAD_EXTENSION + #ifndef SQLITE_OMIT_LOAD_EXTENSION {"enable_load_extension", (PyCFunction)pysqlite_enable_load_extension, METH_VARARGS, PyDoc_STR("Enable dynamic loading of SQLite extension modules. Non-standard.")}, {"load_extension", (PyCFunction)pysqlite_load_extension, METH_VARARGS, @@ -1838,10 +1801,8 @@ static PyMethodDef connection_methods[] = { PyDoc_STR("Abort any pending database operation. Non-standard.")}, {"iterdump", (PyCFunction)pysqlite_connection_iterdump, METH_NOARGS, PyDoc_STR("Returns iterator to the dump of the database in an SQL text format. Non-standard.")}, - #ifdef HAVE_BACKUP_API {"backup", (PyCFunction)(void(*)(void))pysqlite_connection_backup, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("Makes a backup of the database. Non-standard.")}, - #endif {"__enter__", (PyCFunction)pysqlite_connection_enter, METH_NOARGS, PyDoc_STR("For context manager. Non-standard.")}, {"__exit__", (PyCFunction)pysqlite_connection_exit, METH_VARARGS, diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index 71d951ee887..82f58eb2480 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -29,8 +29,8 @@ #include "microprotocols.h" #include "row.h" -#if SQLITE_VERSION_NUMBER >= 3003003 -#define HAVE_SHARED_CACHE +#if SQLITE_VERSION_NUMBER < 3007003 +#error "SQLite 3.7.3 or higher required" #endif /* static objects at module-level */ @@ -131,7 +131,6 @@ PyDoc_STRVAR(module_complete_doc, \n\ Checks if a string contains a complete SQL statement. Non-standard."); -#ifdef HAVE_SHARED_CACHE static PyObject* module_enable_shared_cache(PyObject* self, PyObject* args, PyObject* kwargs) { @@ -159,7 +158,6 @@ PyDoc_STRVAR(module_enable_shared_cache_doc, \n\ Enable or disable shared cache mode for the calling thread.\n\ Experimental/Non-standard."); -#endif /* HAVE_SHARED_CACHE */ static PyObject* module_register_adapter(PyObject* self, PyObject* args) { @@ -253,10 +251,8 @@ static PyMethodDef module_methods[] = { METH_VARARGS | METH_KEYWORDS, module_connect_doc}, {"complete_statement", (PyCFunction)(void(*)(void))module_complete, METH_VARARGS | METH_KEYWORDS, module_complete_doc}, -#ifdef HAVE_SHARED_CACHE {"enable_shared_cache", (PyCFunction)(void(*)(void))module_enable_shared_cache, METH_VARARGS | METH_KEYWORDS, module_enable_shared_cache_doc}, -#endif {"register_adapter", (PyCFunction)module_register_adapter, METH_VARARGS, module_register_adapter_doc}, {"register_converter", (PyCFunction)module_register_converter, @@ -307,29 +303,17 @@ static const IntConstantPair _int_constants[] = { {"SQLITE_UPDATE", SQLITE_UPDATE}, {"SQLITE_ATTACH", SQLITE_ATTACH}, {"SQLITE_DETACH", SQLITE_DETACH}, -#if SQLITE_VERSION_NUMBER >= 3002001 {"SQLITE_ALTER_TABLE", SQLITE_ALTER_TABLE}, {"SQLITE_REINDEX", SQLITE_REINDEX}, -#endif -#if SQLITE_VERSION_NUMBER >= 3003000 {"SQLITE_ANALYZE", SQLITE_ANALYZE}, -#endif -#if SQLITE_VERSION_NUMBER >= 3003007 {"SQLITE_CREATE_VTABLE", SQLITE_CREATE_VTABLE}, {"SQLITE_DROP_VTABLE", SQLITE_DROP_VTABLE}, -#endif -#if SQLITE_VERSION_NUMBER >= 3003008 {"SQLITE_FUNCTION", SQLITE_FUNCTION}, -#endif -#if SQLITE_VERSION_NUMBER >= 3006008 {"SQLITE_SAVEPOINT", SQLITE_SAVEPOINT}, -#endif #if SQLITE_VERSION_NUMBER >= 3008003 {"SQLITE_RECURSIVE", SQLITE_RECURSIVE}, #endif -#if SQLITE_VERSION_NUMBER >= 3006011 {"SQLITE_DONE", SQLITE_DONE}, -#endif {(char*)NULL, 0} }; @@ -360,6 +344,11 @@ PyMODINIT_FUNC PyInit__sqlite3(void) PyObject *tmp_obj; int i; + if (sqlite3_libversion_number() < 3007003) { + PyErr_SetString(PyExc_ImportError, MODULE_NAME ": SQLite 3.7.3 or higher required"); + return NULL; + } + module = PyModule_Create(&_sqlite3module); if (!module || diff --git a/setup.py b/setup.py index 21a5a58981f..04b1358bc91 100644 --- a/setup.py +++ b/setup.py @@ -1452,7 +1452,6 @@ class PyBuildExt(build_ext): sqlite_setup_debug = False # verbose debug prints from this script? # We hunt for #define SQLITE_VERSION "n.n.n" - # We need to find >= sqlite version 3.3.9, for sqlite3_prepare_v2 sqlite_incdir = sqlite_libdir = None sqlite_inc_paths = [ '/usr/include', '/usr/include/sqlite', @@ -1463,7 +1462,8 @@ class PyBuildExt(build_ext): ] if CROSS_COMPILING: sqlite_inc_paths = [] - MIN_SQLITE_VERSION_NUMBER = (3, 7, 2) + # We need to find >= sqlite version 3.7.3, for sqlite3_create_function_v2() + MIN_SQLITE_VERSION_NUMBER = (3, 7, 3) MIN_SQLITE_VERSION = ".".join([str(x) for x in MIN_SQLITE_VERSION_NUMBER])