The UNNEST bulk_create optimisation introduced in Django 5.2 (ticket #35936) generates explicit PostgreSQL type casts derived from field.db_type().
LogEntry does not declare an explicit id field. Its primary key type is inherited from the admin app’s AppConfig, which hardcodes default_auto_field = “django.db.models.AutoField” (source). This override was added in commit b5e12d49] to prevent unwanted migration generation when projects set DEFAULT_AUTO_FIELD = BigAutoField.
When calling LogEntryTracing.objects.bulk_create(…), Django 5.2 generates:
INSERT INTO log_entry_tracing (log_entry_id, trace_id)
SELECT * FROM UNNEST(
(ARRAY[3184205939, 3184205940])::integer[],
(ARRAY['trace_a', 'trace_b'])::varchar[]
)
The ::integer[] cast comes from LogEntry.id.db_type() returning “integer” (via AutoField). PostgreSQL rejects the cast because 3184205939 > 2,147,483,647.
This also affects LogEntry.objects.bulk_create() indirectly – while the auto-generated id is omitted from the INSERT (so no cast on id itself), any model holding a FK to LogEntry.id will get the ::integer[] cast on its FK column.
Before 5.2, this was harmless – VALUES (%s, %s, …) doesn’t type-cast parameters. In 5.2, the UNNEST path in django/db/backends/postgresql/compiler.py does:
db_types = [field.db_type(self.connection).split(“(”)[0] for field in fields]
# produces ::integer[] for AutoField
When calling LogEntryTracing.objects.bulk_create(...), Django 5.2 generates:
INSERT INTO log_entry_tracing (log_entry_id, trace_id)
SELECT * FROM UNNEST(
(ARRAY[3184205939, 3184205940])::integer[],
(ARRAY['trace_a', 'trace_b'])::varchar[]
)
The ::integer[] cast comes from LogEntry.id.db_type() returning "integer" (via AutoField). PostgreSQL rejects the cast because 3184205939 > 2,147,483,647.
This also affects LogEntry.objects.bulk_create() indirectly – while the auto-generated id is omitted from the INSERT (so no cast on id itself), any model holding a FK to LogEntry.id will get the ::integer[] cast on its FK column.
DEFAULT_AUTO_FIELD is not used
Projects can set DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" globally, but Options._get_default_pk_class() checks app_config.default_auto_field first:
# django/db/models/options.py
def _get_default_pk_class(self):
pk_class_path = getattr(
self.app_config,
"default_auto_field",
settings.DEFAULT_AUTO_FIELD,
)
Since SimpleAdminConfig explicitly sets default_auto_field = "django.db.models.AutoField", the global setting is ignored for LogEntry.
Proposed change
Add an explicit BigAutoField primary key to LogEntry and ship a migration. This is a one-line model change plus a migration file:
Model change (django/contrib/admin/models.py):
class LogEntry(models.Model):
id = models.BigAutoField(primary_key=True) # <-- add this
action_time = models.DateTimeField(...)
Alternative proposal
Remove the default_auto_field from SimpleAdminConfig so that DEFAULT_AUTO_FIELD takes precedence
class SimpleAdminConfig(AppConfig):
"""Simple AppConfig which does not do automatic discovery."""
# default_auto_field = "django.db.models.AutoField"