package liquibase.diff.output.changelog.core; import liquibase.change.AddColumnConfig; import liquibase.change.Change; import liquibase.change.core.*; import liquibase.database.Database; import liquibase.database.core.OracleDatabase; import liquibase.datatype.DataTypeFactory; import liquibase.datatype.LiquibaseDataType; import liquibase.diff.Difference; import liquibase.diff.ObjectDifferences; import liquibase.diff.output.DiffOutputControl; import liquibase.diff.output.changelog.AbstractChangeGenerator; import liquibase.diff.output.changelog.ChangeGeneratorChain; import liquibase.diff.output.changelog.ChangedObjectChangeGenerator; import liquibase.logging.LogFactory; import liquibase.statement.DatabaseFunction; import liquibase.structure.DatabaseObject; import liquibase.structure.core.*; import liquibase.util.ISODateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; public class ChangedColumnChangeGenerator extends AbstractChangeGenerator implements ChangedObjectChangeGenerator { @Override public int getPriority(Class<? extends DatabaseObject> objectType, Database database) { if (Column.class.isAssignableFrom(objectType)) { return PRIORITY_DEFAULT; } return PRIORITY_NONE; } @Override public Class<? extends DatabaseObject>[] runAfterTypes() { return new Class[] { Table.class }; } @Override public Class<? extends DatabaseObject>[] runBeforeTypes() { return new Class[] { PrimaryKey.class }; } @Override public Change[] fixChanged(DatabaseObject changedObject, ObjectDifferences differences, DiffOutputControl control, Database referenceDatabase, Database comparisonDatabase, ChangeGeneratorChain chain) { Column column = (Column) changedObject; if (column.getRelation() instanceof View) { return null; } if (column.getRelation().getSnapshotId() == null) { //not an actual table, maybe an alias, maybe in a different schema. Don't fix it. return null; } List<Change> changes = new ArrayList<Change>(); handleTypeDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase); handleNullableDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase); handleDefaultValueDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase); handleAutoIncrementDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase); Difference remarksDiff = differences.getDifference("remarks"); if (remarksDiff != null) { SetColumnRemarksChange change = new SetColumnRemarksChange(); if (control.getIncludeCatalog()) { change.setCatalogName(column.getSchema().getCatalogName()); } if (control.getIncludeSchema()) { change.setSchemaName(column.getSchema().getName()); } change.setTableName(column.getRelation().getName()); change.setColumnName(column.getName()); change.setRemarks(column.getRemarks()); changes.add(change); } return changes.toArray(new Change[changes.size()]); } protected void handleNullableDifferences(Column column, ObjectDifferences differences, DiffOutputControl control, List<Change> changes, Database referenceDatabase, Database comparisonDatabase) { Difference nullableDifference = differences.getDifference("nullable"); if (nullableDifference != null && nullableDifference.getReferenceValue() != null) { boolean nullable = (Boolean) nullableDifference.getReferenceValue(); if (nullable) { DropNotNullConstraintChange change = new DropNotNullConstraintChange(); if (control.getIncludeCatalog()) { change.setCatalogName(column.getRelation().getSchema().getCatalog().getName()); } if (control.getIncludeSchema()) { change.setSchemaName(column.getRelation().getSchema().getName()); } change.setTableName(column.getRelation().getName()); change.setColumnName(column.getName()); change.setColumnDataType(DataTypeFactory.getInstance().from(column.getType(), comparisonDatabase).toString()); changes.add(change); } else { AddNotNullConstraintChange change = new AddNotNullConstraintChange(); if (control.getIncludeCatalog()) { change.setCatalogName(column.getRelation().getSchema().getCatalog().getName()); } if (control.getIncludeSchema()) { change.setSchemaName(column.getRelation().getSchema().getName()); } change.setTableName(column.getRelation().getName()); change.setColumnName(column.getName()); change.setColumnDataType(DataTypeFactory.getInstance().from(column.getType(), comparisonDatabase).toString()); changes.add(change); } } } protected void handleAutoIncrementDifferences(Column column, ObjectDifferences differences, DiffOutputControl control, List<Change> changes, Database referenceDatabase, Database comparisonDatabase) { Difference difference = differences.getDifference("autoIncrementInformation"); if (difference != null) { if (difference.getReferenceValue() == null) { LogFactory.getLogger().info("ChangedColumnChangeGenerator cannot fix dropped auto increment values"); //todo: Support dropping auto increments } else { AddAutoIncrementChange change = new AddAutoIncrementChange(); if (control.getIncludeCatalog()) { change.setCatalogName(column.getRelation().getSchema().getCatalog().getName()); } if (control.getIncludeSchema()) { change.setSchemaName(column.getRelation().getSchema().getName()); } change.setTableName(column.getRelation().getName()); change.setColumnName(column.getName()); change.setColumnDataType(DataTypeFactory.getInstance().from(column.getType(), comparisonDatabase).toString()); changes.add(change); } } } protected void handleTypeDifferences(Column column, ObjectDifferences differences, DiffOutputControl control, List<Change> changes, Database referenceDatabase, Database comparisonDatabase) { Difference typeDifference = differences.getDifference("type"); if (typeDifference != null) { String catalogName = null; String schemaName = null; if (control.getIncludeCatalog()) { catalogName = column.getRelation().getSchema().getCatalog().getName(); } if (control.getIncludeSchema()) { schemaName = column.getRelation().getSchema().getName(); } String tableName = column.getRelation().getName(); if (comparisonDatabase instanceof OracleDatabase && (((DataType) typeDifference.getReferenceValue()).getTypeName().equalsIgnoreCase("clob") || ((DataType) typeDifference.getComparedValue()).getTypeName().equalsIgnoreCase("clob"))) { String tempColName = "TEMP_CLOB_CONVERT"; OutputChange outputChange = new OutputChange(); outputChange.setMessage("Cannot convert directly from " + ((DataType) typeDifference.getComparedValue()).getTypeName()+" to "+((DataType) typeDifference.getReferenceValue()).getTypeName()+". Instead a new column will be created and the data transferred. This may cause unexpected side effects including constraint issues and/or table locks."); changes.add(outputChange); AddColumnChange addColumn = new AddColumnChange(); addColumn.setCatalogName(catalogName); addColumn.setSchemaName(schemaName); addColumn.setTableName(tableName); AddColumnConfig addColumnConfig = new AddColumnConfig(column); addColumnConfig.setName(tempColName); addColumnConfig.setType(typeDifference.getReferenceValue().toString()); addColumnConfig.setAfterColumn(column.getName()); addColumn.setColumns(Arrays.asList(addColumnConfig)); changes.add(addColumn); changes.add(new RawSQLChange("UPDATE "+referenceDatabase.escapeObjectName(tableName, Table.class)+" SET "+tempColName+"="+referenceDatabase.escapeObjectName(column.getName(), Column.class))); DropColumnChange dropColumnChange = new DropColumnChange(); dropColumnChange.setCatalogName(catalogName); dropColumnChange.setSchemaName(schemaName); dropColumnChange.setTableName(tableName); dropColumnChange.setColumnName(column.getName()); changes.add(dropColumnChange); RenameColumnChange renameColumnChange = new RenameColumnChange(); renameColumnChange.setCatalogName(catalogName); renameColumnChange.setSchemaName(schemaName); renameColumnChange.setTableName(tableName); renameColumnChange.setOldColumnName(tempColName); renameColumnChange.setNewColumnName(column.getName()); changes.add(renameColumnChange); } else { ModifyDataTypeChange change = new ModifyDataTypeChange(); change.setCatalogName(catalogName); change.setSchemaName(schemaName); change.setTableName(tableName); change.setColumnName(column.getName()); DataType referenceType = (DataType) typeDifference.getReferenceValue(); change.setNewDataType(DataTypeFactory.getInstance().from(referenceType, comparisonDatabase).toString()); changes.add(change); } } } protected void handleDefaultValueDifferences(Column column, ObjectDifferences differences, DiffOutputControl control, List<Change> changes, Database referenceDatabase, Database comparisonDatabase) { Difference difference = differences.getDifference("defaultValue"); if (difference != null) { Object value = difference.getReferenceValue(); LiquibaseDataType columnDataType = DataTypeFactory.getInstance().from(column.getType(), comparisonDatabase); if (value == null) { DropDefaultValueChange change = new DropDefaultValueChange(); if (control.getIncludeCatalog()) { change.setCatalogName(column.getRelation().getSchema().getCatalog().getName()); } if (control.getIncludeSchema()) { change.setSchemaName(column.getRelation().getSchema().getName()); } change.setTableName(column.getRelation().getName()); change.setColumnName(column.getName()); change.setColumnDataType(columnDataType.toString()); changes.add(change); } else { AddDefaultValueChange change = new AddDefaultValueChange(); if (control.getIncludeCatalog()) { change.setCatalogName(column.getRelation().getSchema().getCatalog().getName()); } if (control.getIncludeSchema()) { change.setSchemaName(column.getRelation().getSchema().getName()); } change.setTableName(column.getRelation().getName()); change.setColumnName(column.getName()); change.setColumnDataType(columnDataType.toString()); if (value instanceof Boolean) { change.setDefaultValueBoolean((Boolean) value); } else if (value instanceof Date) { change.setDefaultValueDate(new ISODateFormat().format(((Date) value))); } else if (value instanceof Number) { change.setDefaultValueNumeric(value.toString()); } else if (value instanceof DatabaseFunction) { change.setDefaultValueComputed(((DatabaseFunction) value)); } else { change.setDefaultValue(value.toString()); } change.setDefaultValueConstraintName(column.getDefaultValueConstraintName()); changes.add(change); } } } }