001package io.ebeaninternal.dbmigration.ddlgeneration.platform; 002 003import io.ebean.annotation.ConstraintMode; 004import io.ebean.annotation.Platform; 005import io.ebean.config.DatabaseConfig; 006import io.ebean.config.DbConstraintNaming; 007import io.ebean.config.dbplatform.DatabasePlatform; 008import io.ebean.config.dbplatform.DbDefaultValue; 009import io.ebean.config.dbplatform.DbIdentity; 010import io.ebean.config.dbplatform.IdType; 011import io.ebean.util.StringHelper; 012import io.ebeaninternal.dbmigration.ddlgeneration.*; 013import io.ebeaninternal.dbmigration.ddlgeneration.platform.util.PlatformTypeConverter; 014import io.ebeaninternal.dbmigration.ddlgeneration.platform.util.VowelRemover; 015import io.ebeaninternal.dbmigration.migration.AddHistoryTable; 016import io.ebeaninternal.dbmigration.migration.AlterColumn; 017import io.ebeaninternal.dbmigration.migration.Column; 018import io.ebeaninternal.dbmigration.migration.DropHistoryTable; 019import io.ebeaninternal.dbmigration.model.MTable; 020 021import java.util.List; 022import java.util.Locale; 023import java.util.regex.Matcher; 024import java.util.regex.Pattern; 025 026/** 027 * Controls the DDL generation for a specific database platform. 028 */ 029public class PlatformDdl { 030 031 // matches on pattern "check ( COLUMNAME ... )". ColumnName is match group 2; 032 private static final Pattern CHECK_PATTERN = Pattern.compile("(.*?\\( *)([^ ]+)(.*)"); 033 034 protected final DatabasePlatform platform; 035 036 protected PlatformHistoryDdl historyDdl = new NoHistorySupportDdl(); 037 038 /** 039 * Converter for logical/standard types to platform specific types. (eg. clob -> text) 040 */ 041 private final PlatformTypeConverter typeConverter; 042 043 /** 044 * For handling support of sequences and autoincrement. 045 */ 046 private final DbIdentity dbIdentity; 047 048 /** 049 * Set to true if table and column comments are included inline with the create statements. 050 */ 051 protected boolean inlineComments; 052 053 /** 054 * Default assumes if exists is supported. 055 */ 056 protected String dropTableIfExists = "drop table if exists "; 057 058 protected String dropTableCascade = ""; 059 060 /** 061 * Default assumes if exists is supported. 062 */ 063 protected String dropSequenceIfExists = "drop sequence if exists "; 064 065 protected String foreignKeyOnDelete = "on delete"; 066 protected String foreignKeyOnUpdate = "on update"; 067 068 protected String identitySuffix = " auto_increment"; 069 protected String identityStartWith = "start with"; 070 protected String identityIncrementBy = "increment by"; 071 protected String identityCache = "cache"; 072 protected String sequenceStartWith = "start with"; 073 protected String sequenceIncrementBy = "increment by"; 074 protected String sequenceCache = "cache"; 075 076 protected String alterTableIfExists = ""; 077 078 protected String dropConstraintIfExists = "drop constraint if exists"; 079 080 protected String dropIndexIfExists = "drop index if exists "; 081 082 protected String alterColumn = "alter column"; 083 084 protected String dropUniqueConstraint = "drop constraint"; 085 086 protected String addConstraint = "add constraint"; 087 088 protected String addColumn = "add column"; 089 090 protected String columnSetType = ""; 091 092 protected String columnSetDefault = "set default"; 093 094 protected String columnDropDefault = "drop default"; 095 096 protected String columnSetNotnull = "set not null"; 097 098 protected String columnSetNull = "set null"; 099 100 protected String columnNotNull = "not null"; 101 102 protected String updateNullWithDefault = "update ${table} set ${column} = ${default} where ${column} is null"; 103 104 protected String createTable = "create table"; 105 106 protected String dropColumn = "drop column"; 107 108 protected String addForeignKeySkipCheck = ""; 109 110 protected String uniqueIndex = "unique"; 111 protected String indexConcurrent = ""; 112 protected String createIndexIfNotExists = ""; 113 114 /** 115 * Set false for MsSqlServer to allow multiple nulls for OneToOne mapping. 116 */ 117 protected boolean inlineUniqueWhenNullable = true; 118 119 protected DbConstraintNaming naming; 120 121 /** 122 * Generally not desired as then they are not named (used with SQLite). 123 */ 124 protected boolean inlineForeignKeys; 125 126 protected boolean includeStorageEngine; 127 128 protected final DbDefaultValue dbDefaultValue; 129 130 protected String fallbackArrayType = "varchar(1000)"; 131 132 public PlatformDdl(DatabasePlatform platform) { 133 this.platform = platform; 134 this.dbIdentity = platform.getDbIdentity(); 135 this.dbDefaultValue = platform.getDbDefaultValue(); 136 this.typeConverter = new PlatformTypeConverter(platform.getDbTypeMap()); 137 } 138 139 /** 140 * Set configuration options. 141 */ 142 public void configure(DatabaseConfig config) { 143 historyDdl.configure(config, this); 144 naming = config.getConstraintNaming(); 145 } 146 147 /** 148 * Create a DdlHandler for the specific database platform. 149 */ 150 public DdlHandler createDdlHandler(DatabaseConfig config) { 151 return new BaseDdlHandler(config, this); 152 } 153 154 /** 155 * Return the identity type to use given the support in the underlying database 156 * platform for sequences and identity/autoincrement. 157 */ 158 public IdType useIdentityType(IdType modelIdentity) { 159 if (modelIdentity == null) { 160 // use the default 161 return dbIdentity.getIdType(); 162 } 163 return identityType(modelIdentity, dbIdentity.getIdType(), dbIdentity.isSupportsSequence(), dbIdentity.isSupportsIdentity()); 164 } 165 166 /** 167 * Determine the id type to use based on requested identityType and 168 * the support for that in the database platform. 169 */ 170 private IdType identityType(IdType modelIdentity, IdType platformIdType, boolean supportsSequence, boolean supportsIdentity) { 171 switch (modelIdentity) { 172 case GENERATOR: 173 return IdType.GENERATOR; 174 case EXTERNAL: 175 return IdType.EXTERNAL; 176 case SEQUENCE: 177 return supportsSequence ? IdType.SEQUENCE : platformIdType; 178 case IDENTITY: 179 return supportsIdentity ? IdType.IDENTITY : platformIdType; 180 default: 181 return platformIdType; 182 } 183 } 184 185 /** 186 * Modify and return the column definition for autoincrement or identity definition. 187 */ 188 public String asIdentityColumn(String columnDefn, DdlIdentity identity) { 189 return columnDefn + identitySuffix; 190 } 191 192 /** 193 * SQl2003 standard identity definition. 194 */ 195 protected String asIdentityStandardOptions(String columnDefn, DdlIdentity identity) { 196 StringBuilder sb = new StringBuilder(columnDefn.length() + 60); 197 sb.append(columnDefn).append(identity.optionGenerated()); 198 sb.append(identity.identityOptions(identityStartWith, identityIncrementBy, identityCache)); 199 return sb.toString(); 200 } 201 202 /** 203 * Return true if the table and column comments are included inline. 204 */ 205 public boolean isInlineComments() { 206 return inlineComments; 207 } 208 209 /** 210 * Return true if the platform includes storage engine clause. 211 */ 212 public boolean isIncludeStorageEngine() { 213 return includeStorageEngine; 214 } 215 216 /** 217 * Return true if foreign key reference constraints need to inlined with create table. 218 * Ideally we don't do this as then the constraints are not named. Do this for SQLite. 219 */ 220 public boolean isInlineForeignKeys() { 221 return inlineForeignKeys; 222 } 223 224 /** 225 * By default this does nothing returning null / no lock timeout. 226 */ 227 public String setLockTimeout(int lockTimeoutSeconds) { 228 return null; 229 } 230 231 /** 232 * Write all the table columns converting to platform types as necessary. 233 */ 234 public void writeTableColumns(DdlBuffer apply, List<Column> columns, DdlIdentity identity) { 235 for (int i = 0; i < columns.size(); i++) { 236 if (i > 0) { 237 apply.append(","); 238 } 239 apply.newLine(); 240 writeColumnDefinition(apply, columns.get(i), identity); 241 } 242 243 for (Column column : columns) { 244 String checkConstraint = column.getCheckConstraint(); 245 if (hasValue(checkConstraint)) { 246 checkConstraint = createCheckConstraint(maxConstraintName(column.getCheckConstraintName()), checkConstraint); 247 if (hasValue(checkConstraint)) { 248 apply.append(",").newLine(); 249 apply.append(checkConstraint); 250 } 251 } 252 } 253 } 254 255 /** 256 * Write the column definition to the create table statement. 257 */ 258 protected void writeColumnDefinition(DdlBuffer buffer, Column column, DdlIdentity identity) { 259 260 String columnDefn = convert(column.getType()); 261 if (identity.useIdentity() && isTrue(column.isPrimaryKey())) { 262 columnDefn = asIdentityColumn(columnDefn, identity); 263 } 264 265 buffer.append(" "); 266 buffer.append(quote(column.getName()), 29); 267 buffer.append(columnDefn); 268 if (!Boolean.TRUE.equals(column.isPrimaryKey())) { 269 String defaultValue = convertDefaultValue(column.getDefaultValue()); 270 if (defaultValue != null) { 271 buffer.append(" default ").append(defaultValue); 272 } 273 } 274 if (isTrue(column.isNotnull()) || isTrue(column.isPrimaryKey())) { 275 buffer.appendWithSpace(columnNotNull); 276 } 277 278 // add check constraints later as we really want to give them a nice name 279 // so that the database can potentially provide a nice SQL error 280 } 281 282 /** 283 * Returns the check constraint. 284 */ 285 public String createCheckConstraint(String ckName, String checkConstraint) { 286 return " constraint " + maxConstraintName(ckName) + " " + quoteCheckConstraint(checkConstraint); 287 } 288 289 /** 290 * Convert the DB column default literal to platform specific. 291 */ 292 public String convertDefaultValue(String dbDefault) { 293 return dbDefaultValue.convert(dbDefault); 294 } 295 296 /** 297 * Return the drop foreign key clause. 298 */ 299 public String alterTableDropForeignKey(String tableName, String fkName) { 300 return "alter table " + alterTableIfExists + quote(tableName) + " " + dropConstraintIfExists + " " + maxConstraintName(fkName); 301 } 302 303 /** 304 * Convert the standard type to the platform specific type. 305 */ 306 public String convert(String type) { 307 if (type == null) { 308 return null; 309 } 310 type = extract(type); 311 if (type.contains("[]")) { 312 return convertArrayType(type); 313 } 314 return typeConverter.convert(type); 315 } 316 317 // if columnType is different for different platforms, use pattern 318 // @Column(columnDefinition = PLATFORM1;DEFINITION1;PLATFORM2;DEFINITON2;DEFINITON-DEFAULT) 319 // e.g. @Column(columnDefinition = "db2;blob(64M);sqlserver,h2;varchar(227);varchar(127)") 320 protected String extract(String type) { 321 if (type == null) { 322 return null; 323 } 324 String[] tmp = type.split(";", -1); // do not discard trailing empty strings 325 if (tmp.length % 2 == 0) { 326 throw new IllegalArgumentException("You need an odd number of arguments in '" + type + "'. See Issue #2559 for details"); 327 } 328 for (int i = 0; i < tmp.length - 2; i += 2) { 329 String[] platforms = tmp[i].split(","); 330 for (String plat : platforms) { 331 if (platform.isPlatform(Platform.valueOf(plat.toUpperCase(Locale.ENGLISH)))) { 332 return tmp[i + 1]; 333 } 334 } 335 } 336 return tmp[tmp.length - 1]; // else 337 } 338 339 /** 340 * Convert the logical array type to a db platform specific type to support the array data. 341 */ 342 protected String convertArrayType(String logicalArrayType) { 343 if (logicalArrayType.endsWith("]")) { 344 return fallbackArrayType; 345 } 346 int colonPos = logicalArrayType.lastIndexOf(']'); 347 return "varchar" + logicalArrayType.substring(colonPos + 1); 348 } 349 350 /** 351 * Add history support to this table using the platform specific mechanism. 352 */ 353 public void createWithHistory(DdlWrite writer, MTable table) { 354 historyDdl.createWithHistory(writer, table); 355 } 356 357 /** 358 * Drop history support for a given table. 359 */ 360 public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) { 361 historyDdl.dropHistoryTable(writer, dropHistoryTable); 362 } 363 364 /** 365 * Add history support to an existing table. 366 */ 367 public void addHistoryTable(DdlWrite writer, AddHistoryTable addHistoryTable) { 368 historyDdl.addHistoryTable(writer, addHistoryTable); 369 } 370 371 /** 372 * Regenerate the history triggers (or function) due to a column being added/dropped/excluded or included. 373 */ 374 public void regenerateHistoryTriggers(DdlWrite writer, String tableName) { 375 historyDdl.updateTriggers(writer, tableName); 376 } 377 378 /** 379 * Generate and return the create sequence DDL. 380 */ 381 public String createSequence(String sequenceName, DdlIdentity identity) { 382 StringBuilder sb = new StringBuilder("create sequence "); 383 sb.append(quote(sequenceName)); 384 sb.append(identity.sequenceOptions(sequenceStartWith, sequenceIncrementBy, sequenceCache)); 385 sb.append(";"); 386 return sb.toString(); 387 } 388 389 /** 390 * Return the drop sequence statement (potentially with if exists clause). 391 */ 392 public String dropSequence(String sequenceName) { 393 return dropSequenceIfExists + quote(sequenceName); 394 } 395 396 /** 397 * Return the drop table statement (potentially with if exists clause). 398 */ 399 public String dropTable(String tableName) { 400 return dropTableIfExists + quote(tableName) + dropTableCascade; 401 } 402 403 /** 404 * Return the drop index statement for known non concurrent index. 405 */ 406 public String dropIndex(String indexName, String tableName) { 407 return dropIndex(indexName, tableName, false); 408 } 409 410 /** 411 * Return the drop index statement. 412 */ 413 public String dropIndex(String indexName, String tableName, boolean concurrent) { 414 return dropIndexIfExists + maxConstraintName(indexName); 415 } 416 417 public String createIndex(WriteCreateIndex create) { 418 if (create.useDefinition()) { 419 return create.getDefinition(); 420 } 421 StringBuilder buffer = new StringBuilder(); 422 buffer.append("create "); 423 if (create.isUnique()) { 424 buffer.append(uniqueIndex).append(" "); 425 } 426 buffer.append("index "); 427 if (create.isConcurrent()) { 428 buffer.append(indexConcurrent); 429 } 430 if (create.isNotExistsCheck()) { 431 buffer.append(createIndexIfNotExists); 432 } 433 buffer.append(maxConstraintName(create.getIndexName())).append(" on ").append(quote(create.getTableName())); 434 appendColumns(create.getColumns(), buffer); 435 return buffer.toString(); 436 } 437 438 /** 439 * Return the foreign key constraint when used inline with create table. 440 */ 441 public String tableInlineForeignKey(WriteForeignKey request) { 442 443 StringBuilder buffer = new StringBuilder(90); 444 buffer.append("foreign key"); 445 appendColumns(request.cols(), buffer); 446 buffer.append(" references ").append(quote(request.refTable())); 447 appendColumns(request.refCols(), buffer); 448 appendForeignKeySuffix(request, buffer); 449 return buffer.toString(); 450 } 451 452 /** 453 * Add foreign key. 454 */ 455 public String alterTableAddForeignKey(DdlOptions options, WriteForeignKey request) { 456 457 StringBuilder buffer = new StringBuilder(90); 458 buffer 459 .append("alter table ").append(quote(request.table())) 460 .append(" add constraint ").append(maxConstraintName(request.fkName())) 461 .append(" foreign key"); 462 appendColumns(request.cols(), buffer); 463 buffer 464 .append(" references ") 465 .append(quote(request.refTable())); 466 appendColumns(request.refCols(), buffer); 467 appendForeignKeySuffix(request, buffer); 468 if (options.isForeignKeySkipCheck()) { 469 buffer.append(addForeignKeySkipCheck); 470 } 471 return buffer.toString(); 472 } 473 474 protected void appendForeignKeySuffix(WriteForeignKey request, StringBuilder buffer) { 475 appendForeignKeyOnDelete(buffer, withDefault(request.onDelete())); 476 appendForeignKeyOnUpdate(buffer, withDefault(request.onUpdate())); 477 } 478 479 protected ConstraintMode withDefault(ConstraintMode mode) { 480 return (mode == null) ? ConstraintMode.RESTRICT : mode; 481 } 482 483 protected void appendForeignKeyOnDelete(StringBuilder buffer, ConstraintMode mode) { 484 appendForeignKeyMode(buffer, foreignKeyOnDelete, mode); 485 } 486 487 protected void appendForeignKeyOnUpdate(StringBuilder buffer, ConstraintMode mode) { 488 appendForeignKeyMode(buffer, foreignKeyOnUpdate, mode); 489 } 490 491 protected void appendForeignKeyMode(StringBuilder buffer, String onMode, ConstraintMode mode) { 492 buffer.append(" ").append(onMode).append(" ").append(translate(mode)); 493 } 494 495 protected String translate(ConstraintMode mode) { 496 switch (mode) { 497 case SET_NULL: 498 return "set null"; 499 case SET_DEFAULT: 500 return "set default"; 501 case RESTRICT: 502 return "restrict"; 503 case CASCADE: 504 return "cascade"; 505 default: 506 throw new IllegalStateException("Unknown mode " + mode); 507 } 508 } 509 510 /** 511 * Drop a unique constraint from the table (Sometimes this is an index). 512 */ 513 public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) { 514 return "alter table " + quote(tableName) + " " + dropUniqueConstraint + " " + maxConstraintName(uniqueConstraintName); 515 } 516 517 /** 518 * Drop a unique constraint from the table. 519 */ 520 public String alterTableDropConstraint(String tableName, String constraintName) { 521 return "alter table " + quote(tableName) + " " + dropConstraintIfExists + " " + maxConstraintName(constraintName); 522 } 523 524 /** 525 * Add a unique constraint to the table. 526 * <p> 527 * Overridden by MsSqlServer for specific null handling on unique constraints. 528 */ 529 public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) { 530 531 StringBuilder buffer = new StringBuilder(90); 532 buffer.append("alter table ").append(quote(tableName)).append(" add constraint ").append(maxConstraintName(uqName)).append(" unique "); 533 appendColumns(columns, buffer); 534 return buffer.toString(); 535 } 536 537 public void alterTableAddColumn(DdlWrite writer, String tableName, Column column, boolean onHistoryTable, String defaultValue) { 538 539 String convertedType = convert(column.getType()); 540 DdlBuffer buffer = alterTable(writer, tableName).append(addColumn, column.getName()); 541 buffer.append(convertedType); 542 543 // Add default value also to history table if it is not excluded 544 if (defaultValue != null) { 545 if (!onHistoryTable || !isTrue(column.isHistoryExclude())) { 546 buffer.append(" default "); 547 buffer.append(defaultValue); 548 } 549 } 550 551 if (!onHistoryTable) { 552 if (isTrue(column.isNotnull())) { 553 buffer.appendWithSpace(columnNotNull); 554 } 555 556 // check constraints cannot be added in one statement for h2 557 if (!StringHelper.isNull(column.getCheckConstraint())) { 558 String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(), column.getCheckConstraint()); 559 writer.applyPostAlter().appendStatement(ddl); 560 } 561 } 562 563 } 564 565 /** 566 * This method is used from DbTriggerBasedHistoryDdl to add the sysPeriodColumns. 567 */ 568 public void alterTableAddColumn(DdlWrite writer, String tableName, String columnName, String columnType, String defaultValue) { 569 570 String convertedType = convert(columnType); 571 DdlBuffer buffer = alterTable(writer, tableName).append(addColumn, columnName); 572 buffer.append(convertedType); 573 574 if (defaultValue != null) { 575 buffer.append(" default "); 576 buffer.append(defaultValue); 577 } 578 } 579 580 public void alterTableDropColumn(DdlWrite writer, String tableName, String columnName) { 581 alterTable(writer, tableName).append(dropColumn, columnName); 582 } 583 584 /** 585 * Return true if unique constraints for nullable columns can be inlined as normal. 586 * Returns false for MsSqlServer and DB2 due to it's not possible to to put a constraint 587 * on a nullable column 588 */ 589 public boolean isInlineUniqueWhenNullable() { 590 return inlineUniqueWhenNullable; 591 } 592 593 /** 594 * Alter a column type. 595 * <p> 596 * Note that that MySql, SQL Server, and HANA instead use alterColumn() 597 * </p> 598 */ 599 protected void alterColumnType(DdlWrite writer, AlterColumn alter) { 600 alterTable(writer, alter.getTableName()).append(alterColumn, alter.getColumnName()) 601 .append(columnSetType).append(convert(alter.getType())); 602 } 603 604 /** 605 * Alter a column adding or removing the not null constraint. 606 * <p> 607 * Note that that MySql, SQL Server, and HANA instead use alterColumn() 608 * </p> 609 */ 610 protected void alterColumnNotnull(DdlWrite writer, AlterColumn alter) { 611 DdlBuffer buffer = alterTable(writer, alter.getTableName()).append(alterColumn, alter.getColumnName()); 612 if (Boolean.TRUE.equals(alter.isNotnull())) { 613 buffer.append(columnSetNotnull); 614 } else { 615 buffer.append(columnSetNull); 616 } 617 } 618 619 /** 620 * Alter table adding the check constraint. 621 */ 622 public String alterTableAddCheckConstraint(String tableName, String checkConstraintName, String checkConstraint) { 623 return "alter table " + quote(tableName) + " " + addConstraint + " " + maxConstraintName(checkConstraintName) + " " + quoteCheckConstraint(checkConstraint); 624 } 625 626 627 /** 628 * Alter column setting the default value. 629 * <p> 630 * Note that that MySql, SQL Server, and HANA instead use alterColumn() 631 * </p> 632 */ 633 protected void alterColumnDefault(DdlWrite writer, AlterColumn alter) { 634 DdlBuffer buffer = alterTable(writer, alter.getTableName()).append(alterColumn, alter.getColumnName()); 635 if (DdlHelp.isDropDefault(alter.getDefaultValue())) { 636 buffer.append(columnDropDefault); 637 } else { 638 buffer.append(columnSetDefault).appendWithSpace(convertDefaultValue(alter.getDefaultValue())); 639 } 640 } 641 642 /** 643 * Alter column setting (type, default value and not null constraint). 644 * <p> 645 * Used by MySql, SQL Server, and HANA as these require all column attributes to 646 * be set together. 647 * </p> 648 */ 649 public void alterColumn(DdlWrite writer, AlterColumn alter) { 650 651 if (hasValue(alter.getType())) { 652 alterColumnType(writer, alter); 653 } 654 655 if (hasValue(alter.getDefaultValue())) { 656 alterColumnDefault(writer, alter); 657 } 658 659 if (alter.isNotnull() != null) { 660 alterColumnNotnull(writer, alter); 661 } 662 } 663 664 /** 665 * Creates or replace a new DdlAlterTable for given tableName. 666 */ 667 protected DdlAlterTable alterTable(DdlWrite writer, String tableName) { 668 return writer.applyAlterTable(tableName, k -> new BaseAlterTableWrite(k, this)); 669 } 670 671 protected void appendColumns(String[] columns, StringBuilder buffer) { 672 buffer.append(" ("); 673 for (int i = 0; i < columns.length; i++) { 674 if (i > 0) { 675 buffer.append(","); 676 } 677 buffer.append(quote(columns[i].trim())); 678 } 679 buffer.append(")"); 680 } 681 682 public DatabasePlatform getPlatform() { 683 return platform; 684 } 685 686 public String getUpdateNullWithDefault() { 687 return updateNullWithDefault; 688 } 689 690 /** 691 * Return true if null or trimmed string is empty. 692 */ 693 protected boolean hasValue(String value) { 694 return value != null && !value.trim().isEmpty(); 695 } 696 697 /** 698 * Null safe Boolean true test. 699 */ 700 protected boolean isTrue(Boolean value) { 701 return Boolean.TRUE.equals(value); 702 } 703 704 /** 705 * Add an inline table comment to the create table statement. 706 */ 707 public void inlineTableComment(DdlBuffer apply, String tableComment) { 708 // do nothing by default (MySql only) 709 } 710 711 /** 712 * Add an table storage engine to the create table statement. 713 */ 714 public void tableStorageEngine(DdlBuffer apply, String storageEngine) { 715 // do nothing by default 716 } 717 718 /** 719 * Add table comment as a separate statement (from the create table statement). 720 */ 721 public void addTableComment(DdlBuffer apply, String tableName, String tableComment) { 722 if (DdlHelp.isDropComment(tableComment)) { 723 tableComment = ""; 724 } 725 apply.append(String.format("comment on table %s is '%s'", quote(tableName), tableComment)).endOfStatement(); 726 } 727 728 /** 729 * Add column comment as a separate statement. 730 */ 731 public void addColumnComment(DdlBuffer apply, String table, String column, String comment) { 732 if (DdlHelp.isDropComment(comment)) { 733 comment = ""; 734 } 735 apply.append(String.format("comment on column %s.%s is '%s'", quote(table), quote(column), comment)).endOfStatement(); 736 } 737 738 /** 739 * Use this to generate a prolog for each script (stored procedures) 740 */ 741 public void generateProlog(DdlWrite writer) { 742 743 } 744 745 /** 746 * Use this to generate an epilog. Will be added at the end of script 747 */ 748 public void generateEpilog(DdlWrite writer) { 749 750 } 751 752 /** 753 * Shortens the given name to the maximum constraint name length of the platform in a deterministic way. 754 * <p> 755 * First, all vowels are removed, If the string is still to long, 31 bits are taken from the hash code 756 * of the string and base36 encoded (10 digits and 26 chars) string. 757 * <p> 758 * As 36^6 > 31^2, the resulting string is never longer as 6 chars. 759 */ 760 protected String maxConstraintName(String name) { 761 if (name.length() > platform.getMaxConstraintNameLength()) { 762 int hash = name.hashCode() & 0x7FFFFFFF; 763 name = VowelRemover.trim(name, 4); 764 if (name.length() > platform.getMaxConstraintNameLength()) { 765 return name.substring(0, platform.getMaxConstraintNameLength() - 7) + "_" + Integer.toString(hash, 36); 766 } 767 } 768 return name; 769 } 770 771 /** 772 * Returns the database-specific "create table" command prefix. For HANA this is 773 * either "create column table" or "create row table", for all other databases 774 * it is "create table". 775 * 776 * @return The "create table" command prefix 777 */ 778 public String getCreateTableCommandPrefix() { 779 return createTable; 780 } 781 782 public boolean suppressPrimaryKeyOnPartition() { 783 return false; 784 } 785 786 public void addTablePartition(DdlBuffer apply, String partitionMode, String partitionColumn) { 787 // only supported by postgres initially 788 } 789 790 /** 791 * Adds tablespace declaration. Now only supported for db2. 792 */ 793 public void addTablespace(DdlBuffer apply, String tablespaceName, String indexTablespace, String lobTablespace) { 794 throw new UnsupportedOperationException("Tablespaces are not supported for this platform"); 795 } 796 797 /** 798 * Moves the table to an other tablespace. 799 */ 800 public String alterTableTablespace(String tablename, String tableSpace, String indexSpace, String lobSpace) { 801 if (tableSpace != null || indexSpace != null || lobSpace != null) { 802 throw new UnsupportedOperationException("Tablespaces are not supported for this platform"); 803 } 804 return null; 805 } 806 807 protected String quote(String dbName) { 808 return platform.convertQuotedIdentifiers(dbName); 809 } 810 811 protected String quoteCheckConstraint(String checkConstraint) { 812 Matcher matcher = CHECK_PATTERN.matcher(checkConstraint); 813 if (matcher.matches()) { 814 return matcher.replaceFirst("$1" + quote(matcher.group(2)) + "$3"); 815 } 816 return checkConstraint; 817 } 818 819}