summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSage Abdullah <me@laymonage.com>2025-05-08 09:10:44 +0200
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-05-08 11:37:11 +0100
commit358fd21c47cdf7bda520ce73c5cfd82bba57827b (patch)
tree12b5c91b138d9f22eafcbff98df0740a318f1a81
parent38660a612cd924199df83b93807604cadf6d5125 (diff)
Fixed #36143 -- Made max_query_params respect SQLITE_LIMIT_VARIABLE_NUMBER.
Co-authored-by: Xavier Frankline <xf.xavierfrank@gmail.com>
-rw-r--r--django/db/backends/sqlite3/features.py12
-rw-r--r--django/db/backends/sqlite3/operations.py4
-rw-r--r--tests/backends/sqlite/test_features.py12
-rw-r--r--tests/backends/sqlite/test_operations.py30
4 files changed, 55 insertions, 3 deletions
diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py
index ef42f70595..1e995a87c0 100644
--- a/django/db/backends/sqlite3/features.py
+++ b/django/db/backends/sqlite3/features.py
@@ -1,4 +1,5 @@
import operator
+import sqlite3
from django.db import transaction
from django.db.backends.base.features import BaseDatabaseFeatures
@@ -13,7 +14,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
test_db_allows_multiple_connections = False
supports_unspecified_pk = True
supports_timezones = False
- max_query_params = 999
supports_transactions = True
atomic_transactions = False
can_rollback_ddl = True
@@ -140,6 +140,16 @@ class DatabaseFeatures(BaseDatabaseFeatures):
"SmallAutoField": "AutoField",
}
+ @property
+ def max_query_params(self):
+ """
+ SQLite has a variable limit per query. The limit can be changed using
+ the SQLITE_MAX_VARIABLE_NUMBER compile-time option (which defaults to
+ 999 in versions < 3.32.0 or 32766 in newer versions) or lowered per
+ connection at run-time with setlimit(SQLITE_LIMIT_VARIABLE_NUMBER, N).
+ """
+ return self.connection.connection.getlimit(sqlite3.SQLITE_LIMIT_VARIABLE_NUMBER)
+
@cached_property
def supports_json_field(self):
with self.connection.cursor() as cursor:
diff --git a/django/db/backends/sqlite3/operations.py b/django/db/backends/sqlite3/operations.py
index 0ab853f766..340bd28ef8 100644
--- a/django/db/backends/sqlite3/operations.py
+++ b/django/db/backends/sqlite3/operations.py
@@ -30,8 +30,8 @@ class DatabaseOperations(BaseDatabaseOperations):
def bulk_batch_size(self, fields, objs):
"""
- SQLite has a compile-time default (SQLITE_LIMIT_VARIABLE_NUMBER) of
- 999 variables per query.
+ SQLite has a variable limit defined by SQLITE_LIMIT_VARIABLE_NUMBER
+ (reflected in max_query_params).
If there's only a single field to insert, the limit is 500
(SQLITE_MAX_COMPOUND_SELECT).
diff --git a/tests/backends/sqlite/test_features.py b/tests/backends/sqlite/test_features.py
index 5bc891f5ee..95e060fbcc 100644
--- a/tests/backends/sqlite/test_features.py
+++ b/tests/backends/sqlite/test_features.py
@@ -1,3 +1,4 @@
+import sqlite3
from unittest import mock, skipUnless
from django.db import OperationalError, connection
@@ -17,3 +18,14 @@ class FeaturesTests(TestCase):
):
with self.assertRaisesMessage(OperationalError, msg):
connection.features.supports_json_field
+
+ def test_max_query_params_respects_variable_limit(self):
+ limit_name = sqlite3.SQLITE_LIMIT_VARIABLE_NUMBER
+ current_limit = connection.features.max_query_params
+ new_limit = min(42, current_limit)
+ try:
+ connection.connection.setlimit(limit_name, new_limit)
+ self.assertEqual(connection.features.max_query_params, new_limit)
+ finally:
+ connection.connection.setlimit(limit_name, current_limit)
+ self.assertEqual(connection.features.max_query_params, current_limit)
diff --git a/tests/backends/sqlite/test_operations.py b/tests/backends/sqlite/test_operations.py
index 10cbffdf80..ab6efaa771 100644
--- a/tests/backends/sqlite/test_operations.py
+++ b/tests/backends/sqlite/test_operations.py
@@ -1,3 +1,4 @@
+import sqlite3
import unittest
from django.core.management.color import no_style
@@ -108,3 +109,32 @@ class SQLiteOperationsTests(TestCase):
),
connection.features.max_query_params // 3,
)
+
+ def test_bulk_batch_size_respects_variable_limit(self):
+ first_name_field = Person._meta.get_field("first_name")
+ last_name_field = Person._meta.get_field("last_name")
+ limit_name = sqlite3.SQLITE_LIMIT_VARIABLE_NUMBER
+ current_limit = connection.features.max_query_params
+ self.assertEqual(
+ connection.ops.bulk_batch_size(
+ [first_name_field, last_name_field], [Person()]
+ ),
+ current_limit // 2,
+ )
+ new_limit = min(42, current_limit)
+ try:
+ connection.connection.setlimit(limit_name, new_limit)
+ self.assertEqual(
+ connection.ops.bulk_batch_size(
+ [first_name_field, last_name_field], [Person()]
+ ),
+ new_limit // 2,
+ )
+ finally:
+ connection.connection.setlimit(limit_name, current_limit)
+ self.assertEqual(
+ connection.ops.bulk_batch_size(
+ [first_name_field, last_name_field], [Person()]
+ ),
+ current_limit // 2,
+ )