diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 19a4155542382f..dcffc779cfba3d 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -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 diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 156bfbd9109ee8..3500b1ba6b1cbd 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -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 --------- diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index d82543663d18bb..34895be018078f 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -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): diff --git a/Misc/NEWS.d/next/Library/2021-09-20-01-25-09.bpo-45243.0pJf0U.rst b/Misc/NEWS.d/next/Library/2021-09-20-01-25-09.bpo-45243.0pJf0U.rst new file mode 100644 index 00000000000000..8292e86245f024 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-09-20-01-25-09.bpo-45243.0pJf0U.rst @@ -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. diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index e9e3064ae0f899..f9323eb21d6f45 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -750,6 +750,83 @@ pysqlite_connection_exit(pysqlite_Connection *self, PyObject *const *args, Py_ss 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 @@ pysqlite_connection_exit(pysqlite_Connection *self, PyObject *const *args, Py_ss #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]*/ diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 94c38ad3954405..f913267e1560e8 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -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} }; diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index 912851ba775dc0..8666af171c8cb5 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -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;