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}