1
0
mirror of https://github.com/django/django.git synced 2025-05-21 14:26:29 +00:00

Fixed #36143 -- Made max_query_params respect SQLITE_LIMIT_VARIABLE_NUMBER.

Co-authored-by: Xavier Frankline <xf.xavierfrank@gmail.com>
This commit is contained in:
Sage Abdullah 2025-05-08 09:10:44 +02:00 committed by Sarah Boyce
parent 38660a612c
commit 358fd21c47
4 changed files with 55 additions and 3 deletions

View File

@ -1,4 +1,5 @@
import operator import operator
import sqlite3
from django.db import transaction from django.db import transaction
from django.db.backends.base.features import BaseDatabaseFeatures from django.db.backends.base.features import BaseDatabaseFeatures
@ -13,7 +14,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
test_db_allows_multiple_connections = False test_db_allows_multiple_connections = False
supports_unspecified_pk = True supports_unspecified_pk = True
supports_timezones = False supports_timezones = False
max_query_params = 999
supports_transactions = True supports_transactions = True
atomic_transactions = False atomic_transactions = False
can_rollback_ddl = True can_rollback_ddl = True
@ -140,6 +140,16 @@ class DatabaseFeatures(BaseDatabaseFeatures):
"SmallAutoField": "AutoField", "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 @cached_property
def supports_json_field(self): def supports_json_field(self):
with self.connection.cursor() as cursor: with self.connection.cursor() as cursor:

View File

@ -30,8 +30,8 @@ class DatabaseOperations(BaseDatabaseOperations):
def bulk_batch_size(self, fields, objs): def bulk_batch_size(self, fields, objs):
""" """
SQLite has a compile-time default (SQLITE_LIMIT_VARIABLE_NUMBER) of SQLite has a variable limit defined by SQLITE_LIMIT_VARIABLE_NUMBER
999 variables per query. (reflected in max_query_params).
If there's only a single field to insert, the limit is 500 If there's only a single field to insert, the limit is 500
(SQLITE_MAX_COMPOUND_SELECT). (SQLITE_MAX_COMPOUND_SELECT).

View File

@ -1,3 +1,4 @@
import sqlite3
from unittest import mock, skipUnless from unittest import mock, skipUnless
from django.db import OperationalError, connection from django.db import OperationalError, connection
@ -17,3 +18,14 @@ class FeaturesTests(TestCase):
): ):
with self.assertRaisesMessage(OperationalError, msg): with self.assertRaisesMessage(OperationalError, msg):
connection.features.supports_json_field 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)

View File

@ -1,3 +1,4 @@
import sqlite3
import unittest import unittest
from django.core.management.color import no_style from django.core.management.color import no_style
@ -108,3 +109,32 @@ class SQLiteOperationsTests(TestCase):
), ),
connection.features.max_query_params // 3, 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,
)