001package io.ebeaninternal.dbmigration.model; 002 003import io.ebeaninternal.dbmigration.migration.AddColumn; 004import io.ebeaninternal.dbmigration.migration.AddHistoryTable; 005import io.ebeaninternal.dbmigration.migration.AddTableComment; 006import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint; 007import io.ebeaninternal.dbmigration.migration.AlterColumn; 008import io.ebeaninternal.dbmigration.migration.AlterForeignKey; 009import io.ebeaninternal.dbmigration.migration.AlterTable; 010import io.ebeaninternal.dbmigration.migration.ChangeSet; 011import io.ebeaninternal.dbmigration.migration.ChangeSetType; 012import io.ebeaninternal.dbmigration.migration.CreateIndex; 013import io.ebeaninternal.dbmigration.migration.DropColumn; 014import io.ebeaninternal.dbmigration.migration.DropHistoryTable; 015import io.ebeaninternal.dbmigration.migration.DropIndex; 016import io.ebeaninternal.dbmigration.migration.Migration; 017 018import java.util.ArrayList; 019import java.util.List; 020import java.util.Map; 021 022/** 023 * Used to prepare a diff in terms of changes required to migrate from 024 * the base model to the newer model. 025 */ 026public class ModelDiff { 027 028 /** 029 * The base model to which we compare the newer model. 030 */ 031 private final ModelContainer baseModel; 032 033 /** 034 * List of 'create' type changes. 035 */ 036 private final List<Object> applyChanges = new ArrayList<>(); 037 038 /** 039 * List of 'drop' type changes. Expected to be placed into a separate DDL script. 040 */ 041 private final List<Object> dropChanges = new ArrayList<>(); 042 043 /** 044 * Construct with a base model. 045 */ 046 public ModelDiff(ModelContainer baseModel) { 047 this.baseModel = baseModel; 048 } 049 050 /** 051 * Construct with a base model. 052 */ 053 public ModelDiff() { 054 this.baseModel = new ModelContainer(); 055 } 056 057 /** 058 * Return true if apply and drop changes are both empty. This means there are no migration changes. 059 */ 060 public boolean isEmpty() { 061 return applyChanges.isEmpty() && dropChanges.isEmpty(); 062 } 063 064 /** 065 * Return the diff as a migration potentially containing an apply changeSet and a drop changeSet. 066 */ 067 public Migration getMigration() { 068 Migration migration = new Migration(); 069 if (!applyChanges.isEmpty()) { 070 // add a non-empty apply changeSet 071 migration.getChangeSet().add(getApplyChangeSet()); 072 } 073 if (!dropChanges.isEmpty()) { 074 // add a non-empty drop changeSet 075 migration.getChangeSet().add(getDropChangeSet()); 076 } 077 return migration; 078 } 079 080 /** 081 * Return the list of 'apply' changes. 082 */ 083 List<Object> getApplyChanges() { 084 return applyChanges; 085 } 086 087 /** 088 * Return the list of 'drop' changes. 089 */ 090 List<Object> getDropChanges() { 091 return dropChanges; 092 } 093 094 /** 095 * Return the 'apply' changeSet. 096 */ 097 public ChangeSet getApplyChangeSet() { 098 // put the changes into a ChangeSet 099 ChangeSet applyChangeSet = new ChangeSet(); 100 applyChangeSet.setType(ChangeSetType.APPLY); 101 applyChangeSet.getChangeSetChildren().addAll(applyChanges); 102 return applyChangeSet; 103 } 104 105 /** 106 * Return the 'drop' changeSet. 107 */ 108 ChangeSet getDropChangeSet() { 109 // put the changes into a ChangeSet 110 ChangeSet createChangeSet = new ChangeSet(); 111 createChangeSet.setType(ChangeSetType.PENDING_DROPS); 112 createChangeSet.getChangeSetChildren().addAll(dropChanges); 113 return createChangeSet; 114 } 115 116 /** 117 * Compare to a 'newer' model and collect the differences. 118 */ 119 public void compareTo(ModelContainer newModel) { 120 Map<String, MTable> newTables = newModel.getTables(); 121 for (MTable newTable : newTables.values()) { 122 123 MTable currentTable = baseModel.getTable(newTable.getName()); 124 if (currentTable == null) { 125 addNewTable(newTable); 126 } else { 127 compareTables(currentTable, newTable); 128 } 129 } 130 131 // search for tables that are no longer used 132 for (MTable existingTable : baseModel.getTables().values()) { 133 if (!newTables.containsKey(existingTable.getName())) { 134 addDropTable(existingTable); 135 } 136 } 137 138 for (MIndex newIndex : newModel.allIndexes()) { 139 MIndex currentIndex = baseModel.getIndex(newIndex); 140 if (currentIndex == null) { 141 addCreateIndex(newIndex.createIndex()); 142 } else { 143 compareIndexes(currentIndex, newIndex); 144 } 145 } 146 147 // search for indexes that are no longer used 148 for (MIndex existingIndex : baseModel.allIndexes()) { 149 if (newModel.dropIndex(existingIndex)) { 150 addDropIndex(existingIndex.dropIndex()); 151 } 152 } 153 154 // register un-applied ones from the previous migrations 155 baseModel.registerPendingHistoryDropColumns(newModel); 156 if (!dropChanges.isEmpty()) { 157 // register new ones created just now as part of this diff 158 newModel.registerPendingHistoryDropColumns(getDropChangeSet()); 159 } 160 } 161 162 protected void addDropTable(MTable existingTable) { 163 dropChanges.add(existingTable.dropTable()); 164 } 165 166 /** 167 * Add CreateTable to the 'apply' changes. 168 */ 169 protected void addNewTable(MTable newTable) { 170 applyChanges.add(newTable.createTable()); 171 } 172 173 /** 174 * Compare tables looking for add/drop/modify columns etc. 175 */ 176 protected void compareTables(MTable currentTable, MTable newTable) { 177 currentTable.compare(this, newTable); 178 } 179 180 /** 181 * Compare tables looking for add/drop/modify columns etc. 182 */ 183 protected void compareIndexes(MIndex currentIndex, MIndex newIndex) { 184 currentIndex.compare(this, newIndex); 185 } 186 187 /** 188 * Add the AlterColumn to the 'apply' changes. 189 */ 190 public void addAlterColumn(AlterColumn alterColumn) { 191 applyChanges.add(alterColumn); 192 } 193 194 /** 195 * Add the AlterColumn to the 'apply' changes. 196 */ 197 public void addAddColumn(AddColumn addColumn) { 198 applyChanges.add(addColumn); 199 } 200 201 /** 202 * Add the DropColumn to the 'drop' changes. 203 */ 204 public void addDropColumn(DropColumn dropColumn) { 205 dropChanges.add(dropColumn); 206 } 207 208 /** 209 * Add the AddHistoryTable to apply changes. 210 */ 211 public void addAddHistoryTable(AddHistoryTable addHistoryTable) { 212 applyChanges.add(addHistoryTable); 213 } 214 215 /** 216 * Add the DropHistoryTable to the 'drop history' changes. 217 */ 218 public void addDropHistoryTable(DropHistoryTable dropHistoryTable) { 219 dropChanges.add(dropHistoryTable); 220 } 221 222 /** 223 * Add the DropIndex to the 'apply' changes. 224 */ 225 public void addDropIndex(DropIndex dropIndex) { 226 applyChanges.add(dropIndex); 227 } 228 229 /** 230 * Add the CreateIndex to the 'apply' changes. 231 */ 232 public void addCreateIndex(CreateIndex createIndex) { 233 applyChanges.add(createIndex); 234 } 235 236 /** 237 * Add a table comment to the 'apply' changes. 238 */ 239 public void addTableComment(AddTableComment addTableComment) { 240 applyChanges.add(addTableComment); 241 } 242 243 /** 244 * Adds (or drops) a unique constraint to the 'apply' changes. 245 */ 246 public void addUniqueConstraint(AddUniqueConstraint addUniqueConstraint) { 247 applyChanges.add(addUniqueConstraint); 248 } 249 250 /** 251 * Adds (or drops) a foreign key constraint to the 'apply' changes. 252 */ 253 public void addAlterForeignKey(AlterForeignKey alterForeignKey) { 254 applyChanges.add(alterForeignKey); 255 } 256 257 /** 258 * Adds a table alter. 259 */ 260 public void addAlterTable(AlterTable alterTable) { 261 applyChanges.add(alterTable); 262 } 263}