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}