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