diff --git a/django/db/backends/sqlite3/operations.py b/django/db/backends/sqlite3/operations.py index 1500ae28aa..ac98324b2e 100644 --- a/django/db/backends/sqlite3/operations.py +++ b/django/db/backends/sqlite3/operations.py @@ -1,5 +1,6 @@ import datetime import decimal +import sqlite3 import uuid from functools import lru_cache from itertools import chain @@ -143,16 +144,15 @@ class DatabaseOperations(BaseDatabaseOperations): """ Only for last_executed_query! Don't use this to execute SQL queries! """ - # This function is limited both by SQLITE_LIMIT_VARIABLE_NUMBER (the - # number of parameters, default = 999) and SQLITE_MAX_COLUMN (the - # number of return values, default = 2000). Since Python's sqlite3 - # module doesn't expose the get_limit() C API, assume the default - # limits are in effect and split the work in batches if needed. - BATCH_SIZE = 999 - if len(params) > BATCH_SIZE: + connection = self.connection.connection + variable_limit = self.connection.features.max_query_params + column_limit = connection.getlimit(sqlite3.SQLITE_LIMIT_COLUMN) + batch_size = min(variable_limit, column_limit) + + if len(params) > batch_size: results = () - for index in range(0, len(params), BATCH_SIZE): - chunk = params[index : index + BATCH_SIZE] + for index in range(0, len(params), batch_size): + chunk = params[index : index + batch_size] results += self._quote_params_for_last_executed_query(chunk) return results diff --git a/tests/backends/sqlite/tests.py b/tests/backends/sqlite/tests.py index 37d95c0cb5..34c0eca0ff 100644 --- a/tests/backends/sqlite/tests.py +++ b/tests/backends/sqlite/tests.py @@ -1,5 +1,6 @@ import os import re +import sqlite3 import tempfile import threading import unittest @@ -215,15 +216,28 @@ class LastExecutedQueryTest(TestCase): substituted = "SELECT '\"''\\'" self.assertEqual(connection.queries[-1]["sql"], substituted) - def test_large_number_of_parameters(self): - # If SQLITE_MAX_VARIABLE_NUMBER (default = 999) has been changed to be - # greater than SQLITE_MAX_COLUMN (default = 2000), last_executed_query - # can hit the SQLITE_MAX_COLUMN limit (#26063). - with connection.cursor() as cursor: - sql = "SELECT MAX(%s)" % ", ".join(["%s"] * 2001) - params = list(range(2001)) - # This should not raise an exception. - cursor.db.ops.last_executed_query(cursor.cursor, sql, params) + def test_parameter_count_exceeds_variable_or_column_limit(self): + sql = "SELECT MAX(%s)" % ", ".join(["%s"] * 1001) + params = list(range(1001)) + for label, limit, current_limit in [ + ( + "variable", + sqlite3.SQLITE_LIMIT_VARIABLE_NUMBER, + connection.features.max_query_params, + ), + ( + "column", + sqlite3.SQLITE_LIMIT_COLUMN, + connection.connection.getlimit(sqlite3.SQLITE_LIMIT_COLUMN), + ), + ]: + with self.subTest(limit=label): + connection.connection.setlimit(limit, 1000) + self.addCleanup(connection.connection.setlimit, limit, current_limit) + with connection.cursor() as cursor: + # This should not raise an exception. + cursor.db.ops.last_executed_query(cursor.cursor, sql, params) + connection.connection.setlimit(limit, current_limit) @unittest.skipUnless(connection.vendor == "sqlite", "SQLite tests")