001package io.ebeaninternal.dbmigration.ddlgeneration.platform;
002
003import io.ebean.annotation.ConstraintMode;
004import io.ebean.config.dbplatform.DatabasePlatform;
005import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
006import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
007import io.ebeaninternal.dbmigration.migration.AlterColumn;
008
009/**
010 * MS SQL Server platform specific DDL.
011 */
012public class SqlServerDdl extends PlatformDdl {
013
014  private static final String CONSTRAINT = "C";
015  private static final String UNIQUE_CONSTRAINT = "UQ";
016  private static final String USER_TABLE = "U";
017  private static final String FOREIGN_KEY = "F";
018  private static final String SEQUENCE_OBJECT = "SO";
019
020  public SqlServerDdl(DatabasePlatform platform) {
021    super(platform);
022    this.identitySuffix = " identity(1,1)";
023    this.alterTableIfExists = "";
024    this.addColumn = "add";
025    this.inlineUniqueWhenNullable = false;
026    this.columnSetDefault = "add default";
027    this.dropConstraintIfExists = "drop constraint";
028    this.historyDdl = new SqlServerHistoryDdl();
029  }
030
031  @Override
032  protected void appendForeignKeyMode(StringBuilder buffer, String onMode, ConstraintMode mode) {
033    if (mode != ConstraintMode.RESTRICT) {
034      super.appendForeignKeyMode(buffer, onMode, mode);
035    }
036  }
037
038  @Override
039  public String dropTable(String tableName) {
040    return ifObjectExists(tableName, USER_TABLE) + "drop table " + tableName;
041  }
042
043  @Override
044  public String alterTableDropForeignKey(String tableName, String fkName) {
045    int pos = tableName.lastIndexOf('.');
046    String objectId = maxConstraintName(fkName);
047    if (pos != -1) {
048      objectId = tableName.substring(0, pos + 1) + fkName;
049    }
050    return ifObjectExists(objectId, FOREIGN_KEY) + super.alterTableDropForeignKey(tableName, fkName);
051  }
052
053  @Override
054  public String dropSequence(String sequenceName) {
055    return ifObjectExists(sequenceName, SEQUENCE_OBJECT) + "drop sequence " + sequenceName;
056  }
057
058  @Override
059  public String dropIndex(String indexName, String tableName, boolean concurrent) {
060    return "IF EXISTS (SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID('" + tableName + "','U') AND name = '"
061        + maxConstraintName(indexName) + "') drop index " + maxConstraintName(indexName) + " ON " + tableName;
062  }
063  /**
064   * MsSqlServer specific null handling on unique constraints.
065   */
066  @Override
067  public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
068    if (nullableColumns == null || nullableColumns.length == 0) {
069      return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
070    }
071    if (uqName == null) {
072      throw new NullPointerException();
073    }
074    // issues#233
075    StringBuilder sb = new StringBuilder(256);
076    sb.append("create unique nonclustered index ").append(uqName).append(" on ").append(tableName).append('(');
077    for (int i = 0; i < columns.length; i++) {
078      if (i > 0) {
079        sb.append(',');
080      }
081      sb.append(columns[i]);
082    }
083    sb.append(") where");
084    String sep = " ";
085    for (String column : nullableColumns) {
086      sb.append(sep).append(column).append(" is not null");
087      sep = " and ";
088    }
089    return sb.toString();
090  }
091
092  @Override
093  public String alterTableDropConstraint(String tableName, String constraintName) {
094    return ifObjectExists(maxConstraintName(constraintName), CONSTRAINT) + super.alterTableDropConstraint(tableName, constraintName);
095  }
096
097  /**
098   * Drop a unique constraint from the table (Sometimes this is an index).
099   */
100  @Override
101  public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) {
102    StringBuilder sb = new StringBuilder();
103    sb.append(dropIndex(uniqueConstraintName, tableName)).append(";\n");
104
105    sb.append(ifObjectExists(maxConstraintName(uniqueConstraintName), UNIQUE_CONSTRAINT))
106        .append(super.alterTableDropUniqueConstraint(tableName, uniqueConstraintName));
107    return sb.toString();
108  }
109
110  /**
111   * Generate and return the create sequence DDL.
112   */
113  @Override
114  public String createSequence(String sequenceName, DdlIdentity identity) {
115    StringBuilder sb = new StringBuilder(80);
116    sb.append("create sequence ").append(sequenceName).append(" as bigint");
117    final int start = identity.getStart();
118    if (start > 1) {
119      sb.append(" start with ").append(start);
120    } else {
121      sb.append(" start with 1");
122    }
123    final int increment = identity.getIncrement();
124    if (increment > 1) {
125      sb.append(" increment by ").append(increment);
126    }
127    final int cache = identity.getCache();
128    if (cache > 1) {
129      sb.append(" cache ").append(increment);
130    }
131    sb.append(";");
132    return sb.toString();
133  }
134
135  @Override
136  protected void alterColumnDefault(DdlWrite writer, AlterColumn alter) {
137    // Unfortunately, the SqlServer creates default values with a random name.
138    // You can specify a name in DDL, but this does not work in conjunction with
139    // temporal tables in certain cases. So we have to delete the constraint with
140    // a rather complex statement.
141    String tableName = alter.getTableName();
142    String columnName = alter.getColumnName();
143    String defaultValue = alter.getDefaultValue();
144    if (DdlHelp.isDropDefault(defaultValue)) {
145      execUspDropDefaultConstraint(writer, tableName, columnName);
146    } else {
147      execUspDropDefaultConstraint(writer, tableName, columnName);
148      setDefaultValue(writer, tableName, columnName, defaultValue);
149    }
150  }
151
152  @Override
153  public void alterColumn(DdlWrite writer, AlterColumn alter) {
154    String tableName = alter.getTableName();
155    String columnName = alter.getColumnName();
156    if (alter.getType() == null && alter.isNotnull() == null) {
157      // No type change or notNull change
158      if (hasValue(alter.getDefaultValue())) {
159        alterColumnDefault(writer, alter);
160      }
161    } else {
162      // we must regenerate whole statement -> read altered and current value
163      String type = alter.getType() != null ? alter.getType() : alter.getCurrentType();
164      type = convert(type);
165      boolean notnull = (alter.isNotnull() != null) ? alter.isNotnull() : Boolean.TRUE.equals(alter.isCurrentNotnull());
166      String defaultValue = alter.getDefaultValue() != null ? alter.getDefaultValue() : alter.getCurrentDefaultValue();
167      if (hasValue(defaultValue)) {
168        // default value present -> drop default constraint before altering
169        execUspDropDefaultConstraint(writer, tableName, columnName);
170      }
171
172      DdlBuffer buffer = alterTable(writer, tableName).append(alterColumn, columnName);
173      buffer.append(type);
174      if (notnull) {
175        buffer.append(" not null");
176      }
177
178      // re add - default constraint
179      if (hasValue(defaultValue) && !DdlHelp.isDropDefault(defaultValue)) {
180        setDefaultValue(writer, tableName, columnName, defaultValue);
181      }
182    }
183  }
184
185  /**
186   * Add table comment as a separate statement (from the create table statement).
187   */
188  @Override
189  public void addTableComment(DdlBuffer apply, String tableName, String tableComment) {
190
191    // do nothing for MS SQL Server (cause it requires stored procedures etc)
192  }
193
194  /**
195   * Add column comment as a separate statement.
196   */
197  @Override
198  public void addColumnComment(DdlBuffer apply, String table, String column, String comment) {
199
200    // do nothing for MS SQL Server (cause it requires stored procedures etc)
201  }
202
203  /**
204   * It is rather complex to delete a column on SqlServer as there must not exist any references
205   * (constraints, default values, indices and foreign keys). That's why we call a user stored procedure here
206   */
207  @Override
208  public void alterTableDropColumn(DdlWrite writer, String tableName, String columnName) {
209    alterTable(writer, tableName).raw("EXEC usp_ebean_drop_column ").append(tableName).append(", ").append(columnName);
210  }
211
212  /**
213   * This writes the multi value datatypes needed for MultiValueBind.
214   */
215  @Override
216  public void generateProlog(DdlWrite writer) {
217    super.generateProlog(writer);
218
219    generateTVPDefinitions(writer, "bigint");
220    generateTVPDefinitions(writer, "float");
221    generateTVPDefinitions(writer, "bit");
222    generateTVPDefinitions(writer, "date");
223    generateTVPDefinitions(writer, "time");
224    //generateTVPDefinitions(write, "datetime2");
225    generateTVPDefinitions(writer, "uniqueidentifier");
226    generateTVPDefinitions(writer, "nvarchar(max)");
227
228  }
229
230  private void generateTVPDefinitions(DdlWrite writer, String definition) {
231    int pos = definition.indexOf('(');
232    String name = pos == -1 ? definition : definition.substring(0, pos);
233
234    dropTVP(writer.dropAll(), name);
235    //TVPs are included in "I__create_procs.sql"
236    //createTVP(write.apply(), name, definition);
237  }
238
239  private void dropTVP(DdlBuffer ddl, String name) {
240    ddl.append("if exists (select name  from sys.types where name = 'ebean_").append(name)
241        .append("_tvp') drop type ebean_").append(name).append("_tvp").endOfStatement();
242  }
243
244  @SuppressWarnings("unused")
245  private void createTVP(DdlBuffer ddl, String name, String definition) {
246    ddl.append("if not exists (select name  from sys.types where name = 'ebean_").append(name)
247    .append("_tvp') create type ebean_").append(name).append("_tvp as table (c1 ").append(definition).append(")")
248        .endOfStatement();
249  }
250
251  public static String ifObjectExists(String object, String objectType) {
252    return "IF OBJECT_ID('" + object + "', '" + objectType + "') IS NOT NULL ";
253  }
254
255  private void execUspDropDefaultConstraint(DdlWrite writer, String tableName, String columnName) {
256    alterTable(writer, tableName).raw("EXEC usp_ebean_drop_default_constraint " + tableName + ", " + columnName);
257  }
258
259  private void setDefaultValue(DdlWrite writer, String tableName, String columnName, String defaultValue) {
260    alterTable(writer, tableName).append("add default " + convertDefaultValue(defaultValue) + " for", columnName);
261  }
262}