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}