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}