001package io.ebeaninternal.dbmigration.ddlgeneration.platform; 002 003import io.ebean.config.DatabaseConfig; 004import io.ebeaninternal.dbmigration.ddlgeneration.DdlAlterTable; 005import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer; 006import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite; 007import io.ebeaninternal.dbmigration.migration.AddHistoryTable; 008import io.ebeaninternal.dbmigration.migration.DropHistoryTable; 009import io.ebeaninternal.dbmigration.model.MColumn; 010import io.ebeaninternal.dbmigration.model.MTable; 011 012import java.util.List; 013 014/** 015 * Uses DB triggers to maintain a history table. 016 */ 017public abstract class DbTriggerBasedHistoryDdl extends DbTableBasedHistoryDdl implements PlatformHistoryDdl { 018 019 protected String sysPeriod; 020 protected String sysPeriodStart; 021 protected String sysPeriodEnd; 022 protected String viewSuffix; 023 protected String sysPeriodType = "datetime(6)"; 024 protected String now = "now(6)"; 025 protected String sysPeriodEndValue = "now(6)"; 026 027 DbTriggerBasedHistoryDdl() { 028 } 029 030 @Override 031 public void configure(DatabaseConfig config, PlatformDdl platformDdl) { 032 super.configure(config, platformDdl); 033 this.sysPeriod = config.getAsOfSysPeriod(); 034 this.viewSuffix = config.getAsOfViewSuffix(); 035 this.sysPeriodStart = sysPeriod + "_start"; 036 this.sysPeriodEnd = sysPeriod + "_end"; 037 } 038 039 @Override 040 public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) { 041 String baseTable = dropHistoryTable.getBaseTable(); 042 043 // drop in appropriate order 044 dropTriggers(writer.applyDropDependencies(), baseTable); 045 dropWithHistoryView(writer.applyDropDependencies(), baseTable); 046 dropHistoryTable(writer.applyDropDependencies(), baseTable); 047 048 dropSysPeriodColumns(writer, baseTable); 049 } 050 051 @Override 052 public void addHistoryTable(DdlWrite writer, AddHistoryTable addHistoryTable) { 053 String baseTable = addHistoryTable.getBaseTable(); 054 MTable table = writer.getTable(baseTable); 055 if (table == null) { 056 throw new IllegalStateException("MTable " + baseTable + " not found in writer? (required for history DDL)"); 057 } 058 createWithHistory(writer, table); 059 } 060 061 @Override 062 public void createWithHistory(DdlWrite writer, MTable table) { 063 String baseTable = quote(table.getName()); 064 065 addSysPeriodColumns(writer, baseTable, table.getWhenCreatedColumn()); 066 createHistoryTable(writer.applyPostAlter(), table); 067 068 createWithHistoryView(writer.applyPostAlter(), table.getName()); 069 createTriggers(writer.applyPostAlter(), baseTable, columnNamesForApply(table)); 070 writer.applyPostAlter().end(); 071 072 // drop all scripts 073 dropTriggers(writer.dropAll(), baseTable); 074 dropWithHistoryView(writer.dropAll(), baseTable); 075 dropHistoryTable(writer.dropAll(), baseTable); 076 // no need to dropSysPeriodColumns as whole table will be deleted soon 077 } 078 079 @Override 080 public void updateTriggers(DdlWrite writer, String tableName) { 081 MTable table = writer.getTable(tableName); 082 if (table != null && table.isWithHistory()) { 083 DdlAlterTable alter = platformDdl.alterTable(writer, tableName); 084 if (!alter.isHistoryHandled()) { 085 // this code effectively disables history support before the table alter and enables it again 086 // immediately after the table alter. As all alters per table are altogether now, this can done here 087 dropTriggers(writer.apply(), tableName); 088 dropWithHistoryView(writer.apply(), tableName); 089 // here are the alter commands 090 createWithHistoryView(writer.applyPostAlter(), tableName); 091 createTriggers(writer.applyPostAlter(), quote(tableName), columnNamesForApply(table)); 092 alter.setHistoryHandled(); 093 } 094 } 095 } 096 097 /** 098 * Will add a history trigger to the buffer. The config 099 */ 100 protected abstract void createTriggers(DdlBuffer buffer, String baseTable, List<String> columnNames); 101 102 protected abstract void dropTriggers(DdlBuffer buffer, String baseTable); 103 104 protected String historyViewName(String baseTableName) { 105 return normalise(baseTableName, viewSuffix); 106 } 107 108 protected String procedureName(String baseTableName) { 109 return normalise(baseTableName, "_history_version"); 110 } 111 112 protected String triggerName(String baseTableName) { 113 return normalise(baseTableName, "_history_upd"); 114 } 115 116 protected String updateTriggerName(String baseTableName) { 117 return normalise(baseTableName, "_history_upd"); 118 } 119 120 protected String deleteTriggerName(String baseTableName) { 121 return normalise(baseTableName, "_history_del"); 122 } 123 124 protected void addSysPeriodColumns(DdlWrite writer, String baseTableName, String whenCreatedColumn) { 125 platformDdl.alterTableAddColumn(writer, baseTableName, sysPeriodStart, sysPeriodType, now); 126 platformDdl.alterTableAddColumn(writer, baseTableName, sysPeriodEnd, sysPeriodType, null); 127 if (whenCreatedColumn != null) { 128 writer.applyPostAlter() 129 .append("update ").append(baseTableName).append(" set ").append(sysPeriodStart).append(" = ").append(whenCreatedColumn).endOfStatement(); 130 } 131 } 132 133 protected void createHistoryTable(DdlBuffer apply, MTable table) { 134 createHistoryTableAs(apply, table); 135 createHistoryTableWithPeriod(apply); 136 // TODO: add tablespace here (currently no DbTriggerBased platforms with tablespace support) 137 apply.endOfStatement(); 138 } 139 140 protected void createHistoryTableAs(DdlBuffer apply, MTable table) { 141 apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(historyTableName(table.getName())).append("(").newLine(); 142 for (MColumn column : table.allColumns()) { 143 if (!column.isDraftOnly()) { 144 writeColumnDefinition(apply, column.getName(), column.getType()); 145 apply.append(",").newLine(); 146 } 147 } 148 // TODO: We must apply also pending dropped columns. Let's do that in a later step 149 if (table.hasDroppedColumns()) { 150 throw new IllegalStateException(table.getName() + " has dropped columns. Please generate drop script before enabling history"); 151 } 152 } 153 154 protected void createHistoryTableWithPeriod(DdlBuffer apply) { 155 writeColumnDefinition(apply, sysPeriodStart, sysPeriodType); 156 apply.append(",").newLine(); 157 writeColumnDefinition(apply, sysPeriodEnd, sysPeriodType); 158 apply.newLine().append(")"); 159 } 160 161 /** 162 * Write the column definition to the create table statement. 163 */ 164 protected void writeColumnDefinition(DdlBuffer buffer, String columnName, String type) { 165 String platformType = platformDdl.convert(type); 166 buffer.append(" "); 167 buffer.append(quote(columnName), 29); 168 buffer.append(platformType); 169 } 170 171 protected void createWithHistoryView(DdlBuffer apply, String baseTableName) { 172 apply 173 .append("create view ").append(historyViewName(baseTableName)) 174 .append(" as select * from ").append(quote(baseTableName)) 175 .append(" union all select * from ").append(historyTableName(baseTableName)) 176 .endOfStatement(); 177 } 178 179 protected void appendSysPeriodColumns(DdlBuffer apply, String prefix) { 180 appendColumnName(apply, prefix, sysPeriodStart); 181 appendColumnName(apply, prefix, sysPeriodEnd); 182 } 183 184 protected void dropWithHistoryView(DdlBuffer apply, String baseTableName) { 185 apply.append("drop view ").append(historyViewName(baseTableName)).endOfStatement(); 186 } 187 188 protected void dropHistoryTable(DdlBuffer apply, String baseTableName) { 189 apply.append("drop table ").append(historyTableName(baseTableName)).endOfStatement().end(); 190 } 191 192 protected void dropSysPeriodColumns(DdlWrite writer, String baseTableName) { 193 platformDdl.alterTableDropColumn(writer, baseTableName, sysPeriodStart); 194 platformDdl.alterTableDropColumn(writer, baseTableName, sysPeriodEnd); 195 } 196 197 protected void appendInsertIntoHistory(DdlBuffer buffer, String baseTable, List<String> columns) { 198 buffer.append(" insert into ").append(historyTableName(baseTable)).append(" (").append(sysPeriodStart).append(",").append(sysPeriodEnd).append(","); 199 appendColumnNames(buffer, columns, ""); 200 buffer.append(") values (OLD.").append(sysPeriodStart).append(", ").append(sysPeriodEndValue).append(","); 201 appendColumnNames(buffer, columns, "OLD."); 202 buffer.append(");").newLine(); 203 } 204 205 void appendColumnNames(DdlBuffer buffer, List<String> columns, String columnPrefix) { 206 for (int i = 0; i < columns.size(); i++) { 207 if (i > 0) { 208 buffer.append(", "); 209 } 210 buffer.append(columnPrefix); 211 buffer.append(quote(columns.get(i))); 212 } 213 } 214 215 /** 216 * Append a single column to the buffer if it is not null. 217 */ 218 void appendColumnName(DdlBuffer buffer, String prefix, String columnName) { 219 if (columnName != null) { 220 buffer.append(prefix).append(columnName); 221 } 222 } 223 224 /** 225 * Return the column names included in history for the apply script. 226 * <p> 227 * Note that dropped columns are actually still included at this point as they are going 228 * to be removed from the history handling when the drop script runs that also deletes 229 * the column. 230 * </p> 231 */ 232 List<String> columnNamesForApply(MTable table) { 233 return table.allHistoryColumns(true); 234 } 235 236 237}