Skip to content

Commit

Permalink
Fix migration alter field
Browse files Browse the repository at this point in the history
Related to #14, #67
  • Loading branch information
absci committed Nov 24, 2021
1 parent 3883ac3 commit 6048db2
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 10 deletions.
42 changes: 41 additions & 1 deletion mssql/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
)
from django import VERSION as django_version
from django.db.models import Index, UniqueConstraint
from django.db.models.fields import AutoField, BigAutoField
from django.db.models.fields import AutoField, BigAutoField, TextField
from django.db.models.sql.where import AND
from django.db.transaction import TransactionManagementError
from django.utils.encoding import force_str
Expand All @@ -34,6 +34,13 @@ def __hash__(self):
def __eq__(self, other):
return self.template == other.template and str(self.parts['name']) == str(other.parts['name'])

def rename_column_references(self, table, old_column, new_column):
for part in self.parts.values():
if hasattr(part, 'rename_column_references'):
part.rename_column_references(table, old_column, new_column)
condition = self.parts['condition']
if condition:
self.parts['condition'] = condition.replace(f'[{old_column}]', f'[{new_column}]')

class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):

Expand Down Expand Up @@ -363,7 +370,30 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
self.execute(self._delete_constraint_sql(self.sql_delete_check, model, constraint_name))
# Have they renamed the column?
if old_field.column != new_field.column:
sql_restore_index = ''
# Drop unique indexes for table to be altered
index_names = self._db_table_constraint_names(model._meta.db_table, index=True)
for index_name in index_names:
if(index_name.endswith('uniq')):
with self.connection.cursor() as cursor:
cursor.execute(f"""
SELECT COL_NAME(ic.object_id,ic.column_id) AS column_name,
filter_definition
FROM sys.indexes AS i
INNER JOIN sys.index_columns AS ic
ON i.object_id = ic.object_id AND i.index_id = ic.index_id
WHERE i.object_id = OBJECT_ID('{model._meta.db_table}')
and i.name = '{index_name}'
""")
result = cursor.fetchall()
columns_to_recreate_index = ', '.join(['%s' % self.quote_name(column[0]) for column in result])
filter_definition = result[0][1]
sql_restore_index += f'CREATE UNIQUE INDEX {index_name} ON {model._meta.db_table} ({columns_to_recreate_index}) WHERE {filter_definition};'
self.execute(self._db_table_delete_constraint_sql(self.sql_delete_index, model._meta.db_table, index_name))
self.execute(self._rename_field_sql(model._meta.db_table, old_field, new_field, new_type))
# Restore indexes for altered table
if(sql_restore_index):
self.execute(sql_restore_index.replace(f'[{old_field.column}]', f'[{new_field.column}]'))
# Rename all references to the renamed column.
for sql in self.deferred_sql:
if isinstance(sql, DjStatement):
Expand Down Expand Up @@ -410,6 +440,8 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
self._delete_unique_constraints(model, old_field, new_field, strict)
# Drop indexes, SQL Server requires explicit deletion
self._delete_indexes(model, old_field, new_field)
if not isinstance(new_field, TextField):
post_actions.append((self._create_index_sql(model, [new_field]), ()))
# Only if we have a default and there is a change from NULL to NOT NULL
four_way_default_alteration = (
new_field.has_default() and
Expand Down Expand Up @@ -557,6 +589,10 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
fragment, other_actions = self._alter_column_type_sql(
new_rel.related_model, old_rel.field, new_rel.field, rel_type
)
# Drop related_model indexes, so it can be altered
index_names = self._db_table_constraint_names(old_rel.related_model._meta.db_table, index=True)
for index_name in index_names:
self.execute(self._db_table_delete_constraint_sql(self.sql_delete_index, old_rel.related_model._meta.db_table, index_name))
self.execute(
self.sql_alter_column % {
"table": self.quote_name(new_rel.related_model._meta.db_table),
Expand All @@ -566,6 +602,8 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
)
for sql, params in other_actions:
self.execute(sql, params)
# Restore related_model indexes
self.execute(self._create_index_sql(new_rel.related_model, [new_rel.field]))
# Does it have a foreign key?
if (new_field.remote_field and
(fks_dropped or not old_field.remote_field or not old_field.db_constraint) and
Expand Down Expand Up @@ -608,6 +646,8 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,

def _delete_indexes(self, model, old_field, new_field):
index_columns = []
if old_field.null != new_field.null:
index_columns.append([old_field.column])
if old_field.db_index and new_field.db_index:
index_columns.append([old_field.column])
for fields in model._meta.index_together:
Expand Down
9 changes: 0 additions & 9 deletions testapp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,8 @@
'migrations.test_executor.ExecutorTests.test_alter_id_type_with_fk',
'migrations.test_operations.OperationTests.test_add_constraint_percent_escaping',
'migrations.test_operations.OperationTests.test_alter_field_pk',
'migrations.test_operations.OperationTests.test_alter_field_reloads_state_on_fk_target_changes',
'migrations.test_operations.OperationTests.test_alter_field_reloads_state_on_fk_with_to_field_target_changes',
'migrations.test_operations.OperationTests.test_alter_field_with_index',
'migrations.test_operations.OperationTests.test_autofield_foreignfield_growth',
'migrations.test_operations.OperationTests.test_rename_field_reloads_state_on_fk_target_changes',
'migrations.test_operations.OperationTests.test_rename_m2m_model_after_rename_field',
'migrations.test_operations.OperationTests.test_rename_model_with_superclass_fk',
'migrations.test_operations.OperationTests.test_rename_referenced_field_state_forward',
'schema.tests.SchemaTests.test_alter_auto_field_to_char_field',
'schema.tests.SchemaTests.test_alter_auto_field_to_integer_field',
'schema.tests.SchemaTests.test_alter_implicit_id_to_explicit',
Expand Down Expand Up @@ -107,8 +101,6 @@
'migrations.test_commands.MigrateTests.test_migrate_syncdb_app_label',
'migrations.test_commands.MigrateTests.test_migrate_syncdb_deferred_sql_executed_with_schemaeditor',
'migrations.test_operations.OperationTests.test_alter_field_pk_fk',
'migrations.test_operations.OperationTests.test_create_model_with_partial_unique_constraint',
'migrations.test_operations.OperationTests.test_rename_field',
'schema.tests.SchemaTests.test_add_foreign_key_quoted_db_table',
'schema.tests.SchemaTests.test_unique_and_reverse_m2m',
'schema.tests.SchemaTests.test_unique_no_unnecessary_fk_drops',
Expand Down Expand Up @@ -179,7 +171,6 @@
'dbshell.tests.DbshellCommandTestCase.test_command_missing',
'schema.tests.SchemaTests.test_char_field_pk_to_auto_field',
'datetimes.tests.DateTimesTests.test_21432',
'migrations.test_operations.OperationTests.test_rename_model_with_m2m',

# JSONFields
'model_fields.test_jsonfield.TestQuerying.test_has_key_list',
Expand Down

0 comments on commit 6048db2

Please sign in to comment.