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