package io.ebean.dbmigration.ddlgeneration.platform; import io.ebean.config.DbConstraintNaming; import io.ebean.config.NamingConvention; import io.ebean.config.ServerConfig; import io.ebean.config.dbplatform.IdType; import io.ebean.dbmigration.ddlgeneration.DdlBuffer; import io.ebean.dbmigration.ddlgeneration.DdlWrite; import io.ebean.dbmigration.ddlgeneration.TableDdl; import io.ebean.dbmigration.ddlgeneration.platform.util.IndexSet; import io.ebean.dbmigration.migration.AddColumn; import io.ebean.dbmigration.migration.AddHistoryTable; import io.ebean.dbmigration.migration.AddTableComment; import io.ebean.dbmigration.migration.AlterColumn; import io.ebean.dbmigration.migration.Column; import io.ebean.dbmigration.migration.CreateIndex; import io.ebean.dbmigration.migration.CreateTable; import io.ebean.dbmigration.migration.DropColumn; import io.ebean.dbmigration.migration.DropHistoryTable; import io.ebean.dbmigration.migration.DropIndex; import io.ebean.dbmigration.migration.DropTable; import io.ebean.dbmigration.migration.ForeignKey; import io.ebean.dbmigration.migration.UniqueConstraint; import io.ebean.dbmigration.model.MTable; import io.ebean.util.StringHelper; import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Base implementation for 'create table' and 'alter table' statements. */ public class BaseTableDdl implements TableDdl { protected final DbConstraintNaming naming; protected final NamingConvention namingConvention; protected final PlatformDdl platformDdl; protected final String historyTableSuffix; /** * Used to check that indexes on foreign keys should be skipped as a unique index on the columns * already exists. */ protected IndexSet indexSet = new IndexSet(); /** * Used when unique constraints specifically for OneToOne can't be created normally (MsSqlServer). */ protected List<Column> externalUnique = new ArrayList<>(); // counters used when constraint names are truncated due to maximum length // and these counters are used to keep the constraint name unique protected int countCheck; protected int countUnique; protected int countForeignKey; protected int countIndex; /** * Base tables that have associated history tables that need their triggers/functions regenerated as * columns have been added, removed, included or excluded. */ protected Map<String, HistoryTableUpdate> regenerateHistoryTriggers = new LinkedHashMap<>(); /** * Construct with a naming convention and platform specific DDL. */ public BaseTableDdl(ServerConfig serverConfig, PlatformDdl platformDdl) { this.namingConvention = serverConfig.getNamingConvention(); this.naming = serverConfig.getConstraintNaming(); this.historyTableSuffix = serverConfig.getHistoryTableSuffix(); this.platformDdl = platformDdl; this.platformDdl.configure(serverConfig); } /** * Reset counters and index set for each table processed. */ protected void reset() { indexSet.clear(); externalUnique.clear(); countCheck = 0; countUnique = 0; countForeignKey = 0; countIndex = 0; } /** * Generate the appropriate 'create table' and matching 'drop table' statements * and add them to the appropriate 'apply' and 'rollback' buffers. */ @Override public void generate(DdlWrite writer, CreateTable createTable) throws IOException { reset(); String tableName = lowerTableName(createTable.getName()); List<Column> columns = createTable.getColumn(); List<Column> pk = determinePrimaryKeyColumns(columns); boolean singleColumnPrimaryKey = (pk.size() == 1); boolean useIdentity = false; boolean useSequence = false; if (singleColumnPrimaryKey) { IdType useDbIdentityType = platformDdl.useIdentityType(createTable.getIdentityType()); useIdentity = (IdType.IDENTITY == useDbIdentityType); useSequence = (IdType.SEQUENCE == useDbIdentityType); } DdlBuffer apply = writer.apply(); apply.append("create table ").append(tableName).append(" ("); writeTableColumns(apply, columns, useIdentity); writeCheckConstraints(apply, createTable); writeUniqueConstraints(apply, createTable); writeCompoundUniqueConstraints(apply, createTable); if (!pk.isEmpty()) { // defined on the columns writePrimaryKeyConstraint(apply, createTable.getPkName(), toColumnNames(pk)); } if (platformDdl.isInlineForeignKeys()) { writeInlineForeignKeys(writer, createTable); } apply.newLine().append(")"); addTableCommentInline(apply, createTable); apply.endOfStatement(); addComments(apply, createTable); writeUniqueOneToOneConstraints(writer, createTable); if (isTrue(createTable.isWithHistory())) { // create history with rollback before the // associated drop table is written to rollback createWithHistory(writer, createTable.getName()); } // add drop table to the rollback buffer - do this before // we drop the related sequence (if sequences are used) dropTable(writer.dropAll(), tableName); if (useSequence) { String pkCol = pk.get(0).getName(); writeSequence(writer, createTable, pkCol); } // add blank line for a bit of whitespace between tables apply.end(); writer.dropAll().end(); if (!platformDdl.isInlineForeignKeys()) { writeAddForeignKeys(writer, createTable); } } /** * Add table and column comments (separate from the create table statement). */ private void addComments(DdlBuffer apply, CreateTable createTable) throws IOException { if (!platformDdl.isInlineComments()) { String tableComment = createTable.getComment(); if (!StringHelper.isNull(tableComment)) { platformDdl.addTableComment(apply, createTable.getName(), tableComment); } List<Column> columns = createTable.getColumn(); for (Column column : columns) { if (!StringHelper.isNull(column.getComment())) { platformDdl.addColumnComment(apply, createTable.getName(), column.getName(), column.getComment()); } } } } /** * Add the table comment inline with the create table statement. */ private void addTableCommentInline(DdlBuffer apply, CreateTable createTable) throws IOException { if (platformDdl.isInlineComments()) { String tableComment = createTable.getComment(); if (!StringHelper.isNull(tableComment)) { platformDdl.inlineTableComment(apply, tableComment); } } } private void writeTableColumns(DdlBuffer apply, List<Column> columns, boolean useIdentity) throws IOException { platformDdl.writeTableColumns(apply, columns, useIdentity); } /** * Specific handling of OneToOne unique constraints for MsSqlServer. * For all other DB platforms these unique constraints are done inline as per normal. */ protected void writeUniqueOneToOneConstraints(DdlWrite write, CreateTable createTable) throws IOException { String tableName = createTable.getName(); for (Column col : externalUnique) { String uqName = col.getUniqueOneToOne(); String[] columnNames = {col.getName()}; write.apply() .append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames)) .endOfStatement(); write.dropAllForeignKeys() .append(platformDdl.dropIndex(uqName, tableName)) .endOfStatement(); } } protected void writeSequence(DdlWrite writer, CreateTable createTable, String pk) throws IOException { // explicit sequence use or platform decides String explicitSequenceName = createTable.getSequenceName(); int initial = toInt(createTable.getSequenceInitial()); int allocate = toInt(createTable.getSequenceAllocate()); String seqName = explicitSequenceName; if (seqName == null) { seqName = namingConvention.getSequenceName(createTable.getName(), pk); } String createSeq = platformDdl.createSequence(seqName, initial, allocate); if (createSeq != null) { writer.apply().append(createSeq).newLine(); writer.dropAll().append(platformDdl.dropSequence(seqName)).endOfStatement(); } } protected void createWithHistory(DdlWrite writer, String name) throws IOException { MTable table = writer.getTable(name); platformDdl.createWithHistory(writer, table); } protected void writeInlineForeignKeys(DdlWrite write, CreateTable createTable) throws IOException { for (Column column : createTable.getColumn()) { String references = column.getReferences(); if (hasValue(references)) { writeInlineForeignKey(write, column); } } writeInlineCompoundForeignKeys(write, createTable); } protected void writeInlineForeignKey(DdlWrite write, Column column) throws IOException { String references = column.getReferences(); int pos = references.lastIndexOf('.'); if (pos == -1) { throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]"); } String refTableName = references.substring(0, pos); String refColumnName = references.substring(pos + 1); String fkConstraint = platformDdl.tableInlineForeignKey(new String[]{column.getName()}, refTableName, new String[]{refColumnName}); write.apply().append(",").newLine().append(" ").append(fkConstraint); } protected void writeInlineCompoundForeignKeys(DdlWrite write, CreateTable createTable) throws IOException { List<ForeignKey> foreignKey = createTable.getForeignKey(); for (ForeignKey key : foreignKey) { String refTableName = key.getRefTableName(); String[] cols = toColumnNamesSplit(key.getColumnNames()); String[] refColumns = toColumnNamesSplit(key.getRefColumnNames()); String fkConstraint = platformDdl.tableInlineForeignKey(cols, refTableName, refColumns); write.apply().append(",").newLine().append(" ").append(fkConstraint); } } protected void writeAddForeignKeys(DdlWrite write, CreateTable createTable) throws IOException { String tableName = createTable.getName(); List<Column> columns = createTable.getColumn(); for (Column column : columns) { String references = column.getReferences(); if (hasValue(references)) { writeForeignKey(write, tableName, column); } } writeAddCompoundForeignKeys(write, createTable); } protected void writeAddCompoundForeignKeys(DdlWrite write, CreateTable createTable) throws IOException { String tableName = createTable.getName(); List<ForeignKey> foreignKey = createTable.getForeignKey(); for (ForeignKey key : foreignKey) { String refTableName = key.getRefTableName(); String fkName = key.getName(); String[] cols = toColumnNamesSplit(key.getColumnNames()); String[] refColumns = toColumnNamesSplit(key.getRefColumnNames()); writeForeignKey(write, fkName, tableName, cols, refTableName, refColumns, key.getIndexName()); } } protected void writeForeignKey(DdlWrite write, String tableName, Column column) throws IOException { String fkName = column.getForeignKeyName(); String references = column.getReferences(); int pos = references.lastIndexOf('.'); if (pos == -1) { throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]"); } String refTableName = references.substring(0, pos); String refColumnName = references.substring(pos + 1); String[] cols = {column.getName()}; String[] refCols = {refColumnName}; writeForeignKey(write, fkName, tableName, cols, refTableName, refCols, column.getForeignKeyIndex()); } protected void writeForeignKey(DdlWrite write, String fkName, String tableName, String[] columns, String refTable, String[] refColumns, String indexName) throws IOException { tableName = lowerTableName(tableName); DdlBuffer fkeyBuffer = write.applyForeignKeys(); alterTableAddForeignKey(fkeyBuffer, fkName, tableName, columns, refTable, refColumns); if (indexName != null) { // no matching unique constraint so add the index fkeyBuffer.append(platformDdl.createIndex(indexName, tableName, columns)).endOfStatement(); } fkeyBuffer.end(); write.dropAllForeignKeys() .append(platformDdl.alterTableDropForeignKey(tableName, fkName)).endOfStatement(); if (indexName != null) { write.dropAllForeignKeys() .append(platformDdl.dropIndex(indexName, tableName)).endOfStatement(); } write.dropAllForeignKeys().end(); } protected void alterTableAddForeignKey(DdlBuffer buffer, String fkName, String tableName, String[] columns, String refTable, String[] refColumns) throws IOException { String fkConstraint = platformDdl.alterTableAddForeignKey(tableName, fkName, columns, refTable, refColumns); if (fkConstraint != null && !fkConstraint.isEmpty()) { buffer.append(fkConstraint).endOfStatement(); } } protected void appendColumns(String[] columns, DdlBuffer buffer) throws IOException { buffer.append(" ("); for (int i = 0; i < columns.length; i++) { if (i > 0) { buffer.append(","); } buffer.append(lowerColumnName(columns[i].trim())); } buffer.append(")"); } /** * Add 'drop table' statement to the buffer. */ protected void dropTable(DdlBuffer buffer, String tableName) throws IOException { buffer.append(platformDdl.dropTable(tableName)).endOfStatement(); } /** * Write all the check constraints. */ protected void writeCheckConstraints(DdlBuffer apply, CreateTable createTable) throws IOException { List<Column> columns = createTable.getColumn(); for (Column column : columns) { String checkConstraint = column.getCheckConstraint(); if (hasValue(checkConstraint)) { writeCheckConstraint(apply, column, checkConstraint); } } } /** * Write a check constraint. */ protected void writeCheckConstraint(DdlBuffer buffer, Column column, String checkConstraint) throws IOException { String ckName = column.getCheckConstraintName(); buffer.append(",").newLine(); buffer.append(" constraint ").append(ckName); buffer.append(" ").append(checkConstraint); } protected void writeCompoundUniqueConstraints(DdlBuffer apply, CreateTable createTable) throws IOException { List<UniqueConstraint> uniqueConstraints = createTable.getUniqueConstraint(); for (UniqueConstraint uniqueConstraint : uniqueConstraints) { String uqName = uniqueConstraint.getName(); String[] columns = toColumnNamesSplit(uniqueConstraint.getColumnNames()); apply.append(",").newLine(); apply.append(" constraint ").append(uqName).append(" unique"); appendColumns(columns, apply); } } /** * Write the unique constraints inline with the create table statement. */ protected void writeUniqueConstraints(DdlBuffer apply, CreateTable createTable) throws IOException { boolean inlineUniqueOneToOne = platformDdl.isInlineUniqueOneToOne(); List<Column> columns = createTable.getColumn(); for (Column column : columns) { if (hasValue(column.getUnique()) || (inlineUniqueOneToOne && hasValue(column.getUniqueOneToOne()))) { // normal mechanism for adding unique constraint inlineUniqueConstraintSingle(apply, column); } else if (!inlineUniqueOneToOne && hasValue(column.getUniqueOneToOne())) { // MsSqlServer specific mechanism for adding unique constraints (that allow nulls) externalUnique.add(column); } } } /** * Write the unique constraint inline with the create table statement. */ protected void inlineUniqueConstraintSingle(DdlBuffer buffer, Column column) throws IOException { String uqName = column.getUnique(); if (uqName == null) { uqName = column.getUniqueOneToOne(); } buffer.append(",").newLine(); buffer.append(" constraint ").append(uqName).append(" unique "); buffer.append("("); buffer.append(lowerColumnName(column.getName())); buffer.append(")"); } /** * Write the primary key constraint inline with the create table statement. */ protected void writePrimaryKeyConstraint(DdlBuffer buffer, String pkName, String[] pkColumns) throws IOException { buffer.append(",").newLine(); buffer.append(" constraint ").append(pkName).append(" primary key"); appendColumns(pkColumns, buffer); } /** * Return as an array of string column names. */ protected String[] toColumnNames(List<Column> columns) { String[] cols = new String[columns.size()]; for (int i = 0; i < cols.length; i++) { cols[i] = columns.get(i).getName(); } return cols; } /** * Return as an array of string column names. */ protected String[] toColumnNamesSplit(String columns) { return columns.split(","); } /** * Convert the table lower case. */ protected String lowerTableName(String name) { return naming.lowerTableName(name); } /** * Convert the column name to lower case. */ protected String lowerColumnName(String name) { return naming.lowerColumnName(name); } /** * Return the list of columns that make the primary key. */ protected List<Column> determinePrimaryKeyColumns(List<Column> columns) { List<Column> pk = new ArrayList<>(3); for (Column column : columns) { if (isTrue(column.isPrimaryKey())) { pk.add(column); } } return pk; } @Override public void generate(DdlWrite writer, CreateIndex createIndex) throws IOException { String[] cols = toColumnNamesSplit(createIndex.getColumns()); writer.apply() .append(platformDdl.createIndex(createIndex.getIndexName(), createIndex.getTableName(), cols)) .endOfStatement(); writer.dropAll() .append(platformDdl.dropIndex(createIndex.getIndexName(), createIndex.getTableName())) .endOfStatement(); } @Override public void generate(DdlWrite writer, DropIndex dropIndex) throws IOException { writer.apply() .append(platformDdl.dropIndex(dropIndex.getIndexName(), dropIndex.getTableName())) .endOfStatement(); } /** * Add add history table DDL. */ @Override public void generate(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException { platformDdl.addHistoryTable(writer, addHistoryTable); } /** * Add drop history table DDL. */ @Override public void generate(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException { platformDdl.dropHistoryTable(writer, dropHistoryTable); } /** * Called at the end to generate additional ddl such as regenerate history triggers. */ @Override public void generateExtra(DdlWrite write) throws IOException { for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) { platformDdl.regenerateHistoryTriggers(write, update); } } @Override public void generate(DdlWrite writer, AddTableComment addTableComment) throws IOException { if (hasValue(addTableComment.getComment())) { platformDdl.addTableComment(writer.apply(), addTableComment.getName(), addTableComment.getComment()); } } /** * Add add column DDL. */ @Override public void generate(DdlWrite writer, AddColumn addColumn) throws IOException { String tableName = addColumn.getTableName(); List<Column> columns = addColumn.getColumn(); for (Column column : columns) { alterTableAddColumn(writer.apply(), tableName, column, false); } if (isTrue(addColumn.isWithHistory())) { // make same changes to the history table String historyTable = historyTable(tableName); for (Column column : columns) { regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.ADD, column.getName()); alterTableAddColumn(writer.apply(), historyTable, column, true); } } for (Column column : columns) { if (hasValue(column.getReferences())) { writeForeignKey(writer, tableName, column); } } // add a bit of whitespace writer.apply().end(); } /** * Add drop table DDL. */ @Override public void generate(DdlWrite writer, DropTable dropTable) throws IOException { dropTable(writer.apply(), dropTable.getName()); } /** * Add drop column DDL. */ @Override public void generate(DdlWrite writer, DropColumn dropColumn) throws IOException { String tableName = dropColumn.getTableName(); alterTableDropColumn(writer.apply(), tableName, dropColumn.getColumnName()); if (isTrue(dropColumn.isWithHistory())) { // also drop from the history table regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.DROP, dropColumn.getColumnName()); alterTableDropColumn(writer.apply(), historyTable(tableName), dropColumn.getColumnName()); } writer.apply().end(); } /** * Add all the appropriate changes based on the column changes. */ @Override public void generate(DdlWrite writer, AlterColumn alterColumn) throws IOException { if (isTrue(alterColumn.isHistoryExclude())) { regenerateHistoryTriggers(alterColumn.getTableName(), HistoryTableUpdate.Change.EXCLUDE, alterColumn.getColumnName()); } else if (isFalse(alterColumn.isHistoryExclude())) { regenerateHistoryTriggers(alterColumn.getTableName(), HistoryTableUpdate.Change.INCLUDE, alterColumn.getColumnName()); } if (hasValue(alterColumn.getDropForeignKey())) { alterColumnDropForeignKey(writer, alterColumn); } if (hasValue(alterColumn.getReferences())) { alterColumnAddForeignKey(writer, alterColumn); } if (hasValue(alterColumn.getDropUnique())) { alterColumnDropUniqueConstraint(writer, alterColumn); } if (hasValue(alterColumn.getUnique())) { alterColumnAddUniqueConstraint(writer, alterColumn); } if (hasValue(alterColumn.getUniqueOneToOne())) { alterColumnAddUniqueOneToOneConstraint(writer, alterColumn); } if (hasValue(alterColumn.getComment())) { alterColumnComment(writer, alterColumn); } boolean alterCheckConstraint = hasValue(alterColumn.getCheckConstraint()); if (alterCheckConstraint) { // drop constraint before altering type etc dropCheckConstraint(writer, alterColumn); } boolean alterBaseAttributes = false; if (hasValue(alterColumn.getType())) { alterColumnType(writer, alterColumn); alterBaseAttributes = true; } if (hasValue(alterColumn.getDefaultValue())) { alterColumnDefaultValue(writer, alterColumn); alterBaseAttributes = true; } if (alterColumn.isNotnull() != null) { alterColumnNotnull(writer, alterColumn); alterBaseAttributes = true; } if (alterBaseAttributes) { alterColumnBaseAttributes(writer, alterColumn); } if (alterCheckConstraint) { // add constraint last (after potential type change) addCheckConstraint(writer, alterColumn); } } private void alterColumnComment(DdlWrite writer, AlterColumn alterColumn) throws IOException { platformDdl.addColumnComment(writer.apply(), alterColumn.getTableName(), alterColumn.getColumnName(), alterColumn.getComment()); } /** * Return the name of the history table given the base table name. */ protected String historyTable(String baseTable) { return baseTable + historyTableSuffix; } /** * Register the base table that we need to regenerate the history triggers on. */ protected void regenerateHistoryTriggers(String baseTableName, HistoryTableUpdate.Change change, String column) { HistoryTableUpdate update = regenerateHistoryTriggers.computeIfAbsent(baseTableName, HistoryTableUpdate::new); update.add(change, column); } /** * This is mysql specific - alter all the base attributes of the column together. */ protected void alterColumnBaseAttributes(DdlWrite writer, AlterColumn alter) throws IOException { String ddl = platformDdl.alterColumnBaseAttributes(alter); if (hasValue(ddl)) { writer.apply().append(ddl).endOfStatement(); if (isTrue(alter.isWithHistory()) && alter.getType() != null) { // mysql and sql server column type change allowing nulls in the history table column AlterColumn alterHistoryColumn = new AlterColumn(); alterHistoryColumn.setTableName(historyTable(alter.getTableName())); alterHistoryColumn.setColumnName(alter.getColumnName()); alterHistoryColumn.setType(alter.getType()); String histColumnDdl = platformDdl.alterColumnBaseAttributes(alterHistoryColumn); // write the apply to history table writer.apply().append(histColumnDdl).endOfStatement(); } } } protected void alterColumnDefaultValue(DdlWrite writer, AlterColumn alter) throws IOException { String ddl = platformDdl.alterColumnDefaultValue(alter.getTableName(), alter.getColumnName(), alter.getDefaultValue()); if (hasValue(ddl)) { writer.apply().append(ddl).endOfStatement(); } } protected void dropCheckConstraint(DdlWrite writer, AlterColumn alter) throws IOException { String ddl = platformDdl.alterTableDropConstraint(alter.getTableName(), alter.getCheckConstraintName()); if (hasValue(ddl)) { writer.apply().append(ddl).endOfStatement(); } } protected void addCheckConstraint(DdlWrite writer, AlterColumn alter) throws IOException { String ddl = platformDdl.alterTableAddCheckConstraint(alter.getTableName(), alter.getCheckConstraintName(), alter.getCheckConstraint()); if (hasValue(ddl)) { writer.apply().append(ddl).endOfStatement(); } } protected void alterColumnNotnull(DdlWrite writer, AlterColumn alter) throws IOException { String ddl = platformDdl.alterColumnNotnull(alter.getTableName(), alter.getColumnName(), alter.isNotnull()); if (hasValue(ddl)) { writer.apply().append(ddl).endOfStatement(); } } protected void alterColumnType(DdlWrite writer, AlterColumn alter) throws IOException { String ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getType()); if (hasValue(ddl)) { writer.apply().append(ddl).endOfStatement(); if (isTrue(alter.isWithHistory())) { // apply same type change to matching column in the history table ddl = platformDdl.alterColumnType(historyTable(alter.getTableName()), alter.getColumnName(), alter.getType()); writer.apply().append(ddl).endOfStatement(); } } } protected void alterColumnAddForeignKey(DdlWrite writer, AlterColumn alterColumn) throws IOException { String tableName = alterColumn.getTableName(); String fkName = alterColumn.getForeignKeyName(); String[] cols = {alterColumn.getColumnName()}; String references = alterColumn.getReferences(); int pos = references.lastIndexOf('.'); if (pos == -1) { throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]"); } String refTableName = references.substring(0, pos); String refColumnName = references.substring(pos + 1); String[] refCols = {refColumnName}; alterTableAddForeignKey(writer.apply(), fkName, tableName, cols, refTableName, refCols); } protected void alterColumnDropForeignKey(DdlWrite writer, AlterColumn alter) throws IOException { writer.apply() .append(platformDdl.alterTableDropForeignKey(alter.getTableName(), alter.getDropForeignKey())) .endOfStatement(); } protected void alterColumnDropUniqueConstraint(DdlWrite writer, AlterColumn alter) throws IOException { writer.apply() .append(platformDdl.alterTableDropUniqueConstraint(alter.getTableName(), alter.getDropUnique())) .endOfStatement(); } protected void alterColumnAddUniqueOneToOneConstraint(DdlWrite writer, AlterColumn alter) throws IOException { addUniqueConstraint(writer, alter, alter.getUniqueOneToOne()); } protected void alterColumnAddUniqueConstraint(DdlWrite writer, AlterColumn alter) throws IOException { addUniqueConstraint(writer, alter, alter.getUnique()); } protected void addUniqueConstraint(DdlWrite writer, AlterColumn alter, String uqName) throws IOException { String[] cols = {alter.getColumnName()}; writer.apply() .append(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols)) .endOfStatement(); writer.dropAllForeignKeys() .append(platformDdl.dropIndex(uqName, alter.getTableName())) .endOfStatement(); } protected void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException { buffer.append("alter table ").append(tableName).append(" drop column ").append(columnName) .endOfStatement(); } protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable) throws IOException { String convertedType = platformDdl.convert(column.getType(), false); buffer.append("alter table ").append(tableName) .append(" add column ").append(column.getName()) .append(" ").append(convertedType); if (!onHistoryTable) { if (isTrue(column.isNotnull())) { buffer.append(" not null"); } if (hasValue(column.getCheckConstraint())) { buffer.append(" constraint ").append(column.getCheckConstraintName()); buffer.append(" ").append(column.getCheckConstraint()); } } buffer.endOfStatement(); } protected boolean isFalse(Boolean value) { return value != null && !value; } /** * Return true if null or trimmed string is empty. */ protected boolean hasValue(String value) { return value != null && !value.trim().isEmpty(); } /** * Null safe Boolean true test. */ protected boolean isTrue(Boolean value) { return Boolean.TRUE.equals(value); } /** * Return as an int value with 0 when it is null. */ protected int toInt(BigInteger value) { return (value == null) ? 0 : value.intValue(); } }