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