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}