@@ -69,20 +69,20 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
6969 sql_delete_column = "ALTER TABLE %(table)s DROP COLUMN %(column)s"
7070 sql_delete_index = "DROP INDEX %(name)s ON %(table)s"
7171 sql_delete_table = """
72- DECLARE @sql_froeign_constraint_name nvarchar(128)
72+ DECLARE @sql_foreign_constraint_name nvarchar(128)
7373 DECLARE @sql_drop_constraint nvarchar(300)
7474 WHILE EXISTS(SELECT 1
7575 FROM sys.foreign_keys
7676 WHERE referenced_object_id = object_id('%(table)s'))
7777 BEGIN
78- SELECT TOP 1 @sql_froeign_constraint_name = name
78+ SELECT TOP 1 @sql_foreign_constraint_name = name
7979 FROM sys.foreign_keys
8080 WHERE referenced_object_id = object_id('%(table)s')
8181 SELECT
8282 @sql_drop_constraint = 'ALTER TABLE [' + OBJECT_NAME(parent_object_id) + '] ' +
83- 'DROP CONSTRAINT [' + @sql_froeign_constraint_name + '] '
83+ 'DROP CONSTRAINT [' + @sql_foreign_constraint_name + '] '
8484 FROM sys.foreign_keys
85- WHERE referenced_object_id = object_id('%(table)s') and name = @sql_froeign_constraint_name
85+ WHERE referenced_object_id = object_id('%(table)s') and name = @sql_foreign_constraint_name
8686 exec sp_executesql @sql_drop_constraint
8787 END
8888 DROP TABLE %(table)s
@@ -283,11 +283,19 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
283283 old_db_params , new_db_params , strict = False ):
284284 """Actually perform a "physical" (non-ManyToMany) field update."""
285285
286- # the backend doesn't support altering from/to (Big)AutoField
287- # because of the limited capability of SQL Server to edit IDENTITY property
286+ # the backend doesn't support altering a column to/from AutoField as
287+ # SQL Server cannot alter columns to add and remove IDENTITY properties
288+ old_is_auto = False
289+ new_is_auto = False
288290 for t in (AutoField , BigAutoField ):
289- if isinstance (old_field , t ) or isinstance (new_field , t ):
290- raise NotImplementedError ("the backend doesn't support altering from/to %s." % t .__name__ )
291+ if isinstance (old_field , t ):
292+ old_is_auto = True
293+ if isinstance (new_field , t ):
294+ new_is_auto = True
295+ if (old_is_auto and not new_is_auto ) or (not old_is_auto and new_is_auto ):
296+ raise NotImplementedError ("the backend doesn't support altering from %s to %s." %
297+ (old_field .get_internal_type (), new_field .get_internal_type ()))
298+
291299 # Drop any FK constraints, we'll remake them later
292300 fks_dropped = set ()
293301 if old_field .remote_field and old_field .db_constraint :
@@ -327,6 +335,17 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
327335 )
328336 for fk_name in rel_fk_names :
329337 self .execute (self ._delete_constraint_sql (self .sql_delete_fk , new_rel .related_model , fk_name ))
338+ # If working with an AutoField or BigAutoField drop all indexes on the related table
339+ # This is needed when doing ALTER column statements on IDENTITY fields
340+ # https://stackoverflow.com/questions/33429775/sql-server-alter-table-alter-column-giving-set-option-error
341+ for t in (AutoField , BigAutoField ):
342+ if isinstance (old_field , t ) or isinstance (new_field , t ):
343+ index_names = self ._constraint_names (model , index = True )
344+ for index_name in index_names :
345+ self .execute (
346+ self ._delete_constraint_sql (self .sql_delete_index , model , index_name )
347+ )
348+ break
330349 # Removed an index? (no strict check, as multiple indexes are possible)
331350 # Remove indexes if db_index switched to False or a unique constraint
332351 # will now be used in lieu of an index. The following lines from the
@@ -542,7 +561,8 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
542561 # Restore unique constraints
543562 # Note: if nullable they are implemented via an explicit filtered UNIQUE INDEX (not CONSTRAINT)
544563 # in order to get ANSI-compliant NULL behaviour (i.e. NULL != NULL, multiple are allowed)
545- if old_field .unique and new_field .unique :
564+ # Note: Don't restore primary keys, we need to re-create those seperately
565+ if old_field .unique and new_field .unique and not new_field .primary_key :
546566 if new_field .null :
547567 self .execute (
548568 self ._create_index_sql (
@@ -568,7 +588,47 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
568588 if old_field .column in columns :
569589 condition = ' AND ' .join (["[%s] IS NOT NULL" % col for col in columns ])
570590 self .execute (self ._create_unique_sql (model , columns , condition = condition ))
591+ # Restore primary keys
592+ if old_field .primary_key and new_field .primary_key :
593+ self .execute (
594+ self .sql_create_pk % {
595+ "table" : self .quote_name (model ._meta .db_table ),
596+ "name" : self .quote_name (
597+ self ._create_index_name (model ._meta .db_table , [new_field .column ], suffix = "_pk" )
598+ ),
599+ "columns" : self .quote_name (new_field .column ),
600+ }
601+ )
602+ # Restore unqiue_together
603+ # If we have ALTERed an AutoField or BigAutoField we need to recreate all unique_together clauses
604+ for t in (AutoField , BigAutoField ):
605+ if isinstance (old_field , t ) or isinstance (new_field , t ):
606+ for field_names in model ._meta .unique_together :
607+ columns = [model ._meta .get_field (field ).column for field in field_names ]
608+ fields = [model ._meta .get_field (field ) for field in field_names ]
609+ condition = ' AND ' .join (["[%s] IS NOT NULL" % col for col in columns ])
610+ # We need to pass fields instead of columns when using >= Django 4.0 because
611+ # of a backwards incompatible change to _create_unique_sql
612+ if django_version >= (4 , 0 ):
613+ self .execute (
614+ self ._create_unique_sql (model , fields , condition = condition )
615+ )
616+ else :
617+ self .execute (
618+ self ._create_unique_sql (model , columns , condition = condition )
619+ )
620+ break
621+
571622 # Restore indexes
623+ # If we have ALTERed an AutoField or BigAutoField we need to recreate all indexes
624+ for t in (AutoField , BigAutoField ):
625+ if isinstance (old_field , t ) or isinstance (new_field , t ):
626+ for field in model ._meta .fields :
627+ if field .db_index :
628+ self .execute (
629+ self ._create_index_sql (model , [field ])
630+ )
631+ break
572632 index_columns = []
573633 if old_field .db_index and new_field .db_index :
574634 index_columns .append ([old_field ])
@@ -626,7 +686,26 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
626686 for sql , params in other_actions :
627687 self .execute (sql , params )
628688 # Restore related_model indexes
629- self .execute (self ._create_index_sql (new_rel .related_model , [new_rel .field ]))
689+ for field in new_rel .related_model ._meta .fields :
690+ if field .db_index :
691+ self .execute (
692+ self ._create_index_sql (new_rel .related_model , [field ])
693+ )
694+ # Restore unique_together clauses
695+ for field_names in new_rel .related_model ._meta .unique_together :
696+ columns = [new_rel .related_model ._meta .get_field (field ).column for field in field_names ]
697+ fields = [new_rel .related_model ._meta .get_field (field ) for field in field_names ]
698+ condition = ' AND ' .join (["[%s] IS NOT NULL" % col for col in columns ])
699+ # We need to pass fields instead of columns when using >= Django 4.0 because
700+ # of a backwards incompatible change to _create_unique_sql
701+ if django_version >= (4 , 0 ):
702+ self .execute (
703+ self ._create_unique_sql (new_rel .related_model , fields , condition = condition )
704+ )
705+ else :
706+ self .execute (
707+ self ._create_unique_sql (new_rel .related_model , columns , condition = condition )
708+ )
630709 # Does it have a foreign key?
631710 if (new_field .remote_field and
632711 (fks_dropped or not old_field .remote_field or not old_field .db_constraint ) and
0 commit comments