Closed
Description
Problem
Modifying a field which is nullable and unique, to no longer be nullable, results in the following error during migration:
...
File "...\venv.37\lib\site-packages\django\db\backends\base\schema.py", line 535, in alter_field
old_db_params, new_db_params, strict)
File "...\venv.37\lib\site-packages\sql_server\pyodbc\schema.py", line 270, in _alter_field
self._delete_unique_constraints(model, old_field, new_field, strict)
File "...\venv.37\lib\site-packages\sql_server\pyodbc\schema.py", line 479, in _delete_unique_constraints
self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_name))
File "...\venv.37\lib\site-packages\sql_server\pyodbc\schema.py", line 681, in execute
cursor.execute(sql, params)
File "...\venv.37\lib\site-packages\django\db\backends\utils.py", line 99, in execute
return super().execute(sql, params)
File "...\venv.37\lib\site-packages\django\db\backends\utils.py", line 67, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File "...\venv.37\lib\site-packages\django\db\backends\utils.py", line 76, in _execute_with_wrappers
return executor(sql, params, many, context)
File "...\venv.37\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "...\venv.37\lib\site-packages\django\db\utils.py", line 89, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "...\venv.37\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "...\venv.37\lib\site-packages\sql_server\pyodbc\base.py", line 559, in execute
return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: ('42000', "[42000] [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]'myapp_mymodel_my_field_a717c0d4_uniq' is not a constraint. (3728) (SQLExecDirectW); [42000] [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Could not drop constraint. See previous errors. (3727)")
Reproduction steps
This is reproducible in a minimal Django project with these dependencies:
Django==2.2.10
django-mssql-backend==2.4.2
pyodbc==4.0.30
pytz==2019.3
sqlparse==0.3.0
and the following single model:
class MyModel(models.Model):
my_field = models.CharField(max_length=100, null=True, unique=True)
makemigrations
to get0001
which creates this model with 1 field that isnull=True, unique=True
migrate
which does the following (note: the uniqueness is enforced by a filtered unique index rather than by a constraint on the table, since this change: Nullable unique constraints #1)
CREATE TABLE [myapp_mymodel] ([id] int IDENTITY (1, 1) NOT NULL PRIMARY KEY, [my_field] nvarchar(100) NULL)
CREATE UNIQUE INDEX [myapp_mymodel_my_field_a717c0d4_uniq] ON [myapp_mymodel]([my_field]) WHERE [my_field] IS NOT NULL
- then modify
my_field
inmodels.py
by removingnull=True
makemigrations
to get0002
which does anAlterField
migrate
which tries to do the following in preparation (before altering the nullability)
ALTER TABLE [myapp_mymodel] DROP CONSTRAINT [myapp_mymodel_my_field_a717c0d4_uniq]
but blows up with the error & stack trace above, because myapp_mymodel_my_field_a717c0d4_uniq
is an index, not a constraint.
Cause
I believe this is a knock-on effect of #1 and this is as far as I got with diagnosing the problem:
_alter_field
calls_delete_unique_constraints
because of the 'Nullability change', which calls_constraint_names(..., unique=True)
_constraint_names
returns['myapp_mymodel_my_field_a717c0d4_uniq']
despite this being an index, and then_delete_unique_constraints
tries to delete it usingsql_delete_unique
(i.e.ALTER TABLE ... DROP CONSTRAINT ...
) which fails because it is just an index
Possible solution
Perhaps _delete_unique_constraints
should call _constraint_names
twice as follows:
- once with
unique=True, index=False
and DROP those usingALTER TABLE... DROP CONSTRAINT
- once with
unique=True, index=True
and DROP those usingDROP INDEX ...
Does that sound reasonable?
Metadata
Metadata
Assignees
Labels
No labels