2.4 KiB
2.4 KiB
UNDF: UNDF-2026-000000378
django-0005: alt_constraints_name list → set in create_altered_constraints()
Severity
MEDIUM
Location
django/db/migrations/autodetector.py — create_altered_constraints()
Description
alt_constraints_name is built as a plain list and appended to inside a
double for old_c / for new_c loop. It is then searched with
c.name not in alt_constraints_name twice in list comprehensions that iterate
over new_constraints and old_constraints.
Worst-case: N models × C old constraints × C new constraints inner loop
builds alt_constraints_name with up to C entries; the two filter
comprehensions then each scan that list O(C) per element → overall
O(N × C³) where C = constraint count per model. In Django projects with
many unique/check constraints the auto-detector fires on every makemigrations
call.
Defective code (lines 1555–1581)
alt_constraints_name = [] # ← plain list
...
for old_c in old_constraints:
for new_c in new_constraints:
...
if ...:
alt_constraints_name.append(new_c.name) # ← O(1) append is fine
add_constraints = [
c
for c in new_constraints
if c not in old_constraints and c.name not in alt_constraints_name # ← O(C) scan
]
rem_constraints = [
c
for c in old_constraints
if c not in new_constraints and c.name not in alt_constraints_name # ← O(C) scan
]
Fix
- alt_constraints_name = []
+ alt_constraints_name = set()
...
- alt_constraints_name.append(new_c.name)
+ alt_constraints_name.add(new_c.name)
Patch
--- a/django/db/migrations/autodetector.py
+++ b/django/db/migrations/autodetector.py
@@ -1553,7 +1553,7 @@ class MigrationAutodetector:
alt_constraints = []
- alt_constraints_name = []
+ alt_constraints_name = set()
for old_c in old_constraints:
for new_c in new_constraints:
@@ -1567,7 +1567,7 @@ class MigrationAutodetector:
):
alt_constraints.append(new_c)
- alt_constraints_name.append(new_c.name)
+ alt_constraints_name.add(new_c.name)
Complexity
- Before: O(N × C² × C) = O(N × C³)
- After: O(N × C²) — the membership tests drop to O(1)