Skip to content

Fix DROP index vs constraint issue (#38) #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)