diff options
| author | Andrew Godwin <andrew@aeracode.org> | 2013-12-11 14:19:05 +0000 |
|---|---|---|
| committer | Andrew Godwin <andrew@aeracode.org> | 2013-12-11 14:19:05 +0000 |
| commit | 5db028affbffa2dc7a957fcf73401830b43bf540 (patch) | |
| tree | 8d4286589ef14f7d5d654ce8185854eac701a661 /django | |
| parent | cee4fe73071b469edf118bec3a080ffca906d323 (diff) | |
Fix altering of SERIAL columns and InnoDB being picky about FK changes
Diffstat (limited to 'django')
| -rw-r--r-- | django/db/backends/postgresql_psycopg2/schema.py | 55 | ||||
| -rw-r--r-- | django/db/backends/schema.py | 65 | ||||
| -rw-r--r-- | django/db/migrations/operations/fields.py | 12 |
3 files changed, 116 insertions, 16 deletions
diff --git a/django/db/backends/postgresql_psycopg2/schema.py b/django/db/backends/postgresql_psycopg2/schema.py index b86e0857bb..946f39d586 100644 --- a/django/db/backends/postgresql_psycopg2/schema.py +++ b/django/db/backends/postgresql_psycopg2/schema.py @@ -2,4 +2,57 @@ from django.db.backends.schema import BaseDatabaseSchemaEditor class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): - pass + + sql_create_sequence = "CREATE SEQUENCE %(sequence)s" + sql_delete_sequence = "DROP SEQUENCE IF EXISTS %(sequence)s CASCADE" + sql_set_sequence_max = "SELECT setval('%(sequence)s', MAX(%(column)s)) FROM %(table)s" + + def _alter_column_type_sql(self, table, column, type): + """ + Makes ALTER TYPE with SERIAL make sense. + """ + if type.lower() == "serial": + sequence_name = "%s_%s_seq" % (table, column) + return ( + ( + self.sql_alter_column_type % { + "column": self.quote_name(column), + "type": "integer", + }, + [], + ), + [ + ( + self.sql_delete_sequence % { + "sequence": sequence_name, + }, + [], + ), + ( + self.sql_create_sequence % { + "sequence": sequence_name, + }, + [], + ), + ( + self.sql_alter_column % { + "table": table, + "changes": self.sql_alter_column_default % { + "column": column, + "default": "nextval('%s')" % sequence_name, + } + }, + [], + ), + ( + self.sql_set_sequence_max % { + "table": table, + "column": column, + "sequence": sequence_name, + }, + [], + ), + ], + ) + else: + return super(DatabaseSchemaEditor, self)._alter_column_type_sql(table, column, type) diff --git a/django/db/backends/schema.py b/django/db/backends/schema.py index 5e3ad4dd80..e33956763c 100644 --- a/django/db/backends/schema.py +++ b/django/db/backends/schema.py @@ -498,6 +498,18 @@ class BaseDatabaseSchemaEditor(object): "name": fk_name, } ) + # Drop incoming FK constraints if we're a primary key and things are going + # to change. + if old_field.primary_key and new_field.primary_key and old_type != new_type: + for rel in new_field.model._meta.get_all_related_objects(): + rel_fk_names = self._constraint_names(rel.model, [rel.field.column], foreign_key=True) + for fk_name in rel_fk_names: + self.execute( + self.sql_delete_fk % { + "table": self.quote_name(rel.model._meta.db_table), + "name": fk_name, + } + ) # Change check constraints? if old_db_params['check'] != new_db_params['check'] and old_db_params['check']: constraint_names = self._constraint_names(model, [old_field.column], check=True) @@ -524,15 +536,12 @@ class BaseDatabaseSchemaEditor(object): }) # Next, start accumulating actions to do actions = [] + post_actions = [] # Type change? if old_type != new_type: - actions.append(( - self.sql_alter_column_type % { - "column": self.quote_name(new_field.column), - "type": new_type, - }, - [], - )) + fragment, other_actions = self._alter_column_type_sql(model._meta.db_table, new_field.column, new_type) + actions.append(fragment) + post_actions.extend(other_actions) # Default change? old_default = self.effective_default(old_field) new_default = self.effective_default(new_field) @@ -596,6 +605,9 @@ class BaseDatabaseSchemaEditor(object): }, params, ) + if post_actions: + for sql, params in post_actions: + self.execute(sql, params) # Added a unique? if not old_field.unique and new_field.unique: self.execute( @@ -619,7 +631,7 @@ class BaseDatabaseSchemaEditor(object): # referring to us. rels_to_update = [] if old_field.primary_key and new_field.primary_key and old_type != new_type: - rels_to_update.extend(model._meta.get_all_related_objects()) + rels_to_update.extend(new_field.model._meta.get_all_related_objects()) # Changed to become primary key? # Note that we don't detect unsetting of a PK, as we assume another field # will always come along and replace it. @@ -647,8 +659,8 @@ class BaseDatabaseSchemaEditor(object): } ) # Update all referencing columns - rels_to_update.extend(model._meta.get_all_related_objects()) - # Handle out type alters on the other end of rels from the PK stuff above + rels_to_update.extend(new_field.model._meta.get_all_related_objects()) + # Handle our type alters on the other end of rels from the PK stuff above for rel in rels_to_update: rel_db_params = rel.field.db_parameters(connection=self.connection) rel_type = rel_db_params['type'] @@ -672,6 +684,18 @@ class BaseDatabaseSchemaEditor(object): "to_column": self.quote_name(new_field.rel.get_related_field().column), } ) + # Rebuild FKs that pointed to us if we previously had to drop them + if old_field.primary_key and new_field.primary_key and old_type != new_type: + for rel in new_field.model._meta.get_all_related_objects(): + self.execute( + self.sql_create_fk % { + "table": self.quote_name(rel.model._meta.db_table), + "name": self._create_index_name(rel.model, [rel.field.column], suffix="_fk"), + "column": self.quote_name(rel.field.column), + "to_table": self.quote_name(model._meta.db_table), + "to_column": self.quote_name(new_field.column), + } + ) # Does it have check constraints we need to add? if old_db_params['check'] != new_db_params['check'] and new_db_params['check']: self.execute( @@ -686,6 +710,27 @@ class BaseDatabaseSchemaEditor(object): if self.connection.features.connection_persists_old_columns: self.connection.close() + def _alter_column_type_sql(self, table, column, type): + """ + Hook to specialise column type alteration for different backends, + for cases when a creation type is different to an alteration type + (e.g. SERIAL in PostgreSQL, PostGIS fields). + + Should return two things; an SQL fragment of (sql, params) to insert + into an ALTER TABLE statement, and a list of extra (sql, params) tuples + to run once the field is altered. + """ + return ( + ( + self.sql_alter_column_type % { + "column": self.quote_name(column), + "type": type, + }, + [], + ), + [], + ) + def _alter_many_to_many(self, model, old_field, new_field, strict): """ Alters M2Ms to repoint their to= endpoints. diff --git a/django/db/migrations/operations/fields.py b/django/db/migrations/operations/fields.py index 73efef4691..c5f0bd1e2b 100644 --- a/django/db/migrations/operations/fields.py +++ b/django/db/migrations/operations/fields.py @@ -24,9 +24,10 @@ class AddField(Operation): state.models[app_label, self.model_name.lower()].fields.append((self.name, field)) def database_forwards(self, app_label, schema_editor, from_state, to_state): + from_model = from_state.render().get_model(app_label, self.model_name) to_model = to_state.render().get_model(app_label, self.model_name) if router.allow_migrate(schema_editor.connection.alias, to_model): - schema_editor.add_field(to_model, to_model._meta.get_field_by_name(self.name)[0]) + schema_editor.add_field(from_model, to_model._meta.get_field_by_name(self.name)[0]) def database_backwards(self, app_label, schema_editor, from_state, to_state): from_model = from_state.render().get_model(app_label, self.model_name) @@ -73,9 +74,10 @@ class RemoveField(Operation): schema_editor.remove_field(from_model, from_model._meta.get_field_by_name(self.name)[0]) def database_backwards(self, app_label, schema_editor, from_state, to_state): + from_model = from_state.render().get_model(app_label, self.model_name) to_model = to_state.render().get_model(app_label, self.model_name) if router.allow_migrate(schema_editor.connection.alias, to_model): - schema_editor.add_field(to_model, to_model._meta.get_field_by_name(self.name)[0]) + schema_editor.add_field(from_model, to_model._meta.get_field_by_name(self.name)[0]) def describe(self): return "Remove field %s from %s" % (self.name, self.model_name) @@ -107,7 +109,7 @@ class AlterField(Operation): to_model = to_state.render().get_model(app_label, self.model_name) if router.allow_migrate(schema_editor.connection.alias, to_model): schema_editor.alter_field( - to_model, + from_model, from_model._meta.get_field_by_name(self.name)[0], to_model._meta.get_field_by_name(self.name)[0], ) @@ -153,7 +155,7 @@ class RenameField(Operation): to_model = to_state.render().get_model(app_label, self.model_name) if router.allow_migrate(schema_editor.connection.alias, to_model): schema_editor.alter_field( - to_model, + from_model, from_model._meta.get_field_by_name(self.old_name)[0], to_model._meta.get_field_by_name(self.new_name)[0], ) @@ -163,7 +165,7 @@ class RenameField(Operation): to_model = to_state.render().get_model(app_label, self.model_name) if router.allow_migrate(schema_editor.connection.alias, to_model): schema_editor.alter_field( - to_model, + from_model, from_model._meta.get_field_by_name(self.new_name)[0], to_model._meta.get_field_by_name(self.old_name)[0], ) |
