Skip to content

bpo-45243: Expose SQLite connection limits as sqlite3.Connection attributes #28790

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions Doc/library/sqlite3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------

Expand Down
48 changes: 48 additions & 0 deletions Lib/sqlite3/test/test_dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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(),
Expand All @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SQLite connection limits are now exposed as :class:`sqlite3.Connection`
attributes. Patch by Erlend E. Aasland.
101 changes: 101 additions & 0 deletions Modules/_sqlite/connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casting the limit macros to void * and back again to int is perhaps a little bit too hackish. But it does allow for a single getter/setter.


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
Expand Down