All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.ebeaninternal.dbmigration.model.ModelDiff Maven / Gradle / Ivy

There is a newer version: 15.8.0
Show newest version
package io.ebeaninternal.dbmigration.model;

import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebeaninternal.dbmigration.migration.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Used to prepare a diff in terms of changes required to migrate from
 * the base model to the newer model.
 */
public class ModelDiff {

  /**
   * The base model to which we compare the newer model.
   */
  private final ModelContainer baseModel;

  /**
   * List of 'create' type changes.
   */
  private final List applyChanges = new ArrayList<>();

  /**
   * List of 'drop' type changes. Expected to be placed into a separate DDL script.
   */
  private final List dropChanges = new ArrayList<>();

  /**
   * Construct with a base model.
   */
  public ModelDiff(ModelContainer baseModel) {
    this.baseModel = baseModel;
  }

  /**
   * Construct with a base model.
   */
  public ModelDiff() {
    this.baseModel = new ModelContainer();
  }

  /**
   * Return true if apply and drop changes are both empty. This means there are no migration changes.
   */
  public boolean isEmpty() {
    return applyChanges.isEmpty() && dropChanges.isEmpty();
  }

  /**
   * Return the diff as a migration potentially containing an apply changeSet and a drop changeSet.
   */
  public Migration getMigration() {
    Migration migration = new Migration();
    if (!applyChanges.isEmpty()) {
      // add a non-empty apply changeSet
      migration.getChangeSet().add(getApplyChangeSet());
    }
    if (!dropChanges.isEmpty()) {
      // add a non-empty drop changeSet
      migration.getChangeSet().add(getDropChangeSet());
    }
    return migration;
  }

  /**
   * Return the list of 'apply' changes.
   */
  List getApplyChanges() {
    return applyChanges;
  }

  /**
   * Return the list of 'drop' changes.
   */
  List getDropChanges() {
    return dropChanges;
  }

  /**
   * Return the 'apply' changeSet.
   */
  public ChangeSet getApplyChangeSet() {
    // put the changes into a ChangeSet
    ChangeSet applyChangeSet = new ChangeSet();
    applyChangeSet.setType(ChangeSetType.APPLY);
    applyChangeSet.getChangeSetChildren().addAll(applyChanges);
    return applyChangeSet;
  }

  /**
   * Return the 'drop' changeSet.
   */
  ChangeSet getDropChangeSet() {
    // put the changes into a ChangeSet
    ChangeSet createChangeSet = new ChangeSet();
    createChangeSet.setType(ChangeSetType.PENDING_DROPS);
    createChangeSet.getChangeSetChildren().addAll(dropChanges);
    return createChangeSet;
  }

  /**
   * Compare to a 'newer' model and collect the differences.
   */
  public void compareTo(ModelContainer newModel) {
    Set baseSchemas = baseModel.getSchemas();
    for (String schema : newModel.getSchemas()) {
      if (!baseSchemas.contains(schema)) {
        addCreateSchema(schema);
      }
    }

    Map newTables = newModel.getTables();
    for (MTable newTable : newTables.values()) {
      MTable currentTable = baseModel.getTable(newTable.getName());
      if (currentTable == null) {
        addNewTable(newTable);
      } else {
        compareTables(currentTable, newTable);
      }
    }

    // search for tables that are no longer used
    for (MTable existingTable : baseModel.getTables().values()) {
      if (!newTables.containsKey(existingTable.getName())) {
        // Compounded foreign keys used in many-to-many mapping tables
        existingTable.getCompoundKeys().forEach(it -> addAlterForeignKey(it.dropForeignKey(existingTable.getName())));
        // Foreign keys through direct mapping (oneToMany)
        existingTable.allColumns().stream()
          .filter(it -> it.getForeignKeyIndex() != null || it.getForeignKeyName() != null)
          .map(it -> {
            AlterForeignKey fk = new AlterForeignKey();
            fk.setName(it.getForeignKeyName());
            fk.setIndexName(it.getForeignKeyIndex());
            fk.setColumnNames(DdlHelp.DROP_FOREIGN_KEY);
            fk.setTableName(existingTable.getName());
            return fk;
          })
          .forEach(applyChanges::add);

        addDropTable(existingTable);
      }
    }

    for (MIndex newIndex : newModel.allIndexes()) {
      MIndex currentIndex = baseModel.getIndex(newIndex);
      if (currentIndex == null) {
        addCreateIndex(newIndex.createIndex());
      } else {
        compareIndexes(currentIndex, newIndex);
      }
    }

    // search for indexes that are no longer used
    for (MIndex existingIndex : baseModel.allIndexes()) {
      if (newModel.dropIndex(existingIndex)) {
        addDropIndex(existingIndex.dropIndex());
      }
    }

    // register un-applied ones from the previous migrations
    baseModel.registerPendingHistoryDropColumns(newModel);
    if (!dropChanges.isEmpty()) {
      // register new ones created just now as part of this diff
      newModel.registerPendingHistoryDropColumns(getDropChangeSet());
    }
  }

  protected void addCreateSchema(String schema) {
    CreateSchema createSchema = new CreateSchema();
    createSchema.setName(schema);
    applyChanges.add(createSchema);
  }

  protected void addDropTable(MTable existingTable) {
    dropChanges.add(existingTable.dropTable());
  }

  /**
   * Add CreateTable to the 'apply' changes.
   */
  protected void addNewTable(MTable newTable) {
    applyChanges.add(newTable.createTable());
  }

  /**
   * Compare tables looking for add/drop/modify columns etc.
   */
  protected void compareTables(MTable currentTable, MTable newTable) {
    currentTable.compare(this, newTable);
  }

  /**
   * Compare tables looking for add/drop/modify columns etc.
   */
  protected void compareIndexes(MIndex currentIndex, MIndex newIndex) {
    currentIndex.compare(this, newIndex);
  }

  /**
   * Add the AlterColumn to the 'apply' changes.
   */
  public void addAlterColumn(AlterColumn alterColumn) {
    applyChanges.add(alterColumn);
  }

  /**
   * Add the AlterColumn to the 'apply' changes.
   */
  public void addAddColumn(AddColumn addColumn) {
    applyChanges.add(addColumn);
  }

  /**
   * Add the DropColumn to the 'drop' changes.
   */
  public void addDropColumn(DropColumn dropColumn) {
    dropChanges.add(dropColumn);
  }

  /**
   * Add the AddHistoryTable to apply changes.
   */
  public void addAddHistoryTable(AddHistoryTable addHistoryTable) {
    applyChanges.add(addHistoryTable);
  }

  /**
   * Add the DropHistoryTable to the 'drop history' changes.
   */
  public void addDropHistoryTable(DropHistoryTable dropHistoryTable) {
    dropChanges.add(dropHistoryTable);
  }

  /**
   * Add the DropIndex to the 'apply' changes.
   */
  public void addDropIndex(DropIndex dropIndex) {
    applyChanges.add(dropIndex);
  }

  /**
   * Add the CreateIndex to the 'apply' changes.
   */
  public void addCreateIndex(CreateIndex createIndex) {
    applyChanges.add(createIndex);
  }

  /**
   * Add a table comment to the 'apply' changes.
   */
  public void addTableComment(AddTableComment addTableComment) {
    applyChanges.add(addTableComment);
  }

  /**
   * Adds (or drops) a unique constraint to the 'apply' changes.
   */
  public void addUniqueConstraint(AddUniqueConstraint addUniqueConstraint) {
    applyChanges.add(addUniqueConstraint);
  }

  /**
   * Adds (or drops) a foreign key constraint to the 'apply' changes.
   */
  public void addAlterForeignKey(AlterForeignKey alterForeignKey) {
    applyChanges.add(alterForeignKey);
  }

  /**
   * Adds a table alter.
   */
  public void addAlterTable(AlterTable alterTable) {
    applyChanges.add(alterTable);
  }
}