Skip to content

Commit

Permalink
Fix DROP index vs constraint issue (michiya#38) (michiya#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
sparrowt authored Mar 10, 2020
1 parent 654f2dd commit 5eec3e1
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 2 deletions.
10 changes: 8 additions & 2 deletions sql_server/pyodbc/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,15 +580,21 @@ def _delete_unique_constraints(self, model, old_field, new_field, strict=False):
unique_columns.append([old_field.column])
if unique_columns:
for columns in unique_columns:
constraint_names = self._constraint_names(model, columns, unique=True)
constraint_names_normal = self._constraint_names(model, columns, unique=True, index=False)
constraint_names_index = self._constraint_names(model, columns, unique=True, index=True)
constraint_names = constraint_names_normal + constraint_names_index
if strict and len(constraint_names) != 1:
raise ValueError("Found wrong number (%s) of unique constraints for %s.%s" % (
len(constraint_names),
model._meta.db_table,
old_field.column,
))
for constraint_name in constraint_names:
for constraint_name in constraint_names_normal:
self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_name))
# Unique indexes which are not table constraints must be deleted using the appropriate SQL.
# These may exist for example to enforce ANSI-compliant unique constraints on nullable columns.
for index_name in constraint_names_index:
self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_name))

def _rename_field_sql(self, table, old_field, new_field, new_type):
new_type = self._set_field_new_type_null_status(old_field, new_type)
Expand Down
19 changes: 19 additions & 0 deletions testapp/migrations/0002_test_unique_nullable_part1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('testapp', '0001_initial'),
]

operations = [
# Create with a field that is unique *and* nullable so it is implemented with a filtered unique index.
migrations.CreateModel(
name='TestUniqueNullableModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('test_field', models.CharField(max_length=100, null=True, unique=True)),
],
),
]
18 changes: 18 additions & 0 deletions testapp/migrations/0003_test_unique_nullable_part2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('testapp', '0002_test_unique_nullable_part1'),
]

operations = [
# Now remove the null=True to check this transition is correctly handled.
migrations.AlterField(
model_name='testuniquenullablemodel',
name='test_field',
field=models.CharField(default='', max_length=100, unique=True),
preserve_default=False,
),
]
7 changes: 7 additions & 0 deletions testapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,10 @@ class UUIDModel(models.Model):

def __str__(self):
return self.pk


class TestUniqueNullableModel(models.Model):
# This field started off as unique=True *and* null=True so it is implemented with a filtered unique index
# Then it is made non-nullable by a subsequent migration, to check this is correctly handled (the index
# should be dropped, then a normal unique constraint should be added, now that the column is not nullable)
test_field = models.CharField(max_length=100, unique=True)

0 comments on commit 5eec3e1

Please sign in to comment.