package org.flywaydb.core.internal.command;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.MigrationInfo;
import org.flywaydb.core.api.MigrationState;
import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.api.callback.Event;
import org.flywaydb.core.api.configuration.Configuration;
import org.flywaydb.core.api.logging.Log;
import org.flywaydb.core.api.logging.LogFactory;
import org.flywaydb.core.api.resolver.MigrationResolver;
import org.flywaydb.core.api.resolver.ResolvedMigration;
import org.flywaydb.core.internal.callback.CallbackExecutor;
import org.flywaydb.core.internal.database.base.Connection;
import org.flywaydb.core.internal.database.base.Database;
import org.flywaydb.core.internal.info.MigrationInfoImpl;
import org.flywaydb.core.internal.info.MigrationInfoServiceImpl;
import org.flywaydb.core.internal.jdbc.ExecutionTemplateFactory;
import org.flywaydb.core.internal.schemahistory.AppliedMigration;
import org.flywaydb.core.internal.schemahistory.SchemaHistory;
import org.flywaydb.core.internal.util.StopWatch;
import org.flywaydb.core.internal.util.TimeFormat;
import java.util.Objects;
import java.util.concurrent.Callable;
public class DbRepair {
private static final Log LOG = LogFactory.getLog(DbRepair.class);
private final Connection connection;
private final MigrationInfoServiceImpl migrationInfoService;
private final SchemaHistory schemaHistory;
private final CallbackExecutor callbackExecutor;
private final Database database;
public DbRepair(Database database, MigrationResolver migrationResolver, SchemaHistory schemaHistory,
CallbackExecutor callbackExecutor, Configuration configuration) {
this.database = database;
this.connection = database.getMainConnection();
this.migrationInfoService = new MigrationInfoServiceImpl(migrationResolver, schemaHistory, configuration,
MigrationVersion.LATEST, true, true, true, true, true);
this.schemaHistory = schemaHistory;
this.callbackExecutor = callbackExecutor;
}
public void repair() {
callbackExecutor.onEvent(Event.BEFORE_REPAIR);
try {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
boolean repaired = ExecutionTemplateFactory.createExecutionTemplate(connection.getJdbcConnection(), database).execute(new Callable<Boolean>() {
public Boolean call() {
schemaHistory.removeFailedMigrations();
migrationInfoService.refresh();
return alignAppliedMigrationsWithResolvedMigrations();
}
});
stopWatch.stop();
LOG.info("Successfully repaired schema history table " + schemaHistory + " (execution time "
+ TimeFormat.format(stopWatch.getTotalTimeMillis()) + ").");
if (repaired && !database.supportsDdlTransactions()) {
LOG.info("Manual cleanup of the remaining effects the failed migration may still be required.");
}
} catch (FlywayException e) {
callbackExecutor.onEvent(Event.AFTER_REPAIR_ERROR);
throw e;
}
callbackExecutor.onEvent(Event.AFTER_REPAIR);
}
private boolean alignAppliedMigrationsWithResolvedMigrations() {
boolean repaired = false;
for (MigrationInfo migrationInfo : migrationInfoService.all()) {
MigrationInfoImpl migrationInfoImpl = (MigrationInfoImpl) migrationInfo;
ResolvedMigration resolved = migrationInfoImpl.getResolvedMigration();
AppliedMigration applied = migrationInfoImpl.getAppliedMigration();
if (resolved != null
&& resolved.getVersion() != null
&& applied != null
&& !applied.getType().isSynthetic()
&& updateNeeded(resolved, applied)) {
schemaHistory.update(applied, resolved);
repaired = true;
}
if (resolved != null
&& resolved.getVersion() == null
&& applied != null
&& !applied.getType().isSynthetic()
&& resolved.checksumMatchesWithoutBeingIdentical(applied.getChecksum())) {
schemaHistory.update(applied, resolved);
repaired = true;
}
}
return repaired;
}
private boolean updateNeeded(ResolvedMigration resolved, AppliedMigration applied) {
return checksumUpdateNeeded(resolved, applied)
|| descriptionUpdateNeeded(resolved, applied)
|| typeUpdateNeeded(resolved, applied);
}
private boolean checksumUpdateNeeded(ResolvedMigration resolved, AppliedMigration applied) {
return !resolved.checksumMatches(applied.getChecksum());
}
private boolean descriptionUpdateNeeded(ResolvedMigration resolved, AppliedMigration applied) {
return !Objects.equals(resolved.getDescription(), applied.getDescription());
}
private boolean typeUpdateNeeded(ResolvedMigration resolved, AppliedMigration applied) {
return !Objects.equals(resolved.getType(), applied.getType());
}
}