Skip to content

Commit 5eec3e1

Browse files
authored
Fix DROP index vs constraint issue (#38) (#39)
1 parent 654f2dd commit 5eec3e1

File tree

4 files changed

+52
-2
lines changed

4 files changed

+52
-2
lines changed

sql_server/pyodbc/schema.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -580,15 +580,21 @@ def _delete_unique_constraints(self, model, old_field, new_field, strict=False):
580580
unique_columns.append([old_field.column])
581581
if unique_columns:
582582
for columns in unique_columns:
583-
constraint_names = self._constraint_names(model, columns, unique=True)
583+
constraint_names_normal = self._constraint_names(model, columns, unique=True, index=False)
584+
constraint_names_index = self._constraint_names(model, columns, unique=True, index=True)
585+
constraint_names = constraint_names_normal + constraint_names_index
584586
if strict and len(constraint_names) != 1:
585587
raise ValueError("Found wrong number (%s) of unique constraints for %s.%s" % (
586588
len(constraint_names),
587589
model._meta.db_table,
588590
old_field.column,
589591
))
590-
for constraint_name in constraint_names:
592+
for constraint_name in constraint_names_normal:
591593
self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_name))
594+
# Unique indexes which are not table constraints must be deleted using the appropriate SQL.
595+
# These may exist for example to enforce ANSI-compliant unique constraints on nullable columns.
596+
for index_name in constraint_names_index:
597+
self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_name))
592598

593599
def _rename_field_sql(self, table, old_field, new_field, new_type):
594600
new_type = self._set_field_new_type_null_status(old_field, new_type)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from django.db import migrations, models
2+
3+
4+
class Migration(migrations.Migration):
5+
6+
dependencies = [
7+
('testapp', '0001_initial'),
8+
]
9+
10+
operations = [
11+
# Create with a field that is unique *and* nullable so it is implemented with a filtered unique index.
12+
migrations.CreateModel(
13+
name='TestUniqueNullableModel',
14+
fields=[
15+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
16+
('test_field', models.CharField(max_length=100, null=True, unique=True)),
17+
],
18+
),
19+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from django.db import migrations, models
2+
3+
4+
class Migration(migrations.Migration):
5+
6+
dependencies = [
7+
('testapp', '0002_test_unique_nullable_part1'),
8+
]
9+
10+
operations = [
11+
# Now remove the null=True to check this transition is correctly handled.
12+
migrations.AlterField(
13+
model_name='testuniquenullablemodel',
14+
name='test_field',
15+
field=models.CharField(default='', max_length=100, unique=True),
16+
preserve_default=False,
17+
),
18+
]

testapp/models.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,10 @@ class UUIDModel(models.Model):
4141

4242
def __str__(self):
4343
return self.pk
44+
45+
46+
class TestUniqueNullableModel(models.Model):
47+
# This field started off as unique=True *and* null=True so it is implemented with a filtered unique index
48+
# Then it is made non-nullable by a subsequent migration, to check this is correctly handled (the index
49+
# should be dropped, then a normal unique constraint should be added, now that the column is not nullable)
50+
test_field = models.CharField(max_length=100, unique=True)

0 commit comments

Comments
 (0)