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}