Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.flywaydb.core.internal.info.MigrationInfoServiceImpl Maven / Gradle / Ivy
/*
* Copyright (C) Red Gate Software Ltd 2010-2024
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.flywaydb.core.internal.info;
import org.flywaydb.core.api.*;
import org.flywaydb.core.api.configuration.Configuration;
import org.flywaydb.core.api.output.CommandResultFactory;
import org.flywaydb.core.api.output.InfoResult;
import org.flywaydb.core.api.output.OperationResult;
import org.flywaydb.core.api.output.ValidateOutput;
import org.flywaydb.core.api.pattern.ValidatePattern;
import org.flywaydb.core.api.resolver.ResolvedMigration;
import org.flywaydb.core.api.MigrationFilter;
import org.flywaydb.core.extensibility.AppliedMigration;
import org.flywaydb.core.extensibility.LicenseGuard;
import org.flywaydb.core.extensibility.MigrationType;
import org.flywaydb.core.extensibility.Tier;
import org.flywaydb.core.internal.database.base.Database;
import org.flywaydb.core.internal.database.base.Schema;
import org.flywaydb.core.internal.license.FlywayEditionUpgradeRequiredException;
import org.flywaydb.core.internal.resolver.CompositeMigrationResolver;
import org.flywaydb.core.internal.schemahistory.SchemaHistory;
import org.flywaydb.core.internal.util.Pair;
import java.util.*;
public class MigrationInfoServiceImpl implements MigrationInfoService, OperationResult {
private final CompositeMigrationResolver migrationResolver;
private final SchemaHistory schemaHistory;
private final Database database;
private final Configuration configuration;
private final MigrationVersion target;
private final boolean outOfOrder;
private final ValidatePattern[] ignorePatterns;
private final MigrationPattern[] cherryPick;
/**
* The migrations infos calculated at the last refresh.
*/
private List migrationInfos;
/**
* Whether all the specified schemas are empty or not.
*/
private Boolean allSchemasEmpty;
/**
* @param migrationResolver The migration resolver for available migrations.
* @param schemaHistory The schema history table for applied migrations.
* @param configuration The current configuration.
* @param target The target version up to which to retrieve the info.
* @param outOfOrder Allows migrations to be run "out of order".
* @param cherryPick The migrations to consider when migration.
*/
public MigrationInfoServiceImpl(CompositeMigrationResolver migrationResolver, SchemaHistory schemaHistory, Database database, final Configuration configuration,
MigrationVersion target, boolean outOfOrder, ValidatePattern[] ignorePatterns, MigrationPattern[] cherryPick) {
this.migrationResolver = migrationResolver;
this.configuration = configuration;
this.database = database;
this.schemaHistory = schemaHistory;
this.target = target;
this.outOfOrder = outOfOrder;
this.ignorePatterns = ignorePatterns;
this.cherryPick = cherryPick;
}
/**
* Refreshes the info about all known migrations from both the classpath and the DB.
*/
public void refresh() {
Collection resolvedMigrations = migrationResolver.resolveMigrations(configuration);
List appliedMigrations = schemaHistory.allAppliedMigrations();
MigrationInfoContext context = new MigrationInfoContext();
context.target = target;
context.outOfOrder = outOfOrder;
context.ignorePatterns = ignorePatterns;
context.cherryPick = cherryPick;
Map, ResolvedMigration> resolvedVersioned = getResolvedVersionedMigrations(resolvedMigrations, context);
Map resolvedRepeatable = new TreeMap<>(getResolvedRepeatableMigrations(resolvedMigrations));
List> appliedVersioned = new ArrayList<>(getAppliedVersionedMigrations(appliedMigrations, context));
List> appliedRepeatable = new ArrayList<>(getAppliedRepeatableMigrations(appliedMigrations));
updateContextFromAppliedVersionedMigrations(appliedVersioned, context);
if (MigrationVersion.CURRENT == target) {
context.target = context.lastApplied;
}
List migrationInfos1 = new ArrayList<>();
for (Pair av : appliedVersioned) {
ResolvedMigration resolvedMigration = resolvedVersioned.get(Pair.of(av.getLeft().getVersion(), av.getLeft().getType()));
migrationInfos1.add(new MigrationInfoImpl(resolvedMigration, av.getLeft(), context, av.getRight().outOfOrder, av.getRight().deleted, av.getRight().undone));
}
for (ResolvedMigration prv : getPendingResolvedVersionedMigrations(appliedVersioned, resolvedVersioned, context)) {
migrationInfos1.add(new MigrationInfoImpl(prv, null, context, false, false, false));
}
if (configuration.isFailOnMissingTarget() &&
target != null &&
target != MigrationVersion.CURRENT &&
target != MigrationVersion.LATEST &&
target != MigrationVersion.NEXT) {
validateTarget(target, migrationInfos1);
}
context.latestRepeatableRuns = getLatestRepeatableRuns(appliedRepeatable);
for (Pair av : appliedRepeatable) {
AppliedMigration appliedRepeatableMigration = av.getLeft();
ResolvedMigration resolvedMigration = resolvedRepeatable.get(appliedRepeatableMigration.getDescription());
migrationInfos1.add(new MigrationInfoImpl(resolvedMigration, appliedRepeatableMigration, context, false, av.getRight().deleted, false));
}
for (ResolvedMigration prr : getPendingResolvedRepeatableMigrations(appliedRepeatable, resolvedRepeatable, context)) {
migrationInfos1.add(new MigrationInfoImpl(prr, null, context, false, false, false));
}
Collections.sort(migrationInfos1);
migrationInfos = migrationInfos1;
if (context.target == MigrationVersion.NEXT) {
MigrationInfo[] pendingMigrationInfos = pending();
if (pendingMigrationInfos.length == 0) {
context.target = null;
} else {
context.target = pendingMigrationInfos[0].getVersion();
}
}
}
private Map, ResolvedMigration> getResolvedVersionedMigrations(Collection resolvedMigrations, MigrationInfoContext context) {
Map, ResolvedMigration> resolvedVersionedMigrations = new TreeMap<>((p1, p2) -> p1.getLeft().compareTo(p2.getLeft()) == 0 ?
p1.getRight().toString().compareTo(p2.getRight().toString()) :
p1.getLeft().compareTo(p2.getLeft()));
for (ResolvedMigration resolvedMigration : resolvedMigrations) {
MigrationVersion version = resolvedMigration.getVersion();
if (version != null) {
if (version.compareTo(context.lastResolved) > 0) {
context.lastResolved = version;
}
resolvedVersionedMigrations.put(Pair.of(version, resolvedMigration.getType()), resolvedMigration);
}
}
return resolvedVersionedMigrations;
}
private Map getResolvedRepeatableMigrations(Collection resolvedMigrations) {
Map resolvedRepeatableMigrations = new TreeMap<>();
for (ResolvedMigration resolvedMigration : resolvedMigrations) {
if (resolvedMigration.getVersion() == null) {
resolvedRepeatableMigrations.put(resolvedMigration.getDescription(), resolvedMigration);
}
}
return resolvedRepeatableMigrations;
}
private List> getAppliedVersionedMigrations(List appliedMigrations, MigrationInfoContext context) {
List> appliedVersionedMigrations = new ArrayList<>();
for (AppliedMigration appliedMigration : appliedMigrations) {
appliedMigration.updateAttributes(appliedVersionedMigrations);
MigrationVersion version = appliedMigration.getVersion();
if (version == null) {
continue;
}
if (appliedMigration.getType() == CoreMigrationType.SCHEMA) {
context.schema = version;
}
if (appliedMigration.getType().isBaseline()) {
if (context.appliedBaseline == null || version.isNewerThan(context.appliedBaseline.getVersion())) {
context.appliedBaseline = version;
}
}
if (appliedMigration.getType().equals(CoreMigrationType.DELETE) && appliedMigration.isSuccess()) {
markAsDeleted(version, appliedVersionedMigrations);
continue;
}
appliedVersionedMigrations.add(Pair.of(appliedMigration, new AppliedMigrationAttributes()));
}
return appliedVersionedMigrations;
}
private List> getAppliedRepeatableMigrations(List appliedMigrations) {
List> appliedRepeatableMigrations = new ArrayList<>();
for (AppliedMigration appliedMigration : appliedMigrations) {
if (appliedMigration.getVersion() == null) {
appliedRepeatableMigrations.add(Pair.of(appliedMigration, new AppliedMigrationAttributes()));
if (appliedMigration.getType().equals(CoreMigrationType.DELETE) && appliedMigration.isSuccess()) {
markRepeatableAsDeleted(appliedMigration.getDescription(), appliedRepeatableMigrations);
}
}
}
return appliedRepeatableMigrations;
}
private void validateTarget(MigrationVersion target, List migrationInfos) {
boolean targetFound = false;
for (MigrationInfoImpl migration : migrationInfos) {
if (target.compareTo(migration.getVersion()) == 0) {
targetFound = true;
break;
}
}
if (!targetFound) {
throw new FlywayException("No migration with a target version " + target + " could be found. Ensure target is specified correctly and the migration exists.");
}
}
private Set getPendingResolvedVersionedMigrations(List> appliedVersionedMigrations,
Map, ResolvedMigration> resolvedVersionedMigrations, MigrationInfoContext context) {
Set pendingResolvedVersionedMigrations = new HashSet<>(resolvedVersionedMigrations.values());
for (Pair av : appliedVersionedMigrations) {
ResolvedMigration resolvedMigration = resolvedVersionedMigrations.get(Pair.of(av.getLeft().getVersion(), av.getLeft().getType()));
if (resolvedMigration != null
&& !av.getRight().deleted && av.getLeft().getType() != CoreMigrationType.DELETE && !av.getRight().undone) {
pendingResolvedVersionedMigrations.remove(resolvedMigration);
}
}
for (ResolvedMigration resolvedMigration : pendingResolvedVersionedMigrations) {
if (resolvedMigration.getType().isBaseline() && (context.pendingBaseline == null || resolvedMigration.getVersion().isNewerThan(context.pendingBaseline.getVersion()))) {
context.pendingBaseline = resolvedMigration.getVersion();
}
}
return pendingResolvedVersionedMigrations;
}
private Set getPendingResolvedRepeatableMigrations(List> appliedRepeatableMigrations,
Map resolvedRepeatableMigrations, MigrationInfoContext context) {
Set pendingResolvedVRepeatableMigrations = new HashSet<>(resolvedRepeatableMigrations.values());
for (Pair av : appliedRepeatableMigrations) {
AppliedMigration appliedRepeatableMigration = av.getLeft();
String desc = appliedRepeatableMigration.getDescription();
int rank = appliedRepeatableMigration.getInstalledRank();
ResolvedMigration resolvedMigration = resolvedRepeatableMigrations.get(desc);
int latestRank = context.latestRepeatableRuns.get(desc);
if (!av.getRight().deleted && av.getLeft().getType() != CoreMigrationType.DELETE &&
resolvedMigration != null && rank == latestRank &&
resolvedMigration.checksumMatches(appliedRepeatableMigration.getChecksum())) {
pendingResolvedVRepeatableMigrations.remove(resolvedMigration);
}
}
return pendingResolvedVRepeatableMigrations;
}
private Map getLatestRepeatableRuns(List> appliedRepeatableMigrations) {
Map latestRepeatableRuns = new HashMap<>();
for (Pair av : appliedRepeatableMigrations) {
if (av.getRight().deleted && av.getLeft().getType() == CoreMigrationType.DELETE) {
continue;
}
AppliedMigration appliedRepeatableMigration = av.getLeft();
String desc = appliedRepeatableMigration.getDescription();
int rank = appliedRepeatableMigration.getInstalledRank();
if (!latestRepeatableRuns.containsKey(desc) || (rank > latestRepeatableRuns.get(desc))) {
latestRepeatableRuns.put(desc, rank);
}
}
return latestRepeatableRuns;
}
private void updateContextFromAppliedVersionedMigrations(List> appliedVersionedMigrations, MigrationInfoContext context) {
for (Pair av : appliedVersionedMigrations) {
AppliedMigration appliedMigration = av.getLeft();
MigrationVersion version = appliedMigration.getVersion();
if (version.compareTo(context.lastApplied) > 0) {
if (av.getLeft().getType() != CoreMigrationType.DELETE && !av.getRight().deleted
&& av.getLeft().isVersioned() && !av.getRight().undone) {
context.lastApplied = version;
}
} else {
av.getRight().outOfOrder = true;
}
}
}
/**
* Marks the latest applied migration with this description as deleted.
*
* @param description The description to match
* @param appliedRepeatable The discovered applied migrations
*/
private void markRepeatableAsDeleted(String description, List> appliedRepeatable) {
for (int i = appliedRepeatable.size() - 1; i >= 0; i--) {
Pair ar = appliedRepeatable.get(i);
if (!ar.getLeft().getType().isSynthetic() && description.equals(ar.getLeft().getDescription())) {
if (!ar.getRight().deleted) {
ar.getRight().deleted = true;
}
return;
}
}
}
/**
* Marks the latest applied migration with this version as deleted.
*
* @param version The version.
* @param appliedVersioned The applied migrations.
*/
private void markAsDeleted(MigrationVersion version, List> appliedVersioned) {
for (int i = appliedVersioned.size() - 1; i >= 0; i--) {
Pair av = appliedVersioned.get(i);
if (!av.getLeft().getType().isSynthetic() && version.equals(av.getLeft().getVersion())) {
if (av.getRight().deleted) {
throw new FlywayException("Corrupted schema history: multiple delete entries for version " + version,
ErrorCode.DUPLICATE_DELETED_MIGRATION);
} else {
av.getRight().deleted = true;
return;
}
}
}
}
@Override
public MigrationInfo[] all() {
return migrationInfos.toArray(new MigrationInfo[0]);
}
public MigrationInfo[] all(MigrationFilter filter) {
if (filter == null) {
return migrationInfos.toArray(new MigrationInfo[0]);
}
return migrationInfos.stream()
.filter(m -> filter.matches(m) || m.getState() == MigrationState.AVAILABLE)
.toArray(MigrationInfo[]::new);
}
@Override
public MigrationInfo current() {
MigrationInfo current = null;
for (MigrationInfoImpl migrationInfo : migrationInfos) {
if (migrationInfo.getState().isApplied()
&& !MigrationState.DELETED.equals(migrationInfo.getState())
&& !migrationInfo.getType().equals(CoreMigrationType.DELETE)
&& !MigrationState.UNDONE.equals(migrationInfo.getState())
&& migrationInfo.isVersioned()
&& (current == null || migrationInfo.getVersion().compareTo(current.getVersion()) > 0)) {
current = migrationInfo;
}
}
if (current != null) {
return current;
}
// If no versioned migration has been applied so far, fall back to the latest repeatable one
for (int i = migrationInfos.size() - 1; i >= 0; i--) {
MigrationInfoImpl migrationInfo = migrationInfos.get(i);
if (migrationInfo.getState().isApplied()
&& !MigrationState.DELETED.equals(migrationInfo.getState())
&& !migrationInfo.getType().equals(CoreMigrationType.DELETE)
&& !MigrationState.UNDONE.equals(migrationInfo.getState())
&& migrationInfo.getVersion() == null
) {
return migrationInfo;
}
}
return null;
}
@Override
public MigrationInfoImpl[] pending() {
List pendingMigrations = new ArrayList<>();
for (MigrationInfoImpl migrationInfo : migrationInfos) {
if (MigrationState.PENDING == migrationInfo.getState()) {
pendingMigrations.add(migrationInfo);
}
}
return pendingMigrations.toArray(new MigrationInfoImpl[0]);
}
@Override
public MigrationInfoImpl[] applied() {
List appliedMigrations = new ArrayList<>();
for (MigrationInfoImpl migrationInfo : migrationInfos) {
if (migrationInfo.getState().isApplied()) {
appliedMigrations.add(migrationInfo);
}
}
return appliedMigrations.toArray(new MigrationInfoImpl[0]);
}
/**
* @return The resolved migrations. An empty array if none.
*/
public MigrationInfo[] resolved() {
List resolvedMigrations = new ArrayList<>();
for (MigrationInfo migrationInfo : migrationInfos) {
if (migrationInfo.getState().isResolved()) {
resolvedMigrations.add(migrationInfo);
}
}
return resolvedMigrations.toArray(new MigrationInfo[0]);
}
/**
* @return The failed migrations. An empty array if none.
*/
public MigrationInfoImpl[] failed() {
List failedMigrations = new ArrayList<>();
for (MigrationInfoImpl migrationInfo : migrationInfos) {
if (migrationInfo.getState().isFailed()) {
failedMigrations.add(migrationInfo);
}
}
return failedMigrations.toArray(new MigrationInfoImpl[0]);
}
/**
* @return The future migrations. An empty array if none.
*/
public MigrationInfo[] future() {
List futureMigrations = new ArrayList<>();
for (MigrationInfo migrationInfo : migrationInfos) {
if (((migrationInfo.getState() == MigrationState.FUTURE_SUCCESS)
|| (migrationInfo.getState() == MigrationState.FUTURE_FAILED))
&& migrationInfo.isVersioned()
) {
futureMigrations.add(migrationInfo);
}
}
return futureMigrations.toArray(new MigrationInfo[0]);
}
/**
* @return The out-of-order migrations. An empty array if none.
*/
public MigrationInfo[] outOfOrder() {
List outOfOrderMigrations = new ArrayList<>();
for (MigrationInfo migrationInfo : migrationInfos) {
if (migrationInfo.getState() == MigrationState.OUT_OF_ORDER) {
outOfOrderMigrations.add(migrationInfo);
}
}
return outOfOrderMigrations.toArray(new MigrationInfo[0]);
}
/**
* @return The undo migrations. An empty array if none.
*/
public MigrationInfoImpl[] undo() {
List result = new ArrayList<>();
for (MigrationInfoImpl migrationInfo : migrationInfos) {
if (migrationInfo.getType().isUndo()) {
result.add(migrationInfo);
}
}
return result.toArray(new MigrationInfoImpl[0]);
}
/**
* @return The list of migrations that failed validation, which is empty if everything is fine.
*/
public List validate() {
List invalidMigrations = new ArrayList<>();
for (MigrationInfoImpl migrationInfo : migrationInfos) {
ErrorDetails validateError = migrationInfo.validate();
if (validateError != null) {
invalidMigrations.add(CommandResultFactory.createValidateOutput(migrationInfo, validateError));
}
}
return invalidMigrations;
}
public void setAllSchemasEmpty(Schema[] schemas) {
allSchemasEmpty = Arrays.stream(schemas).filter(Schema::exists).allMatch(Schema::empty);
}
@Override
public InfoResult getInfoResult() {
return getInfoResult(this.all());
}
public InfoResult getInfoResult(MigrationInfo[] infos) {
return CommandResultFactory.createInfoResult(this.configuration, this.database, infos, this.current(), this.allSchemasEmpty);
}
@Override
public InfoResult getInfoResult(MigrationFilter filter) {
return getInfoResult(this.all(filter));
}
}