/** @return The already executed scripts, as a map from Script => ExecutedScript */ protected Map<Script, ExecutedScript> getAlreadyExecutedScripts() { Map<Script, ExecutedScript> alreadyExecutedScripts = new HashMap<Script, ExecutedScript>(); for (ExecutedScript executedScript : executedScriptInfoSource.getExecutedScripts()) { alreadyExecutedScripts.put(executedScript.getScript(), executedScript); } return alreadyExecutedScripts; }
/** @return the repeatable scripts that failed during the last database update */ protected SortedSet<ExecutedScript> getRepeatableScriptsThatFailedDuringLastUpdate() { SortedSet<ExecutedScript> failedExecutedScripts = new TreeSet<ExecutedScript>(); for (ExecutedScript script : executedScriptInfoSource.getExecutedScripts()) { if (!script.isSuccessful() && script.getScript().isRepeatable()) { failedExecutedScripts.add(script); } } return failedExecutedScripts; }
/** * Executes the given script and updates the database execution registry appropriately. If * successfully, the script execution is registered in the database and marked as successful. If * an error occurred executing the script, the script execution is registered in the database and * marked as unsuccessful. * * @param script The script to execute, not null */ protected void executeScript(Script script) { try { // We register the script execution, but we indicate it to be unsuccessful. If anything goes // wrong or if the update is // interrupted before being completed, this will be the final state and the DbMaintainer will // do a from-scratch update the next time ExecutedScript executedScript = new ExecutedScript(script, new Date(), false); executedScriptInfoSource.registerExecutedScript(executedScript); scriptRunner.execute(script); // We now register the previously registered script execution as being successful executedScript.setSuccessful(true); executedScriptInfoSource.updateExecutedScript(executedScript); } catch (DbMaintainException e) { String message = getErrorMessage(script, e); throw new DbMaintainException(message, e.getCause()); } }
/** * 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(); } }