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