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;
008import io.ebeaninternal.server.persist.platform.MultiValueBind;
009
010import java.io.IOException;
011
012/**
013 * MS SQL Server platform specific DDL.
014 */
015public class SqlServerDdl extends PlatformDdl {
016
017  public SqlServerDdl(DatabasePlatform platform) {
018    super(platform);
019    this.identitySuffix = " identity(1,1)";
020    this.alterTableIfExists = "";
021    this.addColumn = "add";
022    this.inlineUniqueWhenNullable = false;
023    this.columnSetDefault = "add default";
024    this.dropConstraintIfExists = "drop constraint";
025    this.historyDdl = new SqlServerHistoryDdl();
026  }
027
028  @Override
029  protected void appendForeignKeyMode(StringBuilder buffer, String onMode, ConstraintMode mode) {
030    if (mode != ConstraintMode.RESTRICT) {
031      super.appendForeignKeyMode(buffer, onMode, mode);
032    }
033  }
034
035  @Override
036  public String dropTable(String tableName) {
037    StringBuilder buffer = new StringBuilder();
038    buffer.append("IF OBJECT_ID('");
039    buffer.append(tableName);
040    buffer.append("', 'U') IS NOT NULL drop table ");
041    buffer.append(tableName);
042    return buffer.toString();
043  }
044
045  @Override
046  public String alterTableDropForeignKey(String tableName, String fkName) {
047    int pos = tableName.lastIndexOf('.');
048    String objectId = maxConstraintName(fkName);
049    if (pos != -1) {
050      objectId = tableName.substring(0, pos + 1) + fkName;
051    }
052    return "IF OBJECT_ID('" + objectId + "', 'F') IS NOT NULL " + super.alterTableDropForeignKey(tableName, fkName);
053  }
054
055  @Override
056  public String dropSequence(String sequenceName) {
057    return "IF OBJECT_ID('" + sequenceName + "', 'SO') IS NOT NULL drop sequence " + sequenceName;
058  }
059
060  @Override
061  public String dropIndex(String indexName, String tableName, boolean concurrent) {
062    return "IF EXISTS (SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID('" + tableName + "','U') AND name = '"
063        + maxConstraintName(indexName) + "') drop index " + maxConstraintName(indexName) + " ON " + tableName;
064  }
065  /**
066   * MsSqlServer specific null handling on unique constraints.
067   */
068  @Override
069  public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
070    if (nullableColumns == null || nullableColumns.length == 0) {
071      return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
072    }
073    if (uqName == null) {
074      throw new NullPointerException();
075    }
076    // issues#233
077    String start = "create unique nonclustered index " + uqName + " on " + tableName + "(";
078    StringBuilder sb = new StringBuilder(start);
079
080    for (int i = 0; i < columns.length; i++) {
081      if (i > 0) {
082        sb.append(",");
083      }
084      sb.append(columns[i]);
085    }
086    sb.append(") where");
087    String sep = " ";
088    for (String column : nullableColumns) {
089      sb.append(sep).append(column).append(" is not null");
090      sep = " and ";
091    }
092    return sb.toString();
093  }
094
095  @Override
096  public String alterTableDropConstraint(String tableName, String constraintName) {
097    StringBuilder sb = new StringBuilder();
098    sb.append("IF (OBJECT_ID('").append(constraintName).append("', 'C') IS NOT NULL) ");
099    sb.append(super.alterTableDropConstraint(tableName, constraintName));
100    return sb.toString();
101  }
102  /**
103   * Drop a unique constraint from the table (Sometimes this is an index).
104   */
105  @Override
106  public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) {
107    StringBuilder sb = new StringBuilder();
108    sb.append(dropIndex(uniqueConstraintName, tableName)).append(";\n");
109    sb.append("IF (OBJECT_ID('").append(maxConstraintName(uniqueConstraintName)).append("', 'UQ') IS NOT NULL) ");
110    sb.append(super.alterTableDropUniqueConstraint(tableName, uniqueConstraintName));
111    return sb.toString();
112  }
113  /**
114   * Generate and return the create sequence DDL.
115   */
116  @Override
117  public String createSequence(String sequenceName, DdlIdentity identity) {
118    StringBuilder sb = new StringBuilder(80);
119    sb.append("create sequence ").append(sequenceName).append(" as bigint");
120    final int start = identity.getStart();
121    if (start > 1) {
122      sb.append(" start with ").append(start);
123    } else {
124      sb.append(" start with 1");
125    }
126    final int increment = identity.getIncrement();
127    if (increment > 1) {
128      sb.append(" increment by ").append(increment);
129    }
130    final int cache = identity.getCache();
131    if (cache > 1) {
132      sb.append(" cache ").append(increment);
133    }
134    sb.append(";");
135    return sb.toString();
136  }
137
138  @Override
139  public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
140    // Unfortunately, the SqlServer creates default values with a random name.
141    // You can specify a name in DDL, but this does not work in conjunction with
142    // temporal tables in certain cases. So we have to delete the constraint with
143    // a rather complex statement.
144    StringBuilder sb = new StringBuilder();
145    if (DdlHelp.isDropDefault(defaultValue)) {
146      sb.append("EXEC usp_ebean_drop_default_constraint ").append(tableName).append(", ").append(columnName);
147    } else {
148      sb.append("alter table ").append(tableName);
149      sb.append(" add default ").append(convertDefaultValue(defaultValue)).append(" for ").append(columnName);
150    }
151    return sb.toString();
152  }
153
154  @Override
155  public String alterColumnBaseAttributes(AlterColumn alter) {
156    if (alter.getType() == null && alter.isNotnull() == null) {
157      // No type change or notNull change
158      // defaultValue change already handled in alterColumnDefaultValue
159      return null;
160    }
161    String tableName = alter.getTableName();
162    String columnName = alter.getColumnName();
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 notnullClause = notnull ? " not null" : "";
167
168    return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + type + notnullClause;
169  }
170
171  @Override
172  public String alterColumnType(String tableName, String columnName, String type) {
173
174    // can't alter itself - done in alterColumnBaseAttributes()
175    return null;
176  }
177
178  @Override
179  public String alterColumnNotnull(String tableName, String columnName, boolean notnull) {
180
181    // can't alter itself - done in alterColumnBaseAttributes()
182    return null;
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) throws IOException {
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) throws IOException {
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(DdlBuffer buffer, String tableName, String columnName) throws IOException {
209
210    buffer.append("EXEC usp_ebean_drop_column ").append(tableName).append(", ").append(columnName).endOfStatement();
211  }
212
213  /**
214   * This writes the multi value datatypes needed for {@link MultiValueBind}
215   */
216  @Override
217  public void generateProlog(DdlWrite write) throws IOException {
218    super.generateProlog(write);
219
220    generateTVPDefinitions(write, "bigint");
221    generateTVPDefinitions(write, "float");
222    generateTVPDefinitions(write, "bit");
223    generateTVPDefinitions(write, "date");
224    generateTVPDefinitions(write, "time");
225    //generateTVPDefinitions(write, "datetime2");
226    generateTVPDefinitions(write, "uniqueidentifier");
227    generateTVPDefinitions(write, "nvarchar(max)");
228
229  }
230
231  private void generateTVPDefinitions(DdlWrite write, String definition) throws IOException {
232    int pos = definition.indexOf('(');
233    String name = pos == -1 ? definition : definition.substring(0, pos);
234
235    dropTVP(write.dropAll(), name);
236    //TVPs are included in "I__create_procs.sql"
237    //createTVP(write.apply(), name, definition);
238  }
239
240  private void dropTVP(DdlBuffer ddl, String name) throws IOException {
241    ddl.append("if exists (select name  from sys.types where name = 'ebean_").append(name)
242        .append("_tvp') drop type ebean_").append(name).append("_tvp").endOfStatement();
243  }
244
245  private void createTVP(DdlBuffer ddl, String name, String definition) throws IOException {
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}