Skip to content

Commit

Permalink
bpo-40744: Drop support for SQLite pre 3.7.3 (GH-20909)
Browse files Browse the repository at this point in the history
Remove code required to support SQLite pre 3.7.3.

Co-written-by: Berker Peksag <berker.peksag@gmail.com>
Co-written-by: Sergey Fedoseev <fedoseev.sergey@gmail.com>
  • Loading branch information
Erlend Egeberg Aasland authored Sep 7, 2020
1 parent 22748a8 commit 207c321
Show file tree
Hide file tree
Showing 12 changed files with 29 additions and 94 deletions.
8 changes: 2 additions & 6 deletions Doc/library/sqlite3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -591,8 +592,6 @@ Connection Objects
dest = sqlite3.connect(':memory:')
source.backup(dest)

Availability: SQLite 3.6.11 or higher

.. versionadded:: 3.7


Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
=============
Expand Down
1 change: 0 additions & 1 deletion Lib/sqlite3/test/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
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:")
Expand Down
6 changes: 0 additions & 6 deletions Lib/sqlite3/test/dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,6 @@ def CheckOpenUri(self):
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):
Expand Down
6 changes: 0 additions & 6 deletions Lib/sqlite3/test/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ def upper(self):
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
Expand Down Expand Up @@ -240,16 +238,12 @@ def trace(statement):
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 = []
Expand Down
1 change: 0 additions & 1 deletion Lib/sqlite3/test/regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ def CheckStatementFinalizationOnCloseDb(self):
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)")
Expand Down
4 changes: 0 additions & 4 deletions Lib/sqlite3/test/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,12 @@ def CheckToggleAutoCommit(self):
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
Expand Down
2 changes: 0 additions & 2 deletions Lib/sqlite3/test/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,6 @@ def CheckSqliteTimestamp(self):
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)")
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
57 changes: 9 additions & 48 deletions Modules/_sqlite/connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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[] = {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}
Expand All @@ -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:
Expand All @@ -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;

Expand All @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1513,7 +1478,6 @@ pysqlite_connection_iterdump(pysqlite_Connection* self, PyObject* args)
return retval;
}

#ifdef HAVE_BACKUP_API
static PyObject *
pysqlite_connection_backup(pysqlite_Connection *self, PyObject *args, PyObject *kwds)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
25 changes: 7 additions & 18 deletions Modules/_sqlite/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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}
};

Expand Down Expand Up @@ -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 ||
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -1452,7 +1452,6 @@ def detect_sqlite(self):
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',
Expand All @@ -1463,7 +1462,8 @@ def detect_sqlite(self):
]
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])

Expand Down

0 comments on commit 207c321

Please sign in to comment.