bpo-45243: Add support for setting/getting sqlite3 connection limits (GH-28463)

This commit is contained in:
Erlend Egeberg Aasland 2021-11-01 23:50:53 +01:00 committed by GitHub
parent e2063d6a1e
commit b6b38a8226
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 228 additions and 1 deletions

View file

@ -662,6 +662,40 @@ Connection Objects
.. versionadded:: 3.7
.. method:: getlimit(category, /)
Get a connection run-time limit. *category* is the limit category to be
queried.
Example, query the maximum length of an SQL statement::
import sqlite3
con = sqlite3.connect(":memory:")
lim = con.getlimit(sqlite3.SQLITE_LIMIT_SQL_LENGTH)
print(f"SQLITE_LIMIT_SQL_LENGTH={lim}")
.. versionadded:: 3.11
.. method:: setlimit(category, limit, /)
Set a connection run-time limit. *category* is the limit category to be
set. *limit* is the new limit. If the new limit is a negative number, the
limit is unchanged.
Attempts to increase a limit above its hard upper bound are silently
truncated to the hard upper bound. Regardless of whether or not the limit
was changed, the prior value of the limit is returned.
Example, limit the number of attached databases to 1::
import sqlite3
con = sqlite3.connect(":memory:")
con.setlimit(sqlite3.SQLITE_LIMIT_ATTACHED, 1)
.. versionadded:: 3.11
.. _sqlite3-cursor-objects:
Cursor Objects

View file

@ -248,6 +248,12 @@ sqlite3
(Contributed by Aviv Palivoda, Daniel Shahaf, and Erlend E. Aasland in
:issue:`16379`.)
* Add :meth:`~sqlite3.Connection.setlimit` and
:meth:`~sqlite3.Connection.getlimit` to :class:`sqlite3.Connection` for
setting and getting SQLite limits by connection basis.
(Contributed by Erlend E. Aasland in :issue:`45243`.)
threading
---------

View file

@ -167,11 +167,25 @@ def test_module_constants(self):
"SQLITE_TOOBIG",
"SQLITE_TRANSACTION",
"SQLITE_UPDATE",
# Run-time limit categories
"SQLITE_LIMIT_LENGTH",
"SQLITE_LIMIT_SQL_LENGTH",
"SQLITE_LIMIT_COLUMN",
"SQLITE_LIMIT_EXPR_DEPTH",
"SQLITE_LIMIT_COMPOUND_SELECT",
"SQLITE_LIMIT_VDBE_OP",
"SQLITE_LIMIT_FUNCTION_ARG",
"SQLITE_LIMIT_ATTACHED",
"SQLITE_LIMIT_LIKE_PATTERN_LENGTH",
"SQLITE_LIMIT_VARIABLE_NUMBER",
"SQLITE_LIMIT_TRIGGER_DEPTH",
]
if sqlite.sqlite_version_info >= (3, 7, 17):
consts += ["SQLITE_NOTICE", "SQLITE_WARNING"]
if sqlite.sqlite_version_info >= (3, 8, 3):
consts.append("SQLITE_RECURSIVE")
if sqlite.sqlite_version_info >= (3, 8, 7):
consts.append("SQLITE_LIMIT_WORKER_THREADS")
consts += ["PARSE_DECLTYPES", "PARSE_COLNAMES"]
for const in consts:
with self.subTest(const=const):
@ -332,6 +346,28 @@ def test_drop_unused_refs(self):
cu = self.cx.execute(f"select {n}")
self.assertEqual(cu.fetchone()[0], n)
def test_connection_limits(self):
category = sqlite.SQLITE_LIMIT_SQL_LENGTH
saved_limit = self.cx.getlimit(category)
try:
new_limit = 10
prev_limit = self.cx.setlimit(category, new_limit)
self.assertEqual(saved_limit, prev_limit)
self.assertEqual(self.cx.getlimit(category), new_limit)
msg = "string or blob too big"
self.assertRaisesRegex(sqlite.DataError, msg,
self.cx.execute, "select 1 as '16'")
finally: # restore saved limit
self.cx.setlimit(category, saved_limit)
def test_connection_bad_limit_category(self):
msg = "'category' is out of bounds"
cat = 1111
self.assertRaisesRegex(sqlite.ProgrammingError, msg,
self.cx.getlimit, cat)
self.assertRaisesRegex(sqlite.ProgrammingError, msg,
self.cx.setlimit, cat, 0)
class UninitialisedConnectionTests(unittest.TestCase):
def setUp(self):
@ -767,6 +803,8 @@ def test_check_connection_thread(self):
lambda: self.con.set_trace_callback(None),
lambda: self.con.set_authorizer(None),
lambda: self.con.create_collation("foo", None),
lambda: self.con.setlimit(sqlite.SQLITE_LIMIT_LENGTH, -1),
lambda: self.con.getlimit(sqlite.SQLITE_LIMIT_LENGTH),
]
for fn in fns:
with self.subTest(fn=fn):

View file

@ -0,0 +1,4 @@
Add :meth:`~sqlite3.Connection.setlimit` and
:meth:`~sqlite3.Connection.getlimit` to :class:`sqlite3.Connection` for
setting and getting SQLite limits by connection basis. Patch by Erlend E.
Aasland.

View file

@ -750,6 +750,83 @@ exit:
return return_value;
}
PyDoc_STRVAR(setlimit__doc__,
"setlimit($self, category, limit, /)\n"
"--\n"
"\n"
"Set connection run-time limits.\n"
"\n"
" category\n"
" The limit category to be set.\n"
" limit\n"
" The new limit. If the new limit is a negative number, the limit is\n"
" unchanged.\n"
"\n"
"Attempts to increase a limit above its hard upper bound are silently truncated\n"
"to the hard upper bound. Regardless of whether or not the limit was changed,\n"
"the prior value of the limit is returned.");
#define SETLIMIT_METHODDEF \
{"setlimit", (PyCFunction)(void(*)(void))setlimit, METH_FASTCALL, setlimit__doc__},
static PyObject *
setlimit_impl(pysqlite_Connection *self, int category, int limit);
static PyObject *
setlimit(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
int category;
int limit;
if (!_PyArg_CheckPositional("setlimit", nargs, 2, 2)) {
goto exit;
}
category = _PyLong_AsInt(args[0]);
if (category == -1 && PyErr_Occurred()) {
goto exit;
}
limit = _PyLong_AsInt(args[1]);
if (limit == -1 && PyErr_Occurred()) {
goto exit;
}
return_value = setlimit_impl(self, category, limit);
exit:
return return_value;
}
PyDoc_STRVAR(getlimit__doc__,
"getlimit($self, category, /)\n"
"--\n"
"\n"
"Get connection run-time limits.\n"
"\n"
" category\n"
" The limit category to be queried.");
#define GETLIMIT_METHODDEF \
{"getlimit", (PyCFunction)getlimit, METH_O, getlimit__doc__},
static PyObject *
getlimit_impl(pysqlite_Connection *self, int category);
static PyObject *
getlimit(pysqlite_Connection *self, PyObject *arg)
{
PyObject *return_value = NULL;
int category;
category = _PyLong_AsInt(arg);
if (category == -1 && PyErr_Occurred()) {
goto exit;
}
return_value = getlimit_impl(self, category);
exit:
return return_value;
}
#ifndef PYSQLITE_CONNECTION_ENABLE_LOAD_EXTENSION_METHODDEF
#define PYSQLITE_CONNECTION_ENABLE_LOAD_EXTENSION_METHODDEF
#endif /* !defined(PYSQLITE_CONNECTION_ENABLE_LOAD_EXTENSION_METHODDEF) */
@ -757,4 +834,4 @@ exit:
#ifndef PYSQLITE_CONNECTION_LOAD_EXTENSION_METHODDEF
#define PYSQLITE_CONNECTION_LOAD_EXTENSION_METHODDEF
#endif /* !defined(PYSQLITE_CONNECTION_LOAD_EXTENSION_METHODDEF) */
/*[clinic end generated code: output=7567e5d716309258 input=a9049054013a1b77]*/
/*[clinic end generated code: output=0c3901153a3837a5 input=a9049054013a1b77]*/

View file

@ -1896,6 +1896,57 @@ pysqlite_connection_exit_impl(pysqlite_Connection *self, PyObject *exc_type,
Py_RETURN_FALSE;
}
/*[clinic input]
_sqlite3.Connection.setlimit as setlimit
category: int
The limit category to be set.
limit: int
The new limit. If the new limit is a negative number, the limit is
unchanged.
/
Set connection run-time limits.
Attempts to increase a limit above its hard upper bound are silently truncated
to the hard upper bound. Regardless of whether or not the limit was changed,
the prior value of the limit is returned.
[clinic start generated code]*/
static PyObject *
setlimit_impl(pysqlite_Connection *self, int category, int limit)
/*[clinic end generated code: output=0d208213f8d68ccd input=9bd469537e195635]*/
{
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
return NULL;
}
int old_limit = sqlite3_limit(self->db, category, limit);
if (old_limit < 0) {
PyErr_SetString(self->ProgrammingError, "'category' is out of bounds");
return NULL;
}
return PyLong_FromLong(old_limit);
}
/*[clinic input]
_sqlite3.Connection.getlimit as getlimit
category: int
The limit category to be queried.
/
Get connection run-time limits.
[clinic start generated code]*/
static PyObject *
getlimit_impl(pysqlite_Connection *self, int category)
/*[clinic end generated code: output=7c3f5d11f24cecb1 input=61e0849fb4fb058f]*/
{
return setlimit_impl(self, category, -1);
}
static const char connection_doc[] =
PyDoc_STR("SQLite database connection object.");
@ -1927,6 +1978,8 @@ static PyMethodDef connection_methods[] = {
PYSQLITE_CONNECTION_SET_AUTHORIZER_METHODDEF
PYSQLITE_CONNECTION_SET_PROGRESS_HANDLER_METHODDEF
PYSQLITE_CONNECTION_SET_TRACE_CALLBACK_METHODDEF
SETLIMIT_METHODDEF
GETLIMIT_METHODDEF
{NULL, NULL}
};

View file

@ -395,6 +395,21 @@ add_integer_constants(PyObject *module) {
ADD_INT(SQLITE_SAVEPOINT);
#if SQLITE_VERSION_NUMBER >= 3008003
ADD_INT(SQLITE_RECURSIVE);
#endif
// Run-time limit categories
ADD_INT(SQLITE_LIMIT_LENGTH);
ADD_INT(SQLITE_LIMIT_SQL_LENGTH);
ADD_INT(SQLITE_LIMIT_COLUMN);
ADD_INT(SQLITE_LIMIT_EXPR_DEPTH);
ADD_INT(SQLITE_LIMIT_COMPOUND_SELECT);
ADD_INT(SQLITE_LIMIT_VDBE_OP);
ADD_INT(SQLITE_LIMIT_FUNCTION_ARG);
ADD_INT(SQLITE_LIMIT_ATTACHED);
ADD_INT(SQLITE_LIMIT_LIKE_PATTERN_LENGTH);
ADD_INT(SQLITE_LIMIT_VARIABLE_NUMBER);
ADD_INT(SQLITE_LIMIT_TRIGGER_DEPTH);
#if SQLITE_VERSION_NUMBER >= 3008007
ADD_INT(SQLITE_LIMIT_WORKER_THREADS);
#endif
#undef ADD_INT
return 0;