001package io.ebeaninternal.dbmigration.ddlgeneration.platform;
002
003import io.ebean.annotation.Platform;
004import io.ebean.config.DatabaseConfig;
005import io.ebean.config.DbConstraintNaming;
006import io.ebean.config.NamingConvention;
007import io.ebean.config.dbplatform.IdType;
008import io.ebean.util.StringHelper;
009import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
010import io.ebeaninternal.dbmigration.ddlgeneration.DdlOptions;
011import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
012import io.ebeaninternal.dbmigration.ddlgeneration.TableDdl;
013import io.ebeaninternal.dbmigration.migration.AddColumn;
014import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
015import io.ebeaninternal.dbmigration.migration.AddTableComment;
016import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint;
017import io.ebeaninternal.dbmigration.migration.AlterColumn;
018import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
019import io.ebeaninternal.dbmigration.migration.AlterTable;
020import io.ebeaninternal.dbmigration.migration.Column;
021import io.ebeaninternal.dbmigration.migration.CreateIndex;
022import io.ebeaninternal.dbmigration.migration.CreateTable;
023import io.ebeaninternal.dbmigration.migration.DdlScript;
024import io.ebeaninternal.dbmigration.migration.DropColumn;
025import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
026import io.ebeaninternal.dbmigration.migration.DropIndex;
027import io.ebeaninternal.dbmigration.migration.DropTable;
028import io.ebeaninternal.dbmigration.migration.ForeignKey;
029import io.ebeaninternal.dbmigration.migration.UniqueConstraint;
030import io.ebeaninternal.dbmigration.model.MTable;
031import io.ebeaninternal.dbmigration.model.MTableIdentity;
032import io.ebeaninternal.server.deploy.IdentityMode;
033
034import java.util.ArrayList;
035import java.util.Collections;
036import java.util.List;
037
038import static io.ebean.util.StringHelper.replace;
039import static io.ebeaninternal.api.PlatformMatch.matchPlatform;
040import static io.ebeaninternal.dbmigration.ddlgeneration.platform.SplitColumns.split;
041
042/**
043 * Base implementation for 'create table' and 'alter table' statements.
044 */
045public class BaseTableDdl implements TableDdl {
046
047  protected final DbConstraintNaming naming;
048
049  protected final NamingConvention namingConvention;
050
051  protected final PlatformDdl platformDdl;
052
053  protected final String historyTableSuffix;
054
055  /**
056   * Used when unique constraints specifically for OneToOne can't be created normally (MsSqlServer).
057   */
058  protected final List<Column> externalUnique = new ArrayList<>();
059
060  protected final List<UniqueConstraint> externalCompoundUnique = new ArrayList<>();
061
062  private final boolean strictMode;
063
064  private final PlatformHistoryDdl.TableBased tableHistory;
065
066  /**
067   * Helper class that is used to execute the migration ddl before and after the migration action.
068   */
069  private class DdlMigrationHelp {
070    private final List<String> before;
071    private final List<String> after;
072    private final String tableName;
073    private final String columnName;
074    private final String defaultValue;
075    private final boolean withHistory;
076
077    /**
078     * Constructor for DdlMigrationHelp when adding a NEW column.
079     */
080    DdlMigrationHelp(String tableName, Column column, boolean withHistory) {
081      this.tableName = tableName;
082      this.columnName = column.getName();
083      this.defaultValue = platformDdl.convertDefaultValue(column.getDefaultValue());
084      boolean alterNotNull = Boolean.TRUE.equals(column.isNotnull());
085      if (column.getBefore().isEmpty() && alterNotNull && defaultValue == null) {
086        handleStrictError(tableName, columnName);
087      }
088      before = getScriptsForPlatform(column.getBefore());
089      after = getScriptsForPlatform(column.getAfter());
090      this.withHistory = withHistory;
091    }
092
093    /**
094     * Constructor for DdlMigrationHelp when altering a column.
095     */
096    DdlMigrationHelp(AlterColumn alter) {
097      this.tableName = alter.getTableName();
098      this.columnName = alter.getColumnName();
099      String tmp = alter.getDefaultValue() != null ? alter.getDefaultValue() : alter.getCurrentDefaultValue();
100      this.defaultValue = platformDdl.convertDefaultValue(tmp);
101      boolean alterNotNull = Boolean.TRUE.equals(alter.isNotnull());
102      // here we add the platform's default update script
103      withHistory = isTrue(alter.isWithHistory());
104      if (alter.getBefore().isEmpty() && alterNotNull) {
105        if (defaultValue == null) {
106          handleStrictError(tableName, columnName);
107        }
108        before = Collections.singletonList(platformDdl.getUpdateNullWithDefault());
109      } else {
110        before = getScriptsForPlatform(alter.getBefore());
111      }
112      after = getScriptsForPlatform(alter.getAfter());
113    }
114
115    void write(DdlWrite writer) {
116      if (!before.isEmpty()) {
117        writer.apply().end();
118        if (withHistory) {
119          writer.apply().append("-- NOTE: table has @History - special migration may be necessary").newLine();
120        }
121        for (String ddlScript : before) {
122          writer.apply().appendStatement(translate(ddlScript, tableName, columnName, defaultValue));
123        }
124      }
125      // effective order in DDL.sql:
126      // 'before' goes to apply-buffer
127      // altered columns will be in the alterTable buffers.
128      // 'after' goes to the post-alter-buffer
129      if (!after.isEmpty()) {
130        writer.applyPostAlter().append("-- NOTE: table has @History - special migration may be necessary").newLine();
131
132        // here we run post migration scripts
133        for (String ddlScript : after) {
134          writer.applyPostAlter().appendStatement(translate(ddlScript, tableName, columnName, defaultValue));
135        }
136
137        writer.applyPostAlter().end();
138      }
139    }
140
141    private List<String> getScriptsForPlatform(List<DdlScript> scripts) {
142      Platform searchPlatform = platformDdl.getPlatform().getPlatform();
143      for (DdlScript script : scripts) {
144        if (matchPlatform(searchPlatform, script.getPlatforms())) {
145          // just returns the first match (rather than appends them)
146          return script.getDdl();
147        }
148      }
149      return Collections.emptyList();
150    }
151
152    /**
153     * Replaces Table name (${table}), Column name (${column}) and default value (${default}) in DDL.
154     */
155    private String translate(String ddl, String tableName, String columnName, String defaultValue) {
156      String ret = replace(ddl, "${table}", tableName);
157      ret = replace(ret, "${column}", columnName);
158      return replace(ret, "${default}", defaultValue);
159    }
160
161    private void handleStrictError(String tableName, String columnName) {
162      if (strictMode) {
163        String message = "DB Migration of non-null column with no default value specified for: " + tableName + "." + columnName+" Use @DbDefault to specify a default value or specify dbMigration.setStrictMode(false)";
164        throw new IllegalArgumentException(message);
165      }
166    }
167
168    public String getDefaultValue() {
169      return defaultValue;
170    }
171  }
172
173  /**
174   * Construct with a naming convention and platform specific DDL.
175   */
176  public BaseTableDdl(DatabaseConfig config, PlatformDdl platformDdl) {
177    this.namingConvention = config.getNamingConvention();
178    this.naming = config.getConstraintNaming();
179    this.historyTableSuffix = config.getHistoryTableSuffix();
180    this.platformDdl = platformDdl;
181    this.platformDdl.configure(config);
182    this.strictMode = config.isDdlStrictMode();
183    if (platformDdl.historyDdl instanceof PlatformHistoryDdl.TableBased) {
184      this.tableHistory = (PlatformHistoryDdl.TableBased) platformDdl.historyDdl;
185    } else {
186      this.tableHistory = null;
187    }
188  }
189
190  /**
191   * Reset counters and index set for each table processed.
192   */
193  protected void reset() {
194    externalUnique.clear();
195    externalCompoundUnique.clear();
196  }
197
198  /**
199   * Generate the appropriate 'create table' and matching 'drop table' statements
200   * and add them to the appropriate 'apply' and 'rollback' buffers.
201   */
202  @Override
203  public void generate(DdlWrite writer, CreateTable createTable) {
204    reset();
205
206    String tableName = createTable.getName();
207    List<Column> columns = createTable.getColumn();
208    List<Column> pk = determinePrimaryKeyColumns(columns);
209
210    DdlIdentity identity = DdlIdentity.NONE;
211    if ((pk.size() == 1)) {
212      final IdentityMode identityMode = MTableIdentity.fromCreateTable(createTable);
213      IdType idType = platformDdl.useIdentityType(identityMode.getIdType());
214      String sequenceName = identityMode.getSequenceName();
215      if (IdType.SEQUENCE == idType && (sequenceName == null || sequenceName.isEmpty())) {
216        sequenceName = sequenceName(createTable, pk);
217      }
218      identity = new DdlIdentity(idType, identityMode, sequenceName);
219    }
220
221    String partitionMode = createTable.getPartitionMode();
222
223    DdlBuffer apply = writer.apply();
224    apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(platformDdl.quote(tableName)).append(" (");
225    writeTableColumns(apply, columns, identity);
226    writeUniqueConstraints(apply, createTable);
227    writeCompoundUniqueConstraints(apply, createTable);
228    if (!pk.isEmpty()) {
229      // defined on the columns
230      if (partitionMode == null || !platformDdl.suppressPrimaryKeyOnPartition()) {
231        writePrimaryKeyConstraint(apply, createTable.getPkName(), toColumnNames(pk));
232      }
233    }
234    if (platformDdl.isInlineForeignKeys()) {
235      writeInlineForeignKeys(apply, createTable);
236    }
237    apply.newLine().append(")");
238    addTableTableSpaces(apply, createTable);
239    addTableStorageEngine(apply, createTable);
240    addTableCommentInline(apply, createTable);
241    if (partitionMode != null) {
242      platformDdl.addTablePartition(apply, partitionMode, createTable.getPartitionColumn());
243    }
244    apply.endOfStatement();
245
246    addComments(apply, createTable);
247    writeUniqueOneToOneConstraints(writer, createTable);
248    if (isTrue(createTable.isWithHistory())) {
249      // create history with rollback before the
250      // associated drop table is written to rollback
251      createWithHistory(writer, createTable.getName());
252    }
253
254    // add drop table to the rollback buffer - do this before
255    // we drop the related sequence (if sequences are used)
256    dropTable(writer.dropAll(), tableName);
257
258    if (identity.useSequence()) {
259      writeSequence(writer, identity);
260    }
261
262    // add blank line for a bit of whitespace between tables
263    apply.end();
264    writer.dropAll().end();
265    if (!platformDdl.isInlineForeignKeys()) {
266      writeAddForeignKeys(writer, createTable);
267    }
268  }
269
270
271
272  private String sequenceName(CreateTable createTable, List<Column> pk) {
273    return namingConvention.getSequenceName(createTable.getName(), pk.get(0).getName());
274  }
275
276  /**
277   * Add table and column comments (separate from the create table statement).
278   */
279  private void addComments(DdlBuffer apply, CreateTable createTable) {
280    if (!platformDdl.isInlineComments()) {
281      String tableComment = createTable.getComment();
282      if (hasValue(tableComment)) {
283        platformDdl.addTableComment(apply, createTable.getName(), tableComment);
284      }
285      for (Column column : createTable.getColumn()) {
286        if (!StringHelper.isNull(column.getComment())) {
287          platformDdl.addColumnComment(apply, createTable.getName(), column.getName(), column.getComment());
288        }
289      }
290    }
291  }
292
293  /**
294   * Add tablespace declaration.
295   */
296  protected void addTableTableSpaces(DdlBuffer apply, CreateTable createTable) {
297    String tableSpace = platformDdl.extract(createTable.getTablespace());
298    if (hasValue(tableSpace)) {
299      platformDdl.addTablespace(apply,
300        tableSpace,
301        platformDdl.extract(createTable.getIndexTablespace()),
302        platformDdl.extract(createTable.getLobTablespace()));
303    }
304  }
305
306  /**
307   * Add the table storage engine clause.
308   */
309  private void addTableStorageEngine(DdlBuffer apply, CreateTable createTable) {
310    if (platformDdl.isIncludeStorageEngine()) {
311      platformDdl.tableStorageEngine(apply, createTable.getStorageEngine());
312    }
313  }
314
315  /**
316   * Add the table comment inline with the create table statement.
317   */
318  private void addTableCommentInline(DdlBuffer apply, CreateTable createTable) {
319    if (platformDdl.isInlineComments()) {
320      String tableComment = createTable.getComment();
321      if (!StringHelper.isNull(tableComment)) {
322        platformDdl.inlineTableComment(apply, tableComment);
323      }
324    }
325  }
326
327  private void writeTableColumns(DdlBuffer apply, List<Column> columns, DdlIdentity identity) {
328    platformDdl.writeTableColumns(apply, columns, identity);
329  }
330
331  /**
332   * Specific handling of OneToOne unique constraints for MsSqlServer.
333   * For all other DB platforms these unique constraints are done inline as per normal.
334   */
335  protected void writeUniqueOneToOneConstraints(DdlWrite writer, CreateTable createTable) {
336    String tableName = createTable.getName();
337    for (Column col : externalUnique) {
338      String uqName = col.getUniqueOneToOne();
339      if (uqName == null) {
340        uqName = col.getUnique();
341      }
342      String[] columnNames = {col.getName()};
343      String[] nullableColumns = Boolean.TRUE.equals(col.isNotnull()) ? null : columnNames;
344      writer.applyPostAlter().appendStatement(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, nullableColumns));
345      writer.dropAllForeignKeys().appendStatement(platformDdl.dropIndex(uqName, tableName));
346    }
347
348    for (UniqueConstraint constraint : externalCompoundUnique) {
349      String uqName = constraint.getName();
350      String[] columnNames = split(constraint.getColumnNames());
351      String[] nullableColumns = split(constraint.getNullableColumns());
352
353      writer.applyPostAlter().appendStatement(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, nullableColumns));
354      writer.dropAllForeignKeys().appendStatement(platformDdl.dropIndex(uqName, tableName));
355    }
356  }
357
358  protected void writeSequence(DdlWrite writer, DdlIdentity identity) {
359    String seqName = identity.getSequenceName();
360    String createSeq = platformDdl.createSequence(seqName, identity);
361    if (hasValue(createSeq)) {
362      writer.apply().append(createSeq).newLine();
363      writer.dropAll().appendStatement(platformDdl.dropSequence(seqName));
364    }
365  }
366
367  protected void createWithHistory(DdlWrite writer, String name) {
368    MTable table = writer.getTable(name);
369    platformDdl.createWithHistory(writer, table);
370  }
371
372  protected void writeInlineForeignKeys(DdlBuffer buffer, CreateTable createTable) {
373    for (Column column : createTable.getColumn()) {
374      String references = column.getReferences();
375      if (hasValue(references)) {
376        writeInlineForeignKey(buffer, column);
377      }
378    }
379    writeInlineCompoundForeignKeys(buffer, createTable);
380  }
381
382  protected void writeInlineForeignKey(DdlBuffer buffer, Column column) {
383    String fkConstraint = platformDdl.tableInlineForeignKey(new WriteForeignKey(null, column));
384    buffer.append(",").newLine().append("  ").append(fkConstraint);
385  }
386
387  protected void writeInlineCompoundForeignKeys(DdlBuffer buffer, CreateTable createTable) {
388    for (ForeignKey key : createTable.getForeignKey()) {
389      String fkConstraint = platformDdl.tableInlineForeignKey(new WriteForeignKey(null, key));
390      buffer.append(",").newLine().append("  ").append(fkConstraint);
391    }
392  }
393
394  protected void writeAddForeignKeys(DdlWrite writer, CreateTable createTable) {
395    for (Column column : createTable.getColumn()) {
396      String references = column.getReferences();
397      if (hasValue(references)) {
398        writeForeignKey(writer, createTable.getName(), column);
399      }
400    }
401    writeAddCompoundForeignKeys(writer, createTable);
402  }
403
404  protected void writeAddCompoundForeignKeys(DdlWrite writer, CreateTable createTable) {
405    for (ForeignKey key : createTable.getForeignKey()) {
406      writeForeignKey(writer, new WriteForeignKey(createTable.getName(), key));
407    }
408  }
409
410  protected void writeForeignKey(DdlWrite writer, String tableName, Column column) {
411    writeForeignKey(writer, new WriteForeignKey(tableName, column));
412  }
413
414  protected void writeForeignKey(DdlWrite writer, WriteForeignKey request) {
415    DdlBuffer fkeyBuffer = writer.applyForeignKeys();
416    String tableName = request.table();
417    if (request.indexName() != null) {
418      // no matching unique constraint so add the index
419      fkeyBuffer.appendStatement(platformDdl.createIndex(new WriteCreateIndex(request.indexName(), tableName, request.cols(), false)));
420    }
421    alterTableAddForeignKey(writer.getOptions(), fkeyBuffer, request);
422    fkeyBuffer.end();
423
424    writer.dropAllForeignKeys().appendStatement(platformDdl.alterTableDropForeignKey(tableName, request.fkName()));
425    if (hasValue(request.indexName())) {
426      writer.dropAllForeignKeys().appendStatement(platformDdl.dropIndex(request.indexName(), tableName));
427    }
428    writer.dropAllForeignKeys().end();
429  }
430
431  protected void alterTableAddForeignKey(DdlOptions options, DdlBuffer buffer, WriteForeignKey request) {
432    buffer.appendStatement(platformDdl.alterTableAddForeignKey(options, request));
433  }
434
435  protected void appendColumns(String[] columns, DdlBuffer buffer) {
436    buffer.append(" (");
437    for (int i = 0; i < columns.length; i++) {
438      if (i > 0) {
439        buffer.append(",");
440      }
441      buffer.append(platformDdl.quote(columns[i].trim()));
442    }
443    buffer.append(")");
444  }
445
446  /**
447   * Add 'drop table' statement to the buffer.
448   */
449  protected void dropTable(DdlBuffer buffer, String tableName) {
450    buffer.appendStatement(platformDdl.dropTable(tableName));
451  }
452
453  /**
454   * Add 'drop sequence' statement to the buffer.
455   */
456  protected void dropSequence(DdlBuffer buffer, String sequenceName) {
457    buffer.appendStatement(platformDdl.dropSequence(sequenceName));
458  }
459
460  protected void writeCompoundUniqueConstraints(DdlBuffer apply, CreateTable createTable) {
461    boolean inlineUniqueWhenNull = platformDdl.isInlineUniqueWhenNullable();
462    for (UniqueConstraint uniqueConstraint : createTable.getUniqueConstraint()) {
463      if (platformInclude(uniqueConstraint.getPlatforms())) {
464        if (inlineUniqueWhenNull) {
465          String uqName = uniqueConstraint.getName();
466          apply.append(",").newLine();
467          apply.append("  constraint ").append(uqName).append(" unique");
468          appendColumns(split(uniqueConstraint.getColumnNames()), apply);
469        } else {
470          externalCompoundUnique.add(uniqueConstraint);
471        }
472      }
473    }
474  }
475
476  private boolean platformInclude(String platforms) {
477    return matchPlatform(platformDdl.getPlatform().getPlatform(), platforms);
478  }
479
480  /**
481   * Write the unique constraints inline with the create table statement.
482   */
483  protected void writeUniqueConstraints(DdlBuffer apply, CreateTable createTable) {
484    boolean inlineUniqueWhenNullable = platformDdl.isInlineUniqueWhenNullable();
485    List<Column> columns = new WriteUniqueConstraint(createTable.getColumn()).uniqueKeys();
486    for (Column column : columns) {
487      if (Boolean.TRUE.equals(column.isNotnull()) || inlineUniqueWhenNullable) {
488        // normal mechanism for adding unique constraint
489        inlineUniqueConstraintSingle(apply, column);
490      } else {
491        // SqlServer & DB2 specific mechanism for adding unique constraints (that allow nulls)
492        externalUnique.add(column);
493      }
494    }
495  }
496
497  /**
498   * Write the unique constraint inline with the create table statement.
499   */
500  protected void inlineUniqueConstraintSingle(DdlBuffer buffer, Column column) {
501    String uqName = column.getUnique();
502    if (uqName == null) {
503      uqName = column.getUniqueOneToOne();
504    }
505    buffer.append(",").newLine();
506    buffer.append("  constraint ").append(uqName).append(" unique ");
507    buffer.append("(");
508    buffer.append(platformDdl.quote(column.getName()));
509    buffer.append(")");
510  }
511
512  /**
513   * Write the primary key constraint inline with the create table statement.
514   */
515  protected void writePrimaryKeyConstraint(DdlBuffer buffer, String pkName, String[] pkColumns) {
516    buffer.append(",").newLine();
517    buffer.append("  constraint ").append(pkName).append(" primary key");
518    appendColumns(pkColumns, buffer);
519  }
520
521  /**
522   * Return as an array of string column names.
523   */
524  protected String[] toColumnNames(List<Column> columns) {
525    String[] cols = new String[columns.size()];
526    for (int i = 0; i < cols.length; i++) {
527      cols[i] = columns.get(i).getName();
528    }
529    return cols;
530  }
531
532  /**
533   * Return the list of columns that make the primary key.
534   */
535  protected List<Column> determinePrimaryKeyColumns(List<Column> columns) {
536    List<Column> pk = new ArrayList<>(3);
537    for (Column column : columns) {
538      if (isTrue(column.isPrimaryKey())) {
539        pk.add(column);
540      }
541    }
542    return pk;
543  }
544
545  @Override
546  public void generate(DdlWrite writer, CreateIndex index) {
547    if (platformInclude(index.getPlatforms())) {
548      writer.applyForeignKeys().appendStatement(platformDdl.createIndex(new WriteCreateIndex(index)));
549      writer.dropAll().appendStatement(platformDdl.dropIndex(index.getIndexName(), index.getTableName(), Boolean.TRUE.equals(index.isConcurrent())));
550    }
551  }
552
553  @Override
554  public void generate(DdlWrite writer, DropIndex dropIndex) {
555    if (platformInclude(dropIndex.getPlatforms())) {
556      writer.applyDropDependencies()
557        .appendStatement(platformDdl.dropIndex(dropIndex.getIndexName(), dropIndex.getTableName(), Boolean.TRUE.equals(dropIndex.isConcurrent())));
558    }
559  }
560
561  @Override
562  public void generate(DdlWrite writer, AddUniqueConstraint constraint) {
563    if (platformInclude(constraint.getPlatforms())) {
564      if (DdlHelp.isDropConstraint(constraint.getColumnNames())) {
565        writer.applyDropDependencies()
566          .appendStatement(platformDdl.alterTableDropUniqueConstraint(constraint.getTableName(), constraint.getConstraintName()));
567
568      } else {
569        String[] cols = split(constraint.getColumnNames());
570        String[] nullableColumns = split(constraint.getNullableColumns());
571        writer.applyPostAlter()
572          .appendStatement(platformDdl.alterTableAddUniqueConstraint(constraint.getTableName(), constraint.getConstraintName(), cols, nullableColumns));
573      }
574    }
575  }
576
577  @Override
578  public void generate(DdlWrite writer, AlterForeignKey alterForeignKey) {
579    if (DdlHelp.isDropForeignKey(alterForeignKey.getColumnNames())) {
580      writer.applyDropDependencies().appendStatement(platformDdl.alterTableDropForeignKey(alterForeignKey.getTableName(), alterForeignKey.getName()));
581    } else {
582      writer.applyForeignKeys().appendStatement(platformDdl.alterTableAddForeignKey(writer.getOptions(), new WriteForeignKey(alterForeignKey)));
583    }
584  }
585
586  /**
587   * Add add history table DDL.
588   */
589  @Override
590  public void generate(DdlWrite writer, AddHistoryTable addHistoryTable) {
591    platformDdl.addHistoryTable(writer, addHistoryTable);
592  }
593
594  /**
595   * Add drop history table DDL.
596   */
597  @Override
598  public void generate(DdlWrite writer, DropHistoryTable dropHistoryTable) {
599    platformDdl.dropHistoryTable(writer, dropHistoryTable);
600  }
601
602  @Override
603  public void generateProlog(DdlWrite writer) {
604    platformDdl.generateProlog(writer);
605  }
606
607  /**
608   * Called at the end to generate additional ddl such as regenerate history triggers.
609   */
610  @Override
611  public void generateEpilog(DdlWrite writer) {
612    platformDdl.generateEpilog(writer);
613  }
614
615  @Override
616  public void generate(DdlWrite writer, AddTableComment addTableComment) {
617    if (hasValue(addTableComment.getComment())) {
618      platformDdl.addTableComment(writer.applyPostAlter(), addTableComment.getName(), addTableComment.getComment());
619    }
620  }
621
622  /**
623   * Add add column DDL.
624   */
625  @Override
626  public void generate(DdlWrite writer, AddColumn addColumn) {
627    String tableName = addColumn.getTableName();
628    List<Column> columns = addColumn.getColumn();
629    for (Column column : columns) {
630      alterTableAddColumn(writer, tableName, column, false, isTrue(addColumn.isWithHistory()));
631    }
632    if (isTrue(addColumn.isWithHistory())) {
633      platformDdl.regenerateHistoryTriggers(writer, tableName);
634      // make same changes to the history table
635      if (tableHistory != null) {
636        String historyTable = tableHistory.historyTableName(tableName);
637        for (Column column : columns) {
638          alterTableAddColumn(writer, historyTable, column, true, true);
639        }
640      }
641    }
642    for (Column column : columns) {
643      if (hasValue(column.getReferences())) {
644        writeForeignKey(writer, tableName, column);
645      }
646    }
647  }
648
649  /**
650   * Add drop table DDL.
651   */
652  @Override
653  public void generate(DdlWrite writer, DropTable dropTable) {
654    dropTable(writer.applyPostAlter(), dropTable.getName());
655    if (hasValue(dropTable.getSequenceCol())
656        && platformDdl.getPlatform().getDbIdentity().isSupportsSequence()) {
657      String sequenceName = dropTable.getSequenceName();
658      if (!hasValue(sequenceName)) {
659        sequenceName = namingConvention.getSequenceName(dropTable.getName(), dropTable.getSequenceCol());
660      }
661      dropSequence(writer.applyPostAlter(), sequenceName);
662    }
663  }
664
665  /**
666   * Add table related changes to DDL (tableSpace,...)
667   */
668  @Override
669  public void generate(DdlWrite writer, AlterTable alterTable) {
670    String tableSpace = platformDdl.extract(alterTable.getTablespace());
671    String indexSpace = platformDdl.extract(alterTable.getIndexTablespace());
672    String lobSpace = platformDdl.extract(alterTable.getLobTablespace());
673    if (hasValue(tableSpace)
674      || hasValue(indexSpace)
675      || hasValue(lobSpace)) {
676
677      writer.apply().appendStatement(platformDdl.alterTableTablespace(alterTable.getName(),
678        DdlHelp.toTablespace(tableSpace),
679        DdlHelp.toTablespace(indexSpace),
680        DdlHelp.toTablespace(lobSpace)));
681    }
682  }
683
684  /**
685   * Add drop column DDL.
686   */
687  @Override
688  public void generate(DdlWrite writer, DropColumn dropColumn) {
689    String tableName = dropColumn.getTableName();
690    alterTableDropColumn(writer, tableName, dropColumn.getColumnName());
691
692    if (isTrue(dropColumn.isWithHistory())) {
693      platformDdl.regenerateHistoryTriggers(writer, tableName);
694      // also drop from the history table
695      if (tableHistory != null) {
696        alterTableDropColumn(writer, tableHistory.historyTableName(tableName), dropColumn.getColumnName());
697      }
698    }
699  }
700
701  /**
702   * Add all the appropriate changes based on the column changes.
703   */
704  @Override
705  public void generate(DdlWrite writer, AlterColumn alterColumn) {
706    DdlMigrationHelp ddlHelp = new DdlMigrationHelp(alterColumn);
707    ddlHelp.write(writer);
708
709    if (alterColumn.isHistoryExclude() != null) {
710      // CHECKME should we drop a small comment, if historyExclude changes, as not all DB systems will support this
711      // writer.apply().append("-- historyExclude of ").append(alterColumn.getTableName()).append(".").append(alterColumn.getColumnName())
712      //  .append(" altered to ").append(String.valueOf(alterColumn.isHistoryExclude())).end();
713      platformDdl.regenerateHistoryTriggers(writer, alterColumn.getTableName());
714    }
715
716    if (hasValue(alterColumn.getDropForeignKey())) {
717      alterColumnDropForeignKey(writer, alterColumn);
718    }
719    if (hasValue(alterColumn.getReferences())) {
720      alterColumnAddForeignKey(writer, alterColumn);
721    }
722
723    if (hasValue(alterColumn.getDropUnique())) {
724      alterColumnDropUniqueConstraint(writer, alterColumn);
725    }
726    if (hasValue(alterColumn.getUnique())) {
727      alterColumnAddUniqueConstraint(writer, alterColumn);
728    }
729    if (hasValue(alterColumn.getUniqueOneToOne())) {
730      alterColumnAddUniqueOneToOneConstraint(writer, alterColumn);
731    }
732    if (hasValue(alterColumn.getComment())) {
733      alterColumnComment(writer, alterColumn);
734    }
735    if (hasValue(alterColumn.getDropCheckConstraint())) {
736      dropCheckConstraint(writer, alterColumn, alterColumn.getDropCheckConstraint());
737    }
738
739    boolean alterCheckConstraint = hasValue(alterColumn.getCheckConstraint());
740
741    if (alterCheckConstraint) {
742      // drop constraint before altering type etc
743      dropCheckConstraint(writer, alterColumn, alterColumn.getCheckConstraintName());
744    }
745
746    if (typeChange(alterColumn)
747      || hasValue(alterColumn.getDefaultValue())
748      || alterColumn.isNotnull() != null) {
749      alterColumn(writer, alterColumn);
750    }
751
752    if (alterCheckConstraint) {
753      // add constraint last (after potential type change)
754      addCheckConstraint(writer, alterColumn);
755    }
756  }
757
758  private boolean typeChange(AlterColumn alterColumn) {
759    if (!hasValue(alterColumn.getType())) {
760      return false;
761    }
762    // check, if we have really a type change for that platform
763    // When specifying @Column("db2;clob(64K)") this would not alter
764    // other platforms from "String" to "String" for example
765    String currentType = platformDdl.convert(alterColumn.getCurrentType());
766    String type = platformDdl.convert(alterColumn.getType());
767    return !type.equals(currentType);
768  }
769
770  private void alterColumnComment(DdlWrite writer, AlterColumn alterColumn) {
771    platformDdl.addColumnComment(writer.applyPostAlter(), alterColumn.getTableName(), alterColumn.getColumnName(), alterColumn.getComment());
772  }
773
774  /**
775   * alter all the base attributes (type/default/notnull) of the column together.
776   * Some platforms (like mysql/sqlserver/hana) must do that in one statement,
777   * other platforms may use several statements for altering one of the base
778   * attributes.
779   */
780  protected void alterColumn(DdlWrite writer, AlterColumn alter) {
781    platformDdl.alterColumn(writer, alter);
782
783    if (isTrue(alter.isWithHistory())) {
784      // we will apply only type changes or notNull -> null transition
785      boolean isNull = Boolean.FALSE.equals(alter.isNotnull());
786      boolean applyToHistory = alter.getType() != null || isNull;
787      boolean syncExact = platformDdl.getPlatform().isPlatform(Platform.DB2);
788      // DB2 needs exact sync for notnull/null
789      if (syncExact && alter.isNotnull() != null) {
790        applyToHistory = true;
791      }
792      if (applyToHistory) {
793        platformDdl.regenerateHistoryTriggers(writer, alter.getTableName());
794        if (tableHistory != null) {
795          AlterColumn alterHistoryColumn = new AlterColumn();
796          alterHistoryColumn.setTableName(tableHistory.historyTableName(alter.getTableName()));
797          alterHistoryColumn.setColumnName(alter.getColumnName());
798          // ignore default value (not needed on history tables)
799          alterHistoryColumn.setCurrentType(alter.getCurrentType());
800          alterHistoryColumn.setType(alter.getType());
801          if (syncExact) {
802            alterHistoryColumn.setCurrentNotnull(alter.isCurrentNotnull());
803            alterHistoryColumn.setNotnull(alter.isNotnull());
804          } else if (isNull) {
805            // do transition from notNull to null
806            alterHistoryColumn.setCurrentNotnull(Boolean.TRUE);
807            alterHistoryColumn.setNotnull(Boolean.FALSE);
808          } else {
809            // assume that the column contains null values from the past (required for platforms like mysql/sqlserver/hana)
810            alterHistoryColumn.setCurrentNotnull(Boolean.FALSE);
811          }
812          platformDdl.alterColumn(writer, alterHistoryColumn);
813        }
814      }
815    }
816  }
817
818  protected void dropCheckConstraint(DdlWrite writer, AlterColumn alter, String constraintName) {
819    writer.applyDropDependencies()
820      .appendStatement(platformDdl.alterTableDropConstraint(alter.getTableName(), constraintName));
821  }
822
823  protected void addCheckConstraint(DdlWrite writer, AlterColumn alter) {
824    writer.applyPostAlter()
825      .appendStatement(platformDdl.alterTableAddCheckConstraint(alter.getTableName(), alter.getCheckConstraintName(), alter.getCheckConstraint()));
826  }
827
828
829  protected void alterColumnAddForeignKey(DdlWrite writer, AlterColumn alterColumn) {
830    alterTableAddForeignKey(writer.getOptions(), writer.applyForeignKeys(), new WriteForeignKey(alterColumn));
831  }
832
833  protected void alterColumnDropForeignKey(DdlWrite writer, AlterColumn alter) {
834    writer.applyDropDependencies()
835        .appendStatement(platformDdl.alterTableDropForeignKey(alter.getTableName(), alter.getDropForeignKey()));
836  }
837
838  protected void alterColumnDropUniqueConstraint(DdlWrite writer, AlterColumn alter) {
839    writer.applyDropDependencies()
840        .appendStatement(platformDdl.alterTableDropUniqueConstraint(alter.getTableName(), alter.getDropUnique()));
841  }
842
843  protected void alterColumnAddUniqueOneToOneConstraint(DdlWrite writer, AlterColumn alter) {
844    addUniqueConstraint(writer, alter, alter.getUniqueOneToOne());
845  }
846
847  protected void alterColumnAddUniqueConstraint(DdlWrite writer, AlterColumn alter) {
848    addUniqueConstraint(writer, alter, alter.getUnique());
849  }
850
851  protected void addUniqueConstraint(DdlWrite writer, AlterColumn alter, String uqName) {
852    String[] cols = {alter.getColumnName()};
853    boolean notNull = alter.isNotnull() != null ? alter.isNotnull() : Boolean.TRUE.equals(alter.isNotnull());
854    writer.applyPostAlter()
855        .appendStatement(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols, notNull ? null : cols));
856
857    writer.dropAllForeignKeys().appendStatement(platformDdl.dropIndex(uqName, alter.getTableName()));
858  }
859
860
861  protected void alterTableDropColumn(DdlWrite writer, String tableName, String columnName) {
862    platformDdl.alterTableDropColumn(writer, tableName, columnName);
863  }
864
865  protected void alterTableAddColumn(DdlWrite writer, String tableName, Column column, boolean onHistoryTable, boolean withHistory) {
866    DdlMigrationHelp help = new DdlMigrationHelp(tableName, column, withHistory);
867    if (!onHistoryTable) {
868      help.write(writer);
869    }
870
871    platformDdl.alterTableAddColumn(writer, tableName, column, onHistoryTable, help.getDefaultValue());
872    final String comment = column.getComment();
873    if (comment != null && !comment.isEmpty()) {
874      platformDdl.addColumnComment(writer.applyPostAlter(), tableName, column.getName(), comment);
875    }
876
877  }
878
879  protected boolean isFalse(Boolean value) {
880    return value != null && !value;
881  }
882
883  /**
884   * Return true if null or trimmed string is empty.
885   */
886  protected boolean hasValue(String value) {
887    return value != null && !value.trim().isEmpty();
888  }
889
890  /**
891   * Null safe Boolean true test.
892   */
893  protected boolean isTrue(Boolean value) {
894    return Boolean.TRUE.equals(value);
895  }
896
897}