/**
  * Executes the given scripts and updates the database execution registry appropriately. After
  * each successful script execution, the script execution is registered in the database and marked
  * as successful. If a script execution fails, the script execution is registered in the database
  * and marked as unsuccessful.
  *
  * @param scriptUpdates the script updates to be executed
  */
 protected void executeScriptUpdates(SortedSet<ScriptUpdate> scriptUpdates) {
   scriptRunner.initialize();
   try {
     for (ScriptUpdate scriptUpdate : scriptUpdates) {
       long startTimeMs = currentTimeMillis();
       executeScript(scriptUpdate.getScript());
       long durationMs = currentTimeMillis() - startTimeMs;
       logger.info(
           "Executed "
               + scriptUpdatesFormatter.formatScriptUpdate(scriptUpdate)
               + " ("
               + durationMs
               + " ms)");
     }
   } finally {
     scriptRunner.close();
   }
 }
  /**
   * This operation can be used to bring the database to the latest version. First it checks which
   * scripts were already applied to the database and executes the new scripts or the updated
   * repeatable scripts. If an existing incremental script was changed, removed, or if a new
   * incremental script has been added with a lower index than one that was already executed, an
   * error is given; unless the <fromScratch> option is enabled: in that case all database objects
   * at the end.
   *
   * @param dryRun if true, no updates have to be performed on the database - we do a simulation of
   *     the database update instead of actually performing the database update.
   * @return whether updates were performed on the database
   */
  public boolean updateDatabase(boolean dryRun) {
    try {
      ScriptUpdates scriptUpdates = getScriptUpdates();

      if (!getIncrementalScriptsThatFailedDuringLastUpdate().isEmpty()
          && !scriptUpdates.hasIrregularScriptUpdates()) {
        ExecutedScript failedExecutedScriptScript =
            getIncrementalScriptsThatFailedDuringLastUpdate().first();
        throw new DbMaintainException(
            "During the latest update, the execution of the following incremental script failed: "
                + failedExecutedScriptScript
                + ". \nThis problem must be fixed before any other "
                + "updates can be performed.\n"
                + getErrorScriptOptionsMessage(failedExecutedScriptScript.getScript()));
      }

      if (!getRepeatableScriptsThatFailedDuringLastUpdate().isEmpty()
          && !scriptUpdates.hasIrregularScriptUpdates()) {
        ExecutedScript failedScript = getRepeatableScriptsThatFailedDuringLastUpdate().first();
        if (!scriptUpdates
                .getRegularlyAddedOrModifiedScripts()
                .contains(new ScriptUpdate(REPEATABLE_SCRIPT_UPDATED, failedScript.getScript()))
            && !scriptUpdates
                .getRegularlyDeletedRepeatableScripts()
                .contains(new ScriptUpdate(REPEATABLE_SCRIPT_DELETED, failedScript.getScript()))) {
          throw new DbMaintainException(
              "During the latest update, the execution of following repeatable script failed: "
                  + getRepeatableScriptsThatFailedDuringLastUpdate().first()
                  + ". \nThis problem must be fixed "
                  + "before any other updates can be performed.");
        }
      }

      if (scriptUpdates.isEmpty()) {
        logger.info("The database is up to date");
        return false;
      }

      boolean recreateFromScratch = false;
      if (fromScratchEnabled && isInitialDatabaseUpdate()) {
        logger.info(
            "The database is updated for the first time. The database is cleared to be sure that we start with a clean database");
        recreateFromScratch = true;
      }

      if (scriptUpdates.hasIrregularScriptUpdates()) {
        if (fromScratchEnabled) {
          // Recreate the database from scratch
          logger.info(
              "The database is recreated from scratch, since following irregular script updates were detected:\n"
                  + scriptUpdatesFormatter.formatScriptUpdates(
                      scriptUpdates.getIrregularScriptUpdates()));
          recreateFromScratch = true;
        } else {
          throw new DbMaintainException(
              "Following irregular script updates were detected:\n"
                  + scriptUpdatesFormatter.formatScriptUpdates(
                      scriptUpdates.getIrregularScriptUpdates())
                  + "\nBecause of this, dbmaintain can't perform the update. To solve this problem, you can do one of the following:\n"
                  + "  1: Revert the irregular updates and use regular script updates instead\n"
                  + "  2: Enable the fromScratch option so that the database is recreated from scratch (all data will be lost)\n"
                  + "  3: Perform the updates manually on the database and invoke the markDatabaseAsUpToDate operation (error prone)\n");
        }
      }

      if (recreateFromScratch) {
        if (baseLineRevision != null) {
          throw new DbMaintainException(
              "Unable to recreate the database from scratch: a baseline revision is set.\n"
                  + "After clearing the database only scripts starting from the baseline revision would have been executed. The other scripts would have been ignored resulting in an inconsistent database state.\n"
                  + "Please clear the baseline revision if you want to perform a from scratch update.\n"
                  + "Another option is to explicitly clear the database using the clear task and then performing the update.");
        }
        logger.info("The database is cleared, and all database scripts are executed.");
        if (!dryRun) {
          dbClearer.clearDatabase();
          executedScriptInfoSource.resetCachedState();
          executeScripts(scriptRepository.getAllUpdateScripts());
        }
      } else {
        logger.info(
            "The database is updated incrementally, since following regular script updates were detected:\n"
                + scriptUpdatesFormatter.formatScriptUpdates(
                    scriptUpdates.getRegularScriptUpdates()));
        if (!dryRun) {
          // If the disable constraints option is enabled, disable all FK and not null constraints
          if (disableConstraints) {
            constraintsDisabler.disableConstraints();
          }
          // If cleandb is enabled, remove all data from the database.
          if (cleanDb) {
            dbCleaner.cleanDatabase();
          }
          // If there are incremental patch scripts with a lower index and the option
          // allowOutOfSequenceExecutionOfPatches
          // is enabled, execute them first
          executeScriptUpdates(scriptUpdates.getRegularlyAddedPatchScripts());
          // Execute all new incremental and all new or modified repeatable scripts
          executeScriptUpdates(scriptUpdates.getRegularlyAddedOrModifiedScripts());
          // If repeatable scripts were removed, also remove them from the executed scripts
          removeDeletedRepeatableScriptsFromExecutedScripts(
              scriptUpdates.getRegularlyDeletedRepeatableScripts());
          // If regular script renames were detected, update the executed script records to reflect
          // this
          performRegularScriptRenamesInExecutedScripts(scriptUpdates.getRegularlyRenamedScripts());
        }
      }
      if (scriptUpdates.noUpdatesOtherThanRepeatableScriptDeletionsOrRenames()) {
        logger.info(
            "No script updates were detected, except for repeatable script deletions and script renames. Therefore, actions such as the execution of postprocessing scripts and disabling the constraints are skipped.");
        return false;
      }

      if (!dryRun) {
        // Execute all post processing scripts
        executePostprocessingScripts();

        // If the disable constraints option is enabled, disable all FK and not null constraints
        if (disableConstraints) {
          constraintsDisabler.disableConstraints();
        }
        // the scripts could have added data, if cleandb is enabled, remove all data from the
        // database.
        if (cleanDb) {
          dbCleaner.cleanDatabase();
        }
        // If the update sequences option is enabled, update all sequences to have a value equal to
        // or higher than the configured threshold
        if (updateSequences) {
          sequenceUpdater.updateSequences();
        }
        logger.info("The database has been updated successfully.");
      }
      return true;

    } finally {
      sqlHandler.closeAllConnections();
    }
  }