Skip to content

Commit b6b38a8

Browse files
author
Erlend Egeberg Aasland
authored
bpo-45243: Add support for setting/getting sqlite3 connection limits (GH-28463)
1 parent e2063d6 commit b6b38a8

File tree

7 files changed

+228
-1
lines changed

7 files changed

+228
-1
lines changed

Doc/library/sqlite3.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,40 @@ Connection Objects
662662
.. versionadded:: 3.7
663663

664664

665+
.. method:: getlimit(category, /)
666+
667+
Get a connection run-time limit. *category* is the limit category to be
668+
queried.
669+
670+
Example, query the maximum length of an SQL statement::
671+
672+
import sqlite3
673+
con = sqlite3.connect(":memory:")
674+
lim = con.getlimit(sqlite3.SQLITE_LIMIT_SQL_LENGTH)
675+
print(f"SQLITE_LIMIT_SQL_LENGTH={lim}")
676+
677+
.. versionadded:: 3.11
678+
679+
680+
.. method:: setlimit(category, limit, /)
681+
682+
Set a connection run-time limit. *category* is the limit category to be
683+
set. *limit* is the new limit. If the new limit is a negative number, the
684+
limit is unchanged.
685+
686+
Attempts to increase a limit above its hard upper bound are silently
687+
truncated to the hard upper bound. Regardless of whether or not the limit
688+
was changed, the prior value of the limit is returned.
689+
690+
Example, limit the number of attached databases to 1::
691+
692+
import sqlite3
693+
con = sqlite3.connect(":memory:")
694+
con.setlimit(sqlite3.SQLITE_LIMIT_ATTACHED, 1)
695+
696+
.. versionadded:: 3.11
697+
698+
665699
.. _sqlite3-cursor-objects:
666700

667701
Cursor Objects

Doc/whatsnew/3.11.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,12 @@ sqlite3
248248
(Contributed by Aviv Palivoda, Daniel Shahaf, and Erlend E. Aasland in
249249
:issue:`16379`.)
250250

251+
* Add :meth:`~sqlite3.Connection.setlimit` and
252+
:meth:`~sqlite3.Connection.getlimit` to :class:`sqlite3.Connection` for
253+
setting and getting SQLite limits by connection basis.
254+
(Contributed by Erlend E. Aasland in :issue:`45243`.)
255+
256+
251257
threading
252258
---------
253259

Lib/test/test_sqlite3/test_dbapi.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,25 @@ def test_module_constants(self):
167167
"SQLITE_TOOBIG",
168168
"SQLITE_TRANSACTION",
169169
"SQLITE_UPDATE",
170+
# Run-time limit categories
171+
"SQLITE_LIMIT_LENGTH",
172+
"SQLITE_LIMIT_SQL_LENGTH",
173+
"SQLITE_LIMIT_COLUMN",
174+
"SQLITE_LIMIT_EXPR_DEPTH",
175+
"SQLITE_LIMIT_COMPOUND_SELECT",
176+
"SQLITE_LIMIT_VDBE_OP",
177+
"SQLITE_LIMIT_FUNCTION_ARG",
178+
"SQLITE_LIMIT_ATTACHED",
179+
"SQLITE_LIMIT_LIKE_PATTERN_LENGTH",
180+
"SQLITE_LIMIT_VARIABLE_NUMBER",
181+
"SQLITE_LIMIT_TRIGGER_DEPTH",
170182
]
171183
if sqlite.sqlite_version_info >= (3, 7, 17):
172184
consts += ["SQLITE_NOTICE", "SQLITE_WARNING"]
173185
if sqlite.sqlite_version_info >= (3, 8, 3):
174186
consts.append("SQLITE_RECURSIVE")
187+
if sqlite.sqlite_version_info >= (3, 8, 7):
188+
consts.append("SQLITE_LIMIT_WORKER_THREADS")
175189
consts += ["PARSE_DECLTYPES", "PARSE_COLNAMES"]
176190
for const in consts:
177191
with self.subTest(const=const):
@@ -332,6 +346,28 @@ def test_drop_unused_refs(self):
332346
cu = self.cx.execute(f"select {n}")
333347
self.assertEqual(cu.fetchone()[0], n)
334348

349+
def test_connection_limits(self):
350+
category = sqlite.SQLITE_LIMIT_SQL_LENGTH
351+
saved_limit = self.cx.getlimit(category)
352+
try:
353+
new_limit = 10
354+
prev_limit = self.cx.setlimit(category, new_limit)
355+
self.assertEqual(saved_limit, prev_limit)
356+
self.assertEqual(self.cx.getlimit(category), new_limit)
357+
msg = "string or blob too big"
358+
self.assertRaisesRegex(sqlite.DataError, msg,
359+
self.cx.execute, "select 1 as '16'")
360+
finally: # restore saved limit
361+
self.cx.setlimit(category, saved_limit)
362+
363+
def test_connection_bad_limit_category(self):
364+
msg = "'category' is out of bounds"
365+
cat = 1111
366+
self.assertRaisesRegex(sqlite.ProgrammingError, msg,
367+
self.cx.getlimit, cat)
368+
self.assertRaisesRegex(sqlite.ProgrammingError, msg,
369+
self.cx.setlimit, cat, 0)
370+
335371

336372
class UninitialisedConnectionTests(unittest.TestCase):
337373
def setUp(self):
@@ -767,6 +803,8 @@ def test_check_connection_thread(self):
767803
lambda: self.con.set_trace_callback(None),
768804
lambda: self.con.set_authorizer(None),
769805
lambda: self.con.create_collation("foo", None),
806+
lambda: self.con.setlimit(sqlite.SQLITE_LIMIT_LENGTH, -1),
807+
lambda: self.con.getlimit(sqlite.SQLITE_LIMIT_LENGTH),
770808
]
771809
for fn in fns:
772810
with self.subTest(fn=fn):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add :meth:`~sqlite3.Connection.setlimit` and
2+
:meth:`~sqlite3.Connection.getlimit` to :class:`sqlite3.Connection` for
3+
setting and getting SQLite limits by connection basis. Patch by Erlend E.
4+
Aasland.

Modules/_sqlite/clinic/connection.c.h

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,11 +750,88 @@ pysqlite_connection_exit(pysqlite_Connection *self, PyObject *const *args, Py_ss
750750
return return_value;
751751
}
752752

753+
PyDoc_STRVAR(setlimit__doc__,
754+
"setlimit($self, category, limit, /)\n"
755+
"--\n"
756+
"\n"
757+
"Set connection run-time limits.\n"
758+
"\n"
759+
" category\n"
760+
" The limit category to be set.\n"
761+
" limit\n"
762+
" The new limit. If the new limit is a negative number, the limit is\n"
763+
" unchanged.\n"
764+
"\n"
765+
"Attempts to increase a limit above its hard upper bound are silently truncated\n"
766+
"to the hard upper bound. Regardless of whether or not the limit was changed,\n"
767+
"the prior value of the limit is returned.");
768+
769+
#define SETLIMIT_METHODDEF \
770+
{"setlimit", (PyCFunction)(void(*)(void))setlimit, METH_FASTCALL, setlimit__doc__},
771+
772+
static PyObject *
773+
setlimit_impl(pysqlite_Connection *self, int category, int limit);
774+
775+
static PyObject *
776+
setlimit(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs)
777+
{
778+
PyObject *return_value = NULL;
779+
int category;
780+
int limit;
781+
782+
if (!_PyArg_CheckPositional("setlimit", nargs, 2, 2)) {
783+
goto exit;
784+
}
785+
category = _PyLong_AsInt(args[0]);
786+
if (category == -1 && PyErr_Occurred()) {
787+
goto exit;
788+
}
789+
limit = _PyLong_AsInt(args[1]);
790+
if (limit == -1 && PyErr_Occurred()) {
791+
goto exit;
792+
}
793+
return_value = setlimit_impl(self, category, limit);
794+
795+
exit:
796+
return return_value;
797+
}
798+
799+
PyDoc_STRVAR(getlimit__doc__,
800+
"getlimit($self, category, /)\n"
801+
"--\n"
802+
"\n"
803+
"Get connection run-time limits.\n"
804+
"\n"
805+
" category\n"
806+
" The limit category to be queried.");
807+
808+
#define GETLIMIT_METHODDEF \
809+
{"getlimit", (PyCFunction)getlimit, METH_O, getlimit__doc__},
810+
811+
static PyObject *
812+
getlimit_impl(pysqlite_Connection *self, int category);
813+
814+
static PyObject *
815+
getlimit(pysqlite_Connection *self, PyObject *arg)
816+
{
817+
PyObject *return_value = NULL;
818+
int category;
819+
820+
category = _PyLong_AsInt(arg);
821+
if (category == -1 && PyErr_Occurred()) {
822+
goto exit;
823+
}
824+
return_value = getlimit_impl(self, category);
825+
826+
exit:
827+
return return_value;
828+
}
829+
753830
#ifndef PYSQLITE_CONNECTION_ENABLE_LOAD_EXTENSION_METHODDEF
754831
#define PYSQLITE_CONNECTION_ENABLE_LOAD_EXTENSION_METHODDEF
755832
#endif /* !defined(PYSQLITE_CONNECTION_ENABLE_LOAD_EXTENSION_METHODDEF) */
756833

757834
#ifndef PYSQLITE_CONNECTION_LOAD_EXTENSION_METHODDEF
758835
#define PYSQLITE_CONNECTION_LOAD_EXTENSION_METHODDEF
759836
#endif /* !defined(PYSQLITE_CONNECTION_LOAD_EXTENSION_METHODDEF) */
760-
/*[clinic end generated code: output=7567e5d716309258 input=a9049054013a1b77]*/
837+
/*[clinic end generated code: output=0c3901153a3837a5 input=a9049054013a1b77]*/

Modules/_sqlite/connection.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1896,6 +1896,57 @@ pysqlite_connection_exit_impl(pysqlite_Connection *self, PyObject *exc_type,
18961896
Py_RETURN_FALSE;
18971897
}
18981898

1899+
/*[clinic input]
1900+
_sqlite3.Connection.setlimit as setlimit
1901+
1902+
category: int
1903+
The limit category to be set.
1904+
limit: int
1905+
The new limit. If the new limit is a negative number, the limit is
1906+
unchanged.
1907+
/
1908+
1909+
Set connection run-time limits.
1910+
1911+
Attempts to increase a limit above its hard upper bound are silently truncated
1912+
to the hard upper bound. Regardless of whether or not the limit was changed,
1913+
the prior value of the limit is returned.
1914+
[clinic start generated code]*/
1915+
1916+
static PyObject *
1917+
setlimit_impl(pysqlite_Connection *self, int category, int limit)
1918+
/*[clinic end generated code: output=0d208213f8d68ccd input=9bd469537e195635]*/
1919+
{
1920+
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
1921+
return NULL;
1922+
}
1923+
1924+
int old_limit = sqlite3_limit(self->db, category, limit);
1925+
if (old_limit < 0) {
1926+
PyErr_SetString(self->ProgrammingError, "'category' is out of bounds");
1927+
return NULL;
1928+
}
1929+
return PyLong_FromLong(old_limit);
1930+
}
1931+
1932+
/*[clinic input]
1933+
_sqlite3.Connection.getlimit as getlimit
1934+
1935+
category: int
1936+
The limit category to be queried.
1937+
/
1938+
1939+
Get connection run-time limits.
1940+
[clinic start generated code]*/
1941+
1942+
static PyObject *
1943+
getlimit_impl(pysqlite_Connection *self, int category)
1944+
/*[clinic end generated code: output=7c3f5d11f24cecb1 input=61e0849fb4fb058f]*/
1945+
{
1946+
return setlimit_impl(self, category, -1);
1947+
}
1948+
1949+
18991950
static const char connection_doc[] =
19001951
PyDoc_STR("SQLite database connection object.");
19011952

@@ -1927,6 +1978,8 @@ static PyMethodDef connection_methods[] = {
19271978
PYSQLITE_CONNECTION_SET_AUTHORIZER_METHODDEF
19281979
PYSQLITE_CONNECTION_SET_PROGRESS_HANDLER_METHODDEF
19291980
PYSQLITE_CONNECTION_SET_TRACE_CALLBACK_METHODDEF
1981+
SETLIMIT_METHODDEF
1982+
GETLIMIT_METHODDEF
19301983
{NULL, NULL}
19311984
};
19321985

Modules/_sqlite/module.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,21 @@ add_integer_constants(PyObject *module) {
395395
ADD_INT(SQLITE_SAVEPOINT);
396396
#if SQLITE_VERSION_NUMBER >= 3008003
397397
ADD_INT(SQLITE_RECURSIVE);
398+
#endif
399+
// Run-time limit categories
400+
ADD_INT(SQLITE_LIMIT_LENGTH);
401+
ADD_INT(SQLITE_LIMIT_SQL_LENGTH);
402+
ADD_INT(SQLITE_LIMIT_COLUMN);
403+
ADD_INT(SQLITE_LIMIT_EXPR_DEPTH);
404+
ADD_INT(SQLITE_LIMIT_COMPOUND_SELECT);
405+
ADD_INT(SQLITE_LIMIT_VDBE_OP);
406+
ADD_INT(SQLITE_LIMIT_FUNCTION_ARG);
407+
ADD_INT(SQLITE_LIMIT_ATTACHED);
408+
ADD_INT(SQLITE_LIMIT_LIKE_PATTERN_LENGTH);
409+
ADD_INT(SQLITE_LIMIT_VARIABLE_NUMBER);
410+
ADD_INT(SQLITE_LIMIT_TRIGGER_DEPTH);
411+
#if SQLITE_VERSION_NUMBER >= 3008007
412+
ADD_INT(SQLITE_LIMIT_WORKER_THREADS);
398413
#endif
399414
#undef ADD_INT
400415
return 0;

0 commit comments

Comments
 (0)