package liquibase.diff.output.changelog.core; import liquibase.change.Change; import liquibase.change.ColumnConfig; import liquibase.change.ConstraintsConfig; import liquibase.change.core.CreateTableChange; import liquibase.database.Database; import liquibase.database.core.InformixDatabase; import liquibase.database.core.MSSQLDatabase; import liquibase.database.core.MySQLDatabase; import liquibase.database.core.PostgresDatabase; import liquibase.datatype.DataTypeFactory; import liquibase.datatype.DatabaseDataType; import liquibase.datatype.LiquibaseDataType; import liquibase.datatype.core.DateTimeType; import liquibase.diff.output.DiffOutputControl; import liquibase.diff.output.changelog.AbstractChangeGenerator; import liquibase.diff.output.changelog.ChangeGeneratorChain; import liquibase.diff.output.changelog.MissingObjectChangeGenerator; import liquibase.statement.DatabaseFunction; import liquibase.structure.DatabaseObject; import liquibase.structure.core.Column; import liquibase.structure.core.PrimaryKey; import liquibase.structure.core.Table; import java.math.BigInteger; import java.util.Date; public class MissingTableChangeGenerator extends AbstractChangeGenerator implements MissingObjectChangeGenerator { @Override public int getPriority(Class<? extends DatabaseObject> objectType, Database database) { if (Table.class.isAssignableFrom(objectType)) { return PRIORITY_DEFAULT; } return PRIORITY_NONE; } @Override public Class<? extends DatabaseObject>[] runAfterTypes() { return null; } @Override public Class<? extends DatabaseObject>[] runBeforeTypes() { return null; } @Override public Change[] fixMissing(DatabaseObject missingObject, DiffOutputControl control, Database referenceDatabase, Database comparisonDatabase, ChangeGeneratorChain chain) { Table missingTable = (Table) missingObject; PrimaryKey primaryKey = missingTable.getPrimaryKey(); // if (control.diffResult.getReferenceSnapshot().getDatabase().isLiquibaseTable(missingTable.getSchema().toCatalogAndSchema(), missingTable.getName())) { // continue; // } CreateTableChange change = createCreateTableChange(); change.setTableName(missingTable.getName()); if (control.getIncludeCatalog()) { change.setCatalogName(missingTable.getSchema().getCatalogName()); } if (control.getIncludeSchema()) { change.setSchemaName(missingTable.getSchema().getName()); } if (missingTable.getRemarks() != null) { change.setRemarks(missingTable.getRemarks()); } for (Column column : missingTable.getColumns()) { ColumnConfig columnConfig = new ColumnConfig(); columnConfig.setName(column.getName()); LiquibaseDataType ldt = DataTypeFactory.getInstance().from(column.getType(), referenceDatabase); DatabaseDataType ddt = ldt.toDatabaseDataType(comparisonDatabase); String typeString = ddt.toString(); if (comparisonDatabase instanceof MSSQLDatabase) { typeString = comparisonDatabase.unescapeDataTypeString(typeString); } columnConfig.setType(typeString); if (column.isAutoIncrement()) { columnConfig.setAutoIncrement(true); } ConstraintsConfig constraintsConfig = null; // In MySQL, the primary key must be specified at creation for an autoincrement column if (column.isAutoIncrement() && primaryKey != null && primaryKey.getColumns().size() == 1 && primaryKey.getColumnNamesAsList().contains(column.getName())) { if (referenceDatabase instanceof MSSQLDatabase && primaryKey.getBackingIndex() != null && primaryKey.getBackingIndex().getClustered() != null && !primaryKey.getBackingIndex().getClustered()) { // have to handle PK as a separate statement } else if (referenceDatabase instanceof PostgresDatabase && primaryKey.getBackingIndex() != null && primaryKey.getBackingIndex().getClustered() != null && primaryKey.getBackingIndex().getClustered()) { // have to handle PK as a separate statement } else { constraintsConfig = new ConstraintsConfig(); if (shouldAddPrimarykeyToConstraints(missingObject, control, referenceDatabase, comparisonDatabase)) { constraintsConfig.setPrimaryKey(true); constraintsConfig.setPrimaryKeyTablespace(primaryKey.getTablespace()); // MySQL sets some primary key names as PRIMARY which is invalid if (comparisonDatabase instanceof MySQLDatabase && "PRIMARY".equals(primaryKey.getName())) { constraintsConfig.setPrimaryKeyName(null); } else { constraintsConfig.setPrimaryKeyName(primaryKey.getName()); } control.setAlreadyHandledMissing(primaryKey); control.setAlreadyHandledMissing(primaryKey.getBackingIndex()); } else { constraintsConfig.setNullable(false); } } } else if (column.isNullable() != null && !column.isNullable()) { constraintsConfig = new ConstraintsConfig(); constraintsConfig.setNullable(false); } if (constraintsConfig != null) { columnConfig.setConstraints(constraintsConfig); } setDefaultValue(columnConfig, column, referenceDatabase); if (column.getRemarks() != null) { columnConfig.setRemarks(column.getRemarks()); } if (column.getAutoIncrementInformation() != null) { BigInteger startWith = column.getAutoIncrementInformation().getStartWith(); BigInteger incrementBy = column.getAutoIncrementInformation().getIncrementBy(); if (startWith != null && !startWith.equals(BigInteger.ONE)) { columnConfig.setStartWith(startWith); } if (incrementBy != null && !incrementBy.equals(BigInteger.ONE)) { columnConfig.setIncrementBy(incrementBy); } } change.addColumn(columnConfig); control.setAlreadyHandledMissing(column); } return new Change[]{ change }; } public boolean shouldAddPrimarykeyToConstraints(DatabaseObject missingObject, DiffOutputControl control, Database referenceDatabase, Database comparisonDatabase) { return true; } public static void setDefaultValue(ColumnConfig columnConfig, Column column, Database database) { LiquibaseDataType dataType = DataTypeFactory.getInstance().from(column.getType(), database); Object defaultValue = column.getDefaultValue(); if (defaultValue == null) { // do nothing } else if (column.isAutoIncrement()) { // do nothing } else if (defaultValue instanceof Date) { columnConfig.setDefaultValueDate((Date) defaultValue); } else if (defaultValue instanceof Boolean) { columnConfig.setDefaultValueBoolean(((Boolean) defaultValue)); } else if (defaultValue instanceof Number) { columnConfig.setDefaultValueNumeric(((Number) defaultValue)); } else if (defaultValue instanceof DatabaseFunction) { DatabaseFunction function = (DatabaseFunction) defaultValue; if ("current".equals(function.getValue())) { if (database instanceof InformixDatabase) { if (dataType instanceof DateTimeType) { if (dataType.getAdditionalInformation() == null || dataType.getAdditionalInformation().length() == 0) { if (dataType.getParameters() != null && dataType.getParameters().length > 0) { String parameter = String.valueOf(dataType.getParameters()[0]); if ("4365".equals(parameter)) { function = new DatabaseFunction("current year to fraction(3)"); } if ("3594".equals(parameter)) { function = new DatabaseFunction("current year to second"); } if ("3080".equals(parameter)) { function = new DatabaseFunction("current year to minute"); } if ("2052".equals(parameter)) { function = new DatabaseFunction("current year to day"); } } } } } } columnConfig.setDefaultValueComputed(function); } else { String defaultValueString = null; try { defaultValueString = DataTypeFactory.getInstance().from(column.getType(), database).objectToSql(defaultValue, database); } catch (NullPointerException e) { throw e; } if (defaultValueString != null) { defaultValueString = defaultValueString.replaceFirst("'", "").replaceAll("'$", ""); } columnConfig.setDefaultValue(defaultValueString); } columnConfig.setDefaultValueConstraintName(column.getDefaultValueConstraintName()); } protected CreateTableChange createCreateTableChange() { return new CreateTableChange(); } }