001package io.ebeaninternal.dbmigration.ddlgeneration.platform; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import io.ebean.config.dbplatform.DatabasePlatform; 007import io.ebean.util.StringHelper; 008import io.ebeaninternal.dbmigration.ddlgeneration.DdlAlterTable; 009import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer; 010import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite; 011import io.ebeaninternal.dbmigration.migration.AlterColumn; 012import io.ebeaninternal.dbmigration.migration.Column; 013 014/** 015 * DB2 platform specific DDL. 016 */ 017public class DB2Ddl extends PlatformDdl { 018 private static final String MOVE_TABLE = "CALL SYSPROC.ADMIN_MOVE_TABLE(CURRENT_SCHEMA,'%s','%s','%s','%s','','','','','','MOVE')"; 019 020 public DB2Ddl(DatabasePlatform platform) { 021 super(platform); 022 this.dropTableIfExists = "drop table "; 023 this.dropSequenceIfExists = "drop sequence "; 024 this.dropConstraintIfExists = "NOT USED"; 025 this.dropIndexIfExists = "NOT USED"; 026 this.identitySuffix = " generated by default as identity"; 027 this.columnSetNull = "drop not null"; 028 this.columnSetType = "set data type "; 029 this.inlineUniqueWhenNullable = false; 030 this.historyDdl = new Db2HistoryDdl(); 031 } 032 033 @Override 034 public String alterTableTablespace(String tablename, String tableSpace, String indexSpace, String lobSpace) { 035 if(tableSpace == null) { 036 // if no tableSpace set, use the default tablespace USERSPACE1 037 return String.format(MOVE_TABLE, tablename.toUpperCase(), "USERSPACE1", "USERSPACE1", "USERSPACE1"); 038 } else { 039 return String.format(MOVE_TABLE, tablename.toUpperCase(), tableSpace, indexSpace, lobSpace); 040 } 041 } 042 043 @Override 044 public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) { 045 if (nullableColumns == null || nullableColumns.length == 0) { 046 return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns); 047 } 048 049 if (uqName == null) { 050 throw new NullPointerException(); 051 } 052 StringBuilder sb = new StringBuilder("create unique index "); 053 sb.append(maxConstraintName(uqName)).append(" on ").append(tableName).append('('); 054 055 for (int i = 0; i < columns.length; i++) { 056 if (i > 0) { 057 sb.append(","); 058 } 059 sb.append(columns[i]); 060 } 061 sb.append(") exclude null keys"); 062 return sb.toString(); 063 } 064 065 @Override 066 public void addTablespace(DdlBuffer apply, String tablespaceName, String indexTablespace, String lobTablespace) { 067 apply.append(" in ").append(tablespaceName).append(" index in ").append(indexTablespace).append(" long in ").append(lobTablespace); 068 } 069 070 @Override 071 public void alterTableAddColumn(DdlWrite writer, String tableName, Column column, boolean onHistoryTable, String defaultValue) { 072 073 String convertedType = convert(column.getType()); 074 DdlBuffer buffer = alterTable(writer, tableName).append(addColumn, column.getName()); 075 buffer.append(convertedType); 076 077 // Add default value also to history table if it is not excluded 078 if (defaultValue != null) { 079 buffer.append(" default "); 080 buffer.append(defaultValue); 081 } 082 083 if (isTrue(column.isNotnull())) { 084 buffer.appendWithSpace(columnNotNull); 085 } 086 // DB2 History table must match exact! 087 if (!onHistoryTable) { 088 // check constraints cannot be added in one statement for h2 089 if (!StringHelper.isNull(column.getCheckConstraint())) { 090 String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(), column.getCheckConstraint()); 091 writer.applyPostAlter().appendStatement(ddl); 092 } 093 } 094 095 } 096 @Override 097 public String alterTableDropForeignKey(String tableName, String fkName) { 098 return alterTableDropConstraint(tableName, fkName); 099 }; 100 101 @Override 102 public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) { 103 return alterTableDropConstraint(tableName, uniqueConstraintName) 104 + "\n" + dropIndex(uniqueConstraintName, tableName); 105 } 106 107 private void assertNoSchema(String objName) { 108 if (objName.indexOf('.') != -1) { 109 throw new UnsupportedOperationException("Schemas are not yet supported. ObjectName: '" + objName + "'"); 110 } 111 } 112 @Override 113 public String alterTableDropConstraint(String tableName, String constraintName) { 114 assertNoSchema(tableName); 115 StringBuilder sb = new StringBuilder(300); 116 sb.append("delimiter $$\n") 117 .append("begin\n") 118 .append("if exists (select constname from syscat.tabconst where tabschema = current_schema and ucase(constname) = '") 119 .append(maxConstraintName(constraintName).toUpperCase()) 120 121 .append("' and ucase(tabname) = '").append(naming.normaliseTable(tableName).toUpperCase()).append("') then\n") 122 123 .append(" prepare stmt from 'alter table ").append(tableName) 124 .append(" drop constraint ").append(maxConstraintName(constraintName)).append("';\n") 125 126 .append(" execute stmt;\n") 127 .append("end if;\n") 128 .append("end$$"); 129 return sb.toString(); 130 131 } 132 133 @Override 134 public String dropIndex(String indexName, String tableName, boolean concurrent) { 135 assertNoSchema(indexName); 136 StringBuilder sb = new StringBuilder(300); 137 sb.append("delimiter $$\n") 138 .append("begin\n") 139 .append("if exists (select indname from syscat.indexes where indschema = current_schema and ucase(indname) = '") 140 .append(maxConstraintName(indexName).toUpperCase()).append("') then\n") 141 .append(" prepare stmt from 'drop index ").append(maxConstraintName(indexName)).append("';\n") 142 .append(" execute stmt;\n") 143 .append("end if;\n") 144 .append("end$$"); 145 return sb.toString(); 146 } 147 148 @Override 149 public String dropSequence(String sequenceName) { 150 assertNoSchema(sequenceName); 151 StringBuilder sb = new StringBuilder(300); 152 sb.append("delimiter $$\n"); 153 sb.append("begin\n"); 154 sb.append("if exists (select seqschema from syscat.sequences where seqschema = current_schema and ucase(seqname) = '") 155 .append(maxConstraintName(sequenceName).toUpperCase()).append("') then\n"); 156 sb.append(" prepare stmt from 'drop sequence ").append(maxConstraintName(sequenceName)).append("';\n"); 157 sb.append(" execute stmt;\n"); 158 sb.append("end if;\n"); 159 sb.append("end$$"); 160 return sb.toString(); 161 } 162 163 @Override 164 protected void alterColumnType(DdlWrite writer, AlterColumn alter) { 165 String type = convert(alter.getType()); 166 DB2ColumnOptionsParser parser = new DB2ColumnOptionsParser(type); 167 alterTable(writer, alter.getTableName()).append(alterColumn, alter.getColumnName()) 168 .append(columnSetType).append(parser.getType()); 169 170 if (parser.getInlineLength() != null) { 171 alterTable(writer, alter.getTableName()).append(alterColumn, alter.getColumnName()) 172 .append("set").appendWithSpace(parser.getInlineLength()); 173 } 174 175 if (parser.hasExtraOptions()) { 176 alterTable(writer, alter.getTableName()).raw("-- ignored options for ") 177 .append(alter.getTableName()).append(".").append(alter.getColumnName()) 178 .append(": compact=").append(String.valueOf(parser.isCompact())) 179 .append(", logged=").append(String.valueOf(parser.isLogged())); 180 } 181 } 182 183 @Override 184 protected DdlAlterTable alterTable(DdlWrite writer, String tableName) { 185 return writer.applyAlterTable(tableName, Db2AlterTableWrite::new); 186 }; 187 188 class Db2AlterTableWrite extends BaseAlterTableWrite { 189 190 public Db2AlterTableWrite(String tableName) { 191 super(tableName, DB2Ddl.this); 192 } 193 194 @Override 195 protected List<AlterCmd> postProcessCommands(List<AlterCmd> cmds) { 196 List<AlterCmd> ret = new ArrayList<>(cmds.size() + 1); 197 boolean requiresReorg = false; 198 for (AlterCmd cmd : cmds) { 199 ret.add(cmd); 200 if (!requiresReorg && checkReorg(cmd)) { 201 requiresReorg = true; 202 } 203 } 204 if (requiresReorg) { 205 ret.add(newRawCommand("call sysproc.admin_cmd('reorg table " + tableName() + "')")); 206 } 207 return ret; 208 } 209 210 /** 211 * determine, if we need a reorg. 212 * 213 * See: https://www.ibm.com/docs/en/db2/11.5?topic=statements-alter-table The following is the full list of REORG-recommended 214 * ALTER statements that cause a version change and place the table into a REORG-pending state: 215 * <ul> 216 * <li>DROP COLUMN 217 * <li>ALTER COLUMN SET NOT NULL 218 * <li>ALTER COLUMN DROP NOT NULL 219 * <li>ALTER COLUMN SET DATA TYPE, except in the following situations:<br> 220 * Increasing the length of a VARCHAR or VARGRAPHIC column<br> 221 * Decreasing the length of a VARCHAR or VARGRAPHIC column without truncating trailing blanks from existing data, when no indexes 222 * exist on the column 223 * </ul> 224 * 225 */ 226 private boolean checkReorg(AlterCmd cmd) { 227 switch (cmd.getOperation()) { 228 case "drop column": 229 return true; 230 case "alter column": 231 String alter = cmd.getAlternation(); 232 return alter.equals("set not null") 233 || alter.equals("drop not default") 234 || alter.startsWith("set data type"); // note: altering varchar length only is not detected here 235 default: 236 return false; 237 } 238 } 239 } 240}