001package io.ebeaninternal.dbmigration.ddlgeneration.platform; 002 003import io.ebean.config.DatabaseConfig; 004import io.ebean.config.dbplatform.DatabasePlatform; 005import io.ebean.config.dbplatform.DbPlatformType; 006import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer; 007import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler; 008import io.ebeaninternal.dbmigration.migration.AlterColumn; 009 010import java.io.IOException; 011import java.util.Objects; 012import java.util.regex.Matcher; 013import java.util.regex.Pattern; 014 015public abstract class AbstractHanaDdl extends PlatformDdl { 016 017 private static final Pattern ARRAY_PATTERN = Pattern.compile("(\\w+)\\s*\\[\\s*\\]\\s*(\\(\\d+\\))?", Pattern.CASE_INSENSITIVE); 018 019 public AbstractHanaDdl(DatabasePlatform platform) { 020 super(platform); 021 this.addColumn = "add ("; 022 this.addColumnSuffix = ")"; 023 this.alterColumn = "alter ("; 024 this.alterColumnSuffix = ")"; 025 this.columnDropDefault = " default null"; 026 this.columnSetDefault = " default"; 027 this.columnSetNotnull = " not null"; 028 this.columnSetNull = " null"; 029 this.dropColumn = "drop ("; 030 this.dropColumnSuffix = ")"; 031 this.dropConstraintIfExists = "drop constraint "; 032 this.dropIndexIfExists = "drop index "; 033 this.dropSequenceIfExists = "drop sequence "; 034 this.dropTableCascade = " cascade"; 035 this.dropTableIfExists = "drop table "; 036 this.fallbackArrayType = "nvarchar(1000)"; 037 this.historyDdl = new HanaHistoryDdl(); 038 this.identitySuffix = " generated by default as identity"; 039 } 040 041 @Override 042 public String alterColumnBaseAttributes(AlterColumn alter) { 043 String tableName = alter.getTableName(); 044 String columnName = alter.getColumnName(); 045 String currentType = alter.getCurrentType(); 046 String type = alter.getType() != null ? alter.getType() : currentType; 047 type = convert(type); 048 currentType = convert(currentType); 049 boolean notnull = (alter.isNotnull() != null) ? alter.isNotnull() : Boolean.TRUE.equals(alter.isCurrentNotnull()); 050 String notnullClause = notnull ? " not null" : ""; 051 String defaultValue = DdlHelp.isDropDefault(alter.getDefaultValue()) ? "null" 052 : (alter.getDefaultValue() != null ? alter.getDefaultValue() : alter.getCurrentDefaultValue()); 053 String defaultValueClause = (defaultValue == null || defaultValue.isEmpty()) ? "" : " default " + defaultValue; 054 055 try { 056 DdlBuffer buffer = new BaseDdlBuffer(null); 057 if (!isConvertible(currentType, type)) { 058 // add an intermediate conversion if possible 059 if (isNumberType(currentType)) { 060 // numbers can always be converted to decimal 061 buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName) 062 .append(" decimal ").append(defaultValueClause).append(notnullClause).append(alterColumnSuffix) 063 .endOfStatement(); 064 065 } else if (isStringType(currentType)) { 066 // strings can always be converted to nclob 067 buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName) 068 .append(" nclob ").append(defaultValueClause).append(notnullClause).append(alterColumnSuffix) 069 .endOfStatement(); 070 } 071 } 072 073 buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName) 074 .append(" ").append(type).append(defaultValueClause).append(notnullClause).append(alterColumnSuffix); 075 076 return buffer.getBuffer(); 077 } catch (IOException e) { 078 throw new RuntimeException(e); 079 } 080 } 081 082 @Override 083 public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) { 084 throw new UnsupportedOperationException(); 085 } 086 087 @Override 088 public String alterColumnNotnull(String tableName, String columnName, boolean notnull) { 089 return null; 090 } 091 092 @Override 093 public DdlHandler createDdlHandler(DatabaseConfig config) { 094 return new HanaDdlHandler(config, this); 095 } 096 097 @Override 098 public String alterColumnType(String tableName, String columnName, String type) { 099 return null; 100 } 101 102 @Override 103 protected String convertArrayType(String logicalArrayType) { 104 Matcher matcher = ARRAY_PATTERN.matcher(logicalArrayType); 105 if (matcher.matches()) { 106 return convert(matcher.group(1)) + " array" + (matcher.group(2) == null ? "" : matcher.group(2)); 107 } else { 108 return fallbackArrayType; 109 } 110 } 111 112 @Override 113 public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) { 114 if (nullableColumns == null || nullableColumns.length == 0) { 115 return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns); 116 } else { 117 return "-- cannot create unique index \"" + uqName + "\" on table \"" + tableName + "\" with nullable columns"; 118 } 119 } 120 121 @Override 122 public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) { 123 DdlBuffer buffer = new BaseDdlBuffer(null); 124 try { 125 buffer.append("delimiter $$").newLine(); 126 buffer.append("do").newLine(); 127 buffer.append("begin").newLine(); 128 buffer.append("declare exit handler for sql_error_code 397 begin end").endOfStatement(); 129 buffer.append("exec 'alter table ").append(tableName).append(" ").append(dropUniqueConstraint).append(" ") 130 .append(maxConstraintName(uniqueConstraintName)).append("'").endOfStatement(); 131 buffer.append("end").endOfStatement(); 132 buffer.append("$$"); 133 return buffer.getBuffer(); 134 } catch (IOException e) { 135 throw new RuntimeException(e); 136 } 137 } 138 139 @Override 140 public String alterTableDropConstraint(String tableName, String constraintName) { 141 return alterTableDropUniqueConstraint(tableName, constraintName); 142 } 143 144 /** 145 * It is rather complex to delete a column on HANA as there must not exist any 146 * foreign keys. That's why we call a user stored procedure here 147 */ 148 @Override 149 public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException { 150 buffer.append("CALL usp_ebean_drop_column('").append(tableName).append("', '").append(columnName).append("')") 151 .endOfStatement(); 152 } 153 154 /** 155 * Check if a data type can be converted to another data type. Data types can't 156 * be converted if the target type has a lower precision than the source type. 157 * 158 * @param sourceType The source data type 159 * @param targetType the target data type 160 * @return {@code true} if the type can be converted, {@code false} otherwise 161 */ 162 private boolean isConvertible(String sourceType, String targetType) { 163 if (Objects.equals(sourceType, targetType)) { 164 return true; 165 } 166 167 if (sourceType == null || targetType == null) { 168 return true; 169 } 170 171 if ("bigint".equals(sourceType)) { 172 if ("integer".equals(targetType) || "smallint".equals(targetType) || "tinyint".equals(targetType)) { 173 return false; 174 } 175 } else if ("integer".equals(sourceType)) { 176 if ("smallint".equals(targetType) || "tinyint".equals(targetType)) { 177 return false; 178 } 179 } else if ("smallint".equals(sourceType)) { 180 if ("tinyint".equals(targetType)) { 181 return false; 182 } 183 } else if ("double".equals(sourceType)) { 184 if ("real".equals(targetType)) { 185 return false; 186 } 187 } 188 189 DbPlatformType dbPlatformSourceType = DbPlatformType.parse(sourceType); 190 191 if ("float".equals(dbPlatformSourceType.getName())) { 192 if ("real".equals(targetType)) { 193 return false; 194 } 195 } else if ("varchar".equals(dbPlatformSourceType.getName()) || "nvarchar".equals(dbPlatformSourceType.getName())) { 196 DbPlatformType dbPlatformTargetType = DbPlatformType.parse(targetType); 197 if ("varchar".equals(dbPlatformTargetType.getName()) || "nvarchar".equals(dbPlatformTargetType.getName())) { 198 if (dbPlatformSourceType.getDefaultLength() > dbPlatformTargetType.getDefaultLength()) { 199 return false; 200 } 201 } 202 } else if ("decimal".equals(dbPlatformSourceType.getName())) { 203 DbPlatformType dbPlatformTargetType = DbPlatformType.parse(targetType); 204 if ("decimal".equals(dbPlatformTargetType.getName())) { 205 if (dbPlatformSourceType.getDefaultLength() > dbPlatformTargetType.getDefaultLength() 206 || dbPlatformSourceType.getDefaultScale() > dbPlatformTargetType.getDefaultScale()) { 207 return false; 208 } 209 } 210 } 211 212 return true; 213 } 214 215 private boolean isNumberType(String type) { 216 return type != null 217 && ("bigint".equals(type) || "integer".equals(type) || "smallint".equals(type) || "tinyint".equals(type) 218 || type.startsWith("float") || "real".equals(type) || "double".equals(type) || type.startsWith("decimal")); 219 } 220 221 private boolean isStringType(String type) { 222 return type != null 223 && (type.startsWith("varchar") || type.startsWith("nvarchar") || "clob".equals(type) || "nclob".equals(type)); 224 } 225}