diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index eaea7ae390b972..cfd196a1311818 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -624,6 +624,78 @@ Connection Objects .. versionadded:: 3.7 + .. attribute:: SQLITE_LIMIT_SQL_LENGTH + + The maximum length of an SQL statement, in bytes. + + .. versionadded:: 3.11 + + .. attribute:: SQLITE_LIMIT_COLUMN + + The maximum number of columns in a table definition or in the result set + of a SELECT or the maximum number of columns in an index or in an ORDER + BY or GROUP BY clause. + + .. versionadded:: 3.11 + + .. attribute:: SQLITE_LIMIT_EXPR_DEPTH + + The maximum depth of the parse tree on any expression. + + .. versionadded:: 3.11 + + .. attribute:: SQLITE_LIMIT_COMPOUND_SELECT + + The maximum number of terms in a compound SELECT statement. + + .. versionadded:: 3.11 + + .. attribute:: SQLITE_LIMIT_VDBE_OP + + The maximum number of instructions in a virtual machine program used to + implement an SQL statement. + + .. versionadded:: 3.11 + + .. attribute:: SQLITE_LIMIT_FUNCTION_ARG + + The maximum number of arguments on a function. + + .. versionadded:: 3.11 + + .. attribute:: SQLITE_LIMIT_ATTACHED + + The maximum number of attached databases. + + .. versionadded:: 3.11 + + .. attribute:: SQLITE_LIMIT_LIKE_PATTERN_LENGTH + + The maximum length of the pattern argument to the LIKE or GLOB operators. + + .. versionadded:: 3.11 + + ) + .. attribute:: SQLITE_LIMIT_VARIABLE_NUMBER + + The maximum index number of any parameter in an SQL statement. + + .. versionadded:: 3.11 + + .. attribute:: SQLITE_LIMIT_TRIGGER_DEPTH + + The maximum depth of recursion for triggers. + + .. versionadded:: 3.11 + + .. attribute:: SQLITE_LIMIT_WORKER_THREADS + + The maximum number of auxiliary worker threads that a single prepared + statement may start. Only available for SQLite 3.8.3 or newer. + + .. versionadded:: 3.11 + + .. _sqlite3-cursor-objects: Cursor Objects diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index ff376d231bafa8..b97d216434c8e5 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -239,6 +239,11 @@ sqlite3 (Contributed by Aviv Palivoda, Daniel Shahaf, and Erlend E. Aasland in :issue:`16379`.) +* Connection limits are now exposed as :class:`sqlite3.Connection` + attributes. + (Contributed by Erlend E. Aasland in :issue:`45243`.) + + threading --------- diff --git a/Lib/sqlite3/test/test_dbapi.py b/Lib/sqlite3/test/test_dbapi.py index 732e21dd3a285e..11a011f24da01e 100644 --- a/Lib/sqlite3/test/test_dbapi.py +++ b/Lib/sqlite3/test/test_dbapi.py @@ -332,6 +332,47 @@ def test_drop_unused_refs(self): cu = self.cx.execute(f"select {n}") self.assertEqual(cu.fetchone()[0], n) + def test_connection_limit_attributes(self): + attrs = [ + "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, 8, 3): + attrs.append("SQLITE_LIMIT_WORKER_THREADS") + for attr in attrs: + with self.subTest(attr=attr): + self.assertTrue(hasattr(self.cx, attr)) + + def test_connection_set_get_limit(self): + setval = 10 + limit = self.cx.SQLITE_LIMIT_SQL_LENGTH + try: + self.cx.SQLITE_LIMIT_SQL_LENGTH = setval + self.assertEqual(self.cx.SQLITE_LIMIT_SQL_LENGTH, setval) + msg = "string or blob too big" + self.assertRaisesRegex(sqlite.DataError, msg, + self.cx.execute, "select 1 as '16'") + finally: # restore old limit + self.cx.SQLITE_LIMIT_SQL_LENGTH = limit + + def test_connection_delete_limit(self): + msg = "Cannot delete limit attributes" + with self.assertRaisesRegex(sqlite.ProgrammingError, msg): + del self.cx.SQLITE_LIMIT_LENGTH + + def test_connection_bad_set_limit(self): + with self.assertRaises(TypeError): + self.cx.SQLITE_LIMIT_EXPR_DEPTH = "a" + class UninitialisedConnectionTests(unittest.TestCase): def setUp(self): @@ -759,6 +800,11 @@ def run(err): self.fail("\n".join(err)) def test_check_connection_thread(self): + def set_cx_limit(): + self.con.SQLITE_LIMIT_LENGTH = 0 + def get_cx_limit(): + return self.con.SQLITE_LIMIT_LENGTH + fns = [ lambda: self.con.cursor(), lambda: self.con.commit(), @@ -767,6 +813,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: set_cx_limit(), + lambda: get_cx_limit(), ] 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..25c94597a4c089 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-09-20-01-25-09.bpo-45243.0pJf0U.rst @@ -0,0 +1,2 @@ +SQLite connection limits are now exposed as :class:`sqlite3.Connection` +attributes. Patch by Erlend E. Aasland. diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 02481d9fbd8bb7..b1dfcf0b84a0ec 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -1866,15 +1866,116 @@ pysqlite_connection_exit_impl(pysqlite_Connection *self, PyObject *exc_type, Py_RETURN_FALSE; } +static inline int +cast_limit(void *limit) +{ + union { + int i; + void *ptr; + } u; + + u.ptr = limit; + return u.i; +} + +static PyObject * +get_limit(pysqlite_Connection *self, void *limit) +{ + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + int value = sqlite3_limit(self->db, cast_limit(limit), -1); + return PyLong_FromLong(value); +} + +static int +set_limit(pysqlite_Connection *self, PyObject *value, void *limit) +{ + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return -1; + } + if (value == NULL) { + PyErr_SetString(self->ProgrammingError, + "Cannot delete limit attributes"); + return -1; + } + int setval = _PyLong_AsInt(value); + if (setval == -1 && PyErr_Occurred()) { + return -1; + } + (void)sqlite3_limit(self->db, cast_limit(limit), setval); + return 0; +} + static const char connection_doc[] = PyDoc_STR("SQLite database connection object."); +#define DEF_LIMIT_GETSET(limit, doc) \ + {#limit, (getter)get_limit, (setter)set_limit, doc, (void *)limit}, + static PyGetSetDef connection_getset[] = { {"isolation_level", (getter)pysqlite_connection_get_isolation_level, (setter)pysqlite_connection_set_isolation_level}, {"total_changes", (getter)pysqlite_connection_get_total_changes, (setter)0}, {"in_transaction", (getter)pysqlite_connection_get_in_transaction, (setter)0}, + DEF_LIMIT_GETSET( + SQLITE_LIMIT_LENGTH, + "The maximum size of any string or BLOB or table row, in bytes." + ) + DEF_LIMIT_GETSET( + SQLITE_LIMIT_SQL_LENGTH, + "The maximum length of an SQL statement, in bytes." + ) + DEF_LIMIT_GETSET( + SQLITE_LIMIT_COLUMN, + "The maximum number of columns in a table definition or in the result " + "set of a SELECT or the maximum number of columns in an index or in an " + "ORDER BY or GROUP BY clause." + ) + DEF_LIMIT_GETSET( + SQLITE_LIMIT_EXPR_DEPTH, + "The maximum depth of the parse tree on any expression." + ) + DEF_LIMIT_GETSET( + SQLITE_LIMIT_COMPOUND_SELECT, + "The maximum number of terms in a compound SELECT statement." + ) + DEF_LIMIT_GETSET( + SQLITE_LIMIT_VDBE_OP, + "The maximum number of instructions in a virtual machine program used " + "to implement an SQL statement." + ) + DEF_LIMIT_GETSET( + SQLITE_LIMIT_FUNCTION_ARG, + "The maximum number of arguments on a function." + ) + DEF_LIMIT_GETSET( + SQLITE_LIMIT_ATTACHED, + "The maximum number of attached databases." + ) + DEF_LIMIT_GETSET( + SQLITE_LIMIT_LIKE_PATTERN_LENGTH, + "The maximum length of the pattern argument to the LIKE or GLOB " + "operators." + ) + DEF_LIMIT_GETSET( + SQLITE_LIMIT_VARIABLE_NUMBER, + "The maximum index number of any parameter in an SQL statement." + ) + DEF_LIMIT_GETSET( + SQLITE_LIMIT_TRIGGER_DEPTH, + "The maximum depth of recursion for triggers." + ) +#if SQLITE_VERSION_NUMBER >= 3008003 + DEF_LIMIT_GETSET( + SQLITE_LIMIT_WORKER_THREADS, + "The maximum number of auxiliary worker threads that a single " + "prepared statement may start." + ) +#endif {NULL} }; +#undef DEF_LIMIT_GETSET static PyMethodDef connection_methods[] = { PYSQLITE_CONNECTION_BACKUP_METHODDEF