def change_column_attrs(self, model, mutation, field_name, new_attrs): """Returns the SQL for changing one or more column attributes. This will generate all the statements needed for changing a set of attributes for a column. The resulting AlterTableSQLResult contains all the SQL needed to apply these attributes. """ field = model._meta.get_field(field_name) attrs_sql_result = AlterTableSQLResult(self, model) for attr_name, attr_info in new_attrs.iteritems(): method_name = 'change_column_attr_%s' % attr_name evolve_func = getattr(self, method_name) try: sql_result = evolve_func(model, mutation, field, attr_info['old_value'], attr_info['new_value']) assert not sql_result or isinstance(sql_result, SQLResult) except Exception, e: logging.critical( 'Error running database evolver function %s: %s', method_name, e, exc_info=1) raise attrs_sql_result.add(sql_result)
def delete_column(self, model, f): sql_result = AlterTableSQLResult(self, model) if f.rel: sql_result.add(sql_delete_constraints(self.connection, f.rel.to, {f.rel.to: [(model, f)]})) sql_result.add_sql( super(EvolutionOperations, self).delete_column(model, f)) return sql_result
def rename_column(self, model, old_field, new_field): if old_field.column == new_field.column: # No Operation return [] qn = self.connection.ops.quote_name max_name_length = self.connection.ops.max_name_length() sql_result = AlterTableSQLResult(self, model) pre_sql, stash = self.stash_field_ref_constraints(model=model, replaced_fields={ old_field: new_field, }) sql_result.add_pre_sql(pre_sql) sql_result.add_alter_table([{ 'independent': True, 'sql': 'RENAME COLUMN %s TO %s' % (truncate_name(qn(old_field.column), max_name_length), truncate_name(qn(new_field.column), max_name_length)), }]) sql_result.add_post_sql(self.restore_field_ref_constraints(stash)) return sql_result
def delete_column(self, model, f): sql_result = AlterTableSQLResult(self, model) if f.rel: creation = self.connection.creation style = color.no_style() sql_result.add(creation.sql_remove_table_constraints( f.rel.to, {f.rel.to: [(model, f)]}, style)) sql_result.add_sql( super(EvolutionOperations, self).delete_column(model, f)) return sql_result
def change_column_attr_max_length(self, model, mutation, field, old_value, new_value): qn = self.connection.ops.quote_name field.max_length = new_value db_type = field.db_type(connection=self.connection) params = { 'table': qn(model._meta.db_table), 'column': qn(field.column), 'length': field.max_length, 'type': db_type, } return AlterTableSQLResult( self, model, pre_sql=[ 'UPDATE %(table)s SET %(column)s=LEFT(%(column)s,%(length)d);' % params, ], alter_table=[ { 'op': 'MODIFY COLUMN', 'column': field.column, 'db_type': db_type, }, ])
def get_drop_unique_constraint_sql(self, model, index_name): qn = self.connection.ops.quote_name return AlterTableSQLResult( self, model, [{ 'sql': 'DROP CONSTRAINT %s' % qn(index_name) }])
def rename_column(self, model, old_field, new_field): if old_field.column == new_field.column: # No Operation return [] col_type = new_field.db_type(connection=self.connection) if col_type is None: # Skip ManyToManyFields, because they're not represented as # database columns in this table. return [] models = [] refs = {} opts = model._meta pre_sql = self.remove_field_constraints(old_field, opts, models, refs) alter_table_items = self._get_rename_column_sql( opts, old_field, new_field) post_sql = self.add_primary_key_field_constraints( old_field, new_field, models, refs) return AlterTableSQLResult(self, model, pre_sql=pre_sql, alter_table=alter_table_items, post_sql=post_sql)
def rename_column(self, model, old_field, new_field): if old_field.column == new_field.column: # No Operation return [] qn = self.connection.ops.quote_name max_name_length = self.connection.ops.max_name_length() opts = model._meta refs = {} models = [] return AlterTableSQLResult( self, model, pre_sql=self.remove_field_constraints(old_field, opts, models, refs), alter_table=[ { 'independent': True, 'sql': 'RENAME COLUMN %s TO %s' % (truncate_name(qn(old_field.column), max_name_length), truncate_name(qn(new_field.column), max_name_length)), }, ], post_sql=self.add_primary_key_field_constraints( old_field, new_field, models, refs))
def delete_column(self, model, f): return AlterTableSQLResult( self, model, [ { 'op': 'DROP COLUMN', 'column': f.column, 'params': ['CASCADE'] }, ], )
def set_field_null(self, model, field, null): if null: null_attr = 'DEFAULT NULL' else: null_attr = 'NOT NULL' return AlterTableSQLResult( self, model, [{ 'op': 'MODIFY COLUMN', 'column': field.column, 'db_type': field.db_type(connection=self.connection), 'params': [null_attr], }])
def set_field_null(self, model, field, null): if null: attr = 'DROP NOT NULL' else: attr = 'SET NOT NULL' return AlterTableSQLResult(self, model, [ { 'op': 'ALTER COLUMN', 'column': field.column, 'params': [attr], }, ])
def get_change_column_type_sql(self, model, old_field, new_field): """Return SQL to change the type of a column. Version Added: 2.2 Args: model (type): The type of model owning the field. old_field (django.db.models.Field): The old field. new_field (django.db.models.Field): The new field. Returns: django_evolution.sql_result.AlterTableSQLResult: The SQL statements for changing the column type. """ schema = self.build_column_schema( model=model, field=new_field, initial=new_field.default, skip_references=True) alter_table_items = [] if old_field.primary_key: alter_table_items.append({ 'sql': 'DROP PRIMARY KEY', }) params = [schema['db_type']] if new_field.null: params.append('NULL') else: params.append('NOT NULL') alter_table_items.append({ 'op': 'MODIFY', 'column': schema['name'], 'params': [schema['db_type']] + schema['definition'], 'sql_params': schema['definition_sql_params'], }) return AlterTableSQLResult(self, model, alter_table_items)
def delete_column(self, model, f): sql_result = AlterTableSQLResult(self, model) if f.rel: sql_result.add( sql_delete_constraints(self.connection, f.rel.to, {f.rel.to: [(model, f)]})) sql_result.add_sql( super(EvolutionOperations, self).delete_column(model, f)) return sql_result
def get_change_unique_sql(self, model, field, new_unique_value, constraint_name, initial): """Returns the database-specific SQL to change a column's unique flag. This can be overridden by subclasses if they use a different syntax. """ qn = self.connection.ops.quote_name if new_unique_value: alter_table_item = { 'sql': 'ADD CONSTRAINT %s UNIQUE(%s)' % (constraint_name, qn(field.column)) } else: alter_table_item = {'sql': 'DROP CONSTRAINT %s' % constraint_name} return AlterTableSQLResult(self, model, [alter_table_item])
def change_column_attr_max_length(self, model, mutation, field, old_value, new_value): """Returns the SQL for changing a column's max length.""" field.max_length = new_value qn = self.connection.ops.quote_name column = field.column db_type = field.db_type(connection=self.connection) return AlterTableSQLResult(self, model, [ { 'op': 'ALTER COLUMN', 'column': column, 'params': [ 'TYPE %s USING CAST(%s as %s)' % (db_type, qn(column), db_type), ], }, ])
def delete_column(self, model, f): sql_result = AlterTableSQLResult(self, model) remote_field = get_remote_field(f) if remote_field: remote_field_model = get_remote_field_model(remote_field) sql_result.add( sql_delete_constraints(self.connection, remote_field_model, {remote_field_model: [(model, f)]})) sql_result.add_sql( super(EvolutionOperations, self).delete_column(model, f)) return sql_result
def generate_table_op_sql(self, mutator, op, prev_sql_result, prev_op): """Generates SQL for a single mutation operation. This will call different SQL-generating functions provided by the class, depending on the details of the operation. If two adjacent operations can be merged together (meaning that they can be turned into one ALTER TABLE statement), they'll be placed in the same AlterTableSQLResult. """ model = mutator.create_model() op_type = op['type'] mutation = op['mutation'] if prev_op and self._are_ops_mergeable(prev_op, op): sql_result = prev_sql_result else: sql_result = AlterTableSQLResult(self, model) if op_type == 'add_column': field = op['field'] sql_result.add(self.add_column(model, field, op['initial'])) sql_result.add(self.create_index(model, field)) elif op_type == 'change_column': sql_result.add(self.change_column_attrs(model, mutation, op['field'].name, op['new_attrs'])) elif op_type == 'delete_column': sql_result.add(self.delete_column(model, op['field'])) elif op_type == 'change_meta': evolve_func = getattr(self, 'change_meta_%s' % op['prop_name']) sql_result.add(evolve_func(model, op['old_value'], op['new_value'])) elif op_type == 'sql': sql_result.add(op['sql']) else: raise EvolutionNotImplementedError( 'Unknown mutation operation "%s"' % op_type) mutator.finish_op(op) return sql_result
def add_column(self, model, f, initial): qn = self.connection.ops.quote_name sql_result = AlterTableSQLResult(self, model) if f.rel: # it is a foreign key field # NOT NULL REFERENCES "django_evolution_addbasemodel" # ("id") DEFERRABLE INITIALLY DEFERRED # ALTER TABLE <tablename> ADD COLUMN <column name> NULL # REFERENCES <tablename1> ("<colname>") DEFERRABLE INITIALLY # DEFERRED related_model = f.rel.to related_table = related_model._meta.db_table related_pk_col = related_model._meta.pk.name constraints = ['%sNULL' % (not f.null and 'NOT ' or '')] if f.unique or f.primary_key: constraints.append('UNIQUE') sql_result.add_alter_table([ { 'op': 'ADD COLUMN', 'column': f.column, 'db_type': f.db_type(connection=self.connection), 'params': constraints + [ 'REFERENCES', qn(related_table), '(%s)' % qn(related_pk_col), self.connection.ops.deferrable_sql(), ] } ]) else: null_constraints = '%sNULL' % (not f.null and 'NOT ' or '') if f.unique or f.primary_key: unique_constraints = 'UNIQUE' else: unique_constraints = '' # At this point, initial can only be None if null=True, # otherwise it is a user callable or the default # AddFieldInitialCallback which will shortly raise an exception. if initial is not None: if callable(initial): sql_result.add_alter_table([ { 'op': 'ADD COLUMN', 'column': f.column, 'db_type': f.db_type(connection=self.connection), 'params': [unique_constraints], } ]) sql_result.add_sql([ 'UPDATE %s SET %s = %s WHERE %s IS NULL;' % (qn(model._meta.db_table), qn(f.column), initial(), qn(f.column)) ]) if not f.null: # Only put this sql statement if the column cannot # be null. sql_result.add_sql( self.set_field_null(model, f, f.null)) else: sql_result.add_alter_table([ { 'op': 'ADD COLUMN', 'column': f.column, 'db_type': f.db_type(connection=self.connection), 'params': [ null_constraints, unique_constraints, 'DEFAULT', '%s', ], 'sql_params': [initial] } ]) # Django doesn't generate default columns, so now that # we've added one to get default values for existing # tables, drop that default. sql_result.add_post_sql([ 'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT;' % (qn(model._meta.db_table), qn(f.column)) ]) else: sql_result.add_alter_table([ { 'op': 'ADD COLUMN', 'column': f.column, 'db_type': f.db_type(connection=self.connection), 'params': [null_constraints, unique_constraints], } ]) if f.unique or f.primary_key: self.record_index(model, [f], use_constraint_name=True, unique=True) return sql_result
def get_drop_unique_constraint_sql(self, model, index_name): return AlterTableSQLResult(self, model, [{ 'sql': 'DROP CONSTRAINT %s' % index_name }])
def get_change_column_type_sql(self, model, old_field, new_field): """Return SQL to change the type of a column. Version Added: 2.2 Args: model (type): The type of model owning the field. old_field (django.db.models.Field): The old field. new_field (django.db.models.Field): The new field. Returns: django_evolution.sql_result.AlterTableSQLResult: The SQL statements for changing the column type. """ connection = self.connection qn = connection.ops.quote_name schema = self.build_column_schema( model=model, field=new_field, initial=new_field.default, skip_null_constraint=True, skip_primary_or_unique_constraint=True, skip_references=True) column_name = schema['name'] table_name = model._meta.db_table sql_result = AlterTableSQLResult(self, model) old_field_type = old_field.db_type(connection=connection).lower() new_field_type = schema['db_type'].lower() was_serial = old_field_type in self.alter_field_type_map is_serial = new_field_type in self.alter_field_type_map if is_serial: # This is a serial field. We will need to change the type and # update the sequence. We will also need to choose the actual # type to set for the column definition. new_field_type = self.alter_field_type_map.get( new_field_type, new_field_type) alter_type_params = ['TYPE', new_field_type] + schema['definition'] if not self._are_column_types_compatible(old_field, new_field): alter_type_params += [ 'USING', '%s::%s' % (column_name, new_field_type), ] sql_result.add_alter_table([{ 'op': 'ALTER COLUMN', 'column': column_name, 'params': alter_type_params, 'sql_params': schema['definition_sql_params'], }]) if is_serial: # Reset the sequence. sequence_name = '%s_%s_seq' % (table_name, column_name) sequence_sql_result = AlterTableSQLResult(self, model) sequence_sql_result.add_pre_sql([ 'DROP SEQUENCE IF EXISTS %s CASCADE;' % qn(sequence_name), 'CREATE SEQUENCE %s;' % qn(sequence_name), ]) sequence_sql_result.add_alter_table([{ 'op': 'ALTER COLUMN', 'column': column_name, 'params': [ 'SET', 'DEFAULT', "nextval('%s')" % qn(sequence_name), ], }]) sequence_sql_result.add_post_sql([ "SELECT setval('%s', MAX(%s)) FROM %s;" % (qn(sequence_name), qn(column_name), qn(table_name)), 'ALTER SEQUENCE %s OWNED BY %s.%s;' % (qn(sequence_name), qn(table_name), qn(column_name)), ]) sql_result.add_post_sql(sequence_sql_result) elif was_serial: # Drop the old sequence, since we no longer need it. sequence_name = '%s_%s_seq' % (table_name, old_field.column) sql_result.add_post_sql([ 'DROP SEQUENCE IF EXISTS %s CASCADE;' % qn(sequence_name), ]) return sql_result
def generate_table_op_sql(self, mutator, op, prev_sql_result, prev_op): """Generates SQL for a single mutation operation. This will call different SQL-generating functions provided by the class, depending on the details of the operation. If two adjacent operations can be merged together (meaning that they can be turned into one ALTER TABLE statement), they'll be placed in the same AlterTableSQLResult. """ model = mutator.create_model() op_type = op['type'] mutation = op['mutation'] if prev_op and self._are_ops_mergeable(prev_op, op): sql_result = prev_sql_result else: sql_result = AlterTableSQLResult(self, model) if op_type == 'add_column': field = op['field'] sql_result.add(self.add_column(model, field, op['initial'])) sql_result.add(self.create_index(model, field)) elif op_type == 'change_column': sql_result.add( self.change_column_attrs(model, mutation, op['field'].name, op['new_attrs'])) elif op_type == 'delete_column': sql_result.add(self.delete_column(model, op['field'])) elif op_type == 'change_meta': evolve_func = getattr(self, 'change_meta_%s' % op['prop_name']) sql_result.add(evolve_func(model, op['old_value'], op['new_value'])) elif op_type == 'sql': sql_result.add(op['sql']) else: raise EvolutionNotImplementedError( 'Unknown mutation operation "%s"' % op_type) mutator.finish_op(op) return sql_result
def add_column(self, model, f, initial): qn = self.connection.ops.quote_name sql_result = AlterTableSQLResult(self, model) if f.rel: # it is a foreign key field # NOT NULL REFERENCES "django_evolution_addbasemodel" # ("id") DEFERRABLE INITIALLY DEFERRED # ALTER TABLE <tablename> ADD COLUMN <column name> NULL # REFERENCES <tablename1> ("<colname>") DEFERRABLE INITIALLY # DEFERRED related_model = f.rel.to related_table = related_model._meta.db_table related_pk_col = related_model._meta.pk.name constraints = ['%sNULL' % (not f.null and 'NOT ' or '')] if f.unique or f.primary_key: constraints.append('UNIQUE') sql_result.add_alter_table([{ 'op': 'ADD COLUMN', 'column': f.column, 'db_type': f.db_type(connection=self.connection), 'params': constraints + [ 'REFERENCES', qn(related_table), '(%s)' % qn(related_pk_col), self.connection.ops.deferrable_sql(), ] }]) else: null_constraints = '%sNULL' % (not f.null and 'NOT ' or '') if f.unique or f.primary_key: unique_constraints = 'UNIQUE' else: unique_constraints = '' # At this point, initial can only be None if null=True, # otherwise it is a user callable or the default # AddFieldInitialCallback which will shortly raise an exception. if initial is not None: if callable(initial): sql_result.add_alter_table([{ 'op': 'ADD COLUMN', 'column': f.column, 'db_type': f.db_type(connection=self.connection), 'params': [unique_constraints], }]) sql_result.add_sql([ 'UPDATE %s SET %s = %s WHERE %s IS NULL;' % (qn(model._meta.db_table), qn( f.column), initial(), qn(f.column)) ]) if not f.null: # Only put this sql statement if the column cannot # be null. sql_result.add_sql( self.set_field_null(model, f, f.null)) else: sql_result.add_alter_table([{ 'op': 'ADD COLUMN', 'column': f.column, 'db_type': f.db_type(connection=self.connection), 'params': [ null_constraints, unique_constraints, 'DEFAULT', '%s', ], 'sql_params': [initial] }]) # Django doesn't generate default columns, so now that # we've added one to get default values for existing # tables, drop that default. sql_result.add_post_sql([ 'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT;' % (qn(model._meta.db_table), qn(f.column)) ]) else: sql_result.add_alter_table([{ 'op': 'ADD COLUMN', 'column': f.column, 'db_type': f.db_type(connection=self.connection), 'params': [null_constraints, unique_constraints], }]) if f.unique or f.primary_key: self.record_index(model, [f], use_constraint_name=True, unique=True) return sql_result
def rename_column(self, model, old_field, new_field): """Rename the specified column. This will rename the column through ``ALTER TABLE .. CHANGE COLUMN``. Any constraints on the column will be stashed away before the ``ALTER TABLE`` and restored afterward. If the column has not actually changed, or it's not a real column (a many-to-many relation), then this will return empty statements. Args: model (type): The model representing the table containing the column. old_field (django.db.models.Field): The old field definition. new_field (django.db.models.Field): The new field definition. Returns: django_evolution.db.sql_result.AlterTableSQLResult or list: The statements for renaming the column. This may be an empty list if the column won't be renamed. """ if old_field.column == new_field.column: # No Operation return [] col_type = new_field.db_type(connection=self.connection) if col_type is None: # Skip ManyToManyFields, because they're not represented as # database columns in this table. return [] qn = self.connection.ops.quote_name sql_result = AlterTableSQLResult(self, model) pre_sql, stash = self.stash_field_ref_constraints( model=model, replaced_fields={ old_field: new_field, }) sql_result.add_pre_sql(pre_sql) schema = self.build_column_schema(model=model, field=new_field, initial=new_field.default) alter_table_items = [] if old_field.primary_key: alter_table_items.append('DROP PRIMARY KEY') alter_table_items.append( 'CHANGE COLUMN %s %s' % (qn(old_field.column), ' '.join([ qn(schema['name']), schema['db_type'], ] + schema['definition']))) sql_result.add_alter_table([{ 'sql': ', '.join(alter_table_items), }]) sql_result.add_post_sql(self.restore_field_ref_constraints(stash)) return sql_result