SchemaHistory.java
/*-
* ========================LICENSE_START=================================
* flyway-core
* ========================================================================
* Copyright (C) 2010 - 2025 Red Gate Software Ltd
* ========================================================================
* 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.
* =========================LICENSE_END==================================
*/
package org.flywaydb.core.internal.schemahistory;
import lombok.experimental.ExtensionMethod;
import org.flywaydb.core.api.CoreMigrationType;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.MigrationPattern;
import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.api.output.RepairResult;
import org.flywaydb.core.api.resolver.ResolvedMigration;
import org.flywaydb.core.extensibility.AppliedMigration;
import org.flywaydb.core.extensibility.MigrationType;
import org.flywaydb.core.internal.database.base.Schema;
import org.flywaydb.core.internal.database.base.Table;
import org.flywaydb.core.internal.util.AbbreviationUtils;
import org.flywaydb.core.internal.util.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
/**
* The schema history used to track all applied migrations.
*/
@ExtensionMethod(Arrays.class)
public abstract class SchemaHistory {
public static final String NO_DESCRIPTION_MARKER = "<< no description >>";
/**
* The schema history table used by Flyway.
* Non-final due to the table name fallback mechanism. Will be made final in Flyway 6.0.
*/
protected Table table;
/**
* Acquires an exclusive read-write lock on the schema history table. This lock will be released automatically upon completion.
*
* @return The result of the action.
*/
public abstract <T> T lock(Callable<T> callable);
/**
* @return Whether the schema history table exists.
*/
public abstract boolean exists();
/**
* Creates the schema history. Do nothing if it already exists.
*
* @param baseline Whether to include the creation of a baseline marker.
*/
public abstract void create(boolean baseline);
/**
* Drops the schema history table
*/
public void drop() {
throw new FlywayException("Dropping the schema history table is not supported for this SchemaHistory implementation");
}
/**
* Checks whether the schema history table contains at least one non-synthetic applied migration.
*
* @return {@code true} if it does, {@code false} if it doesn't.
*/
public final boolean hasNonSyntheticAppliedMigrations() {
for (AppliedMigration appliedMigration : allAppliedMigrations()) {
if (!appliedMigration.getType().isSynthetic()
&& !appliedMigration.getType().isUndo()
) {
return true;
}
}
return false;
}
/**
* @return The list of all migrations applied on the schema in the order they were applied (oldest first).
* An empty list if no migration has been applied so far.
*/
public abstract List<AppliedMigration> allAppliedMigrations();
/**
* Retrieves the baseline marker from the schema history table.
*
* @return The baseline marker or {@code null} if none could be found.
*/
public final AppliedMigration getBaselineMarker() {
List<AppliedMigration> appliedMigrations = allAppliedMigrations();
// BASELINE can only be the first or second (in case there is a SCHEMA one) migration.
for (int i = 0; i < Math.min(appliedMigrations.size(), 2); i++) {
AppliedMigration appliedMigration = appliedMigrations.get(i);
if (appliedMigration.getType() == CoreMigrationType.BASELINE) {
return appliedMigration;
}
}
return null;
}
/**
* <p>
* Repairs the schema history table after a failed migration.
* This is only necessary for databases without DDL-transaction support.
* </p>
* <p>
* On databases with DDL transaction support, a migration failure automatically triggers a rollback of all changes,
* including the ones in the schema history table.
* </p>
*
* @param repairResult The result object containing which failed migrations were removed.
* @param migrationPatternFilter The migration patterns to filter by.
*/
public abstract boolean removeFailedMigrations(RepairResult repairResult, MigrationPattern[] migrationPatternFilter);
/**
* Indicates in the schema history table that Flyway created these schemas.
*
* @param schemas The schemas that were created by Flyway.
*/
public final void addSchemasMarker(Schema[] schemas) {
addAppliedMigration(null, "<< Flyway Schema Creation >>",
CoreMigrationType.SCHEMA, StringUtils.arrayToCommaDelimitedString(schemas), null, 0, true);
}
/**
* Checks whether the schema history table contains a marker row for schema creation.
*
* @return {@code true} if it does, {@code false} if it doesn't.
*/
public final boolean hasSchemasMarker() {
final List<AppliedMigration> appliedMigrations = allAppliedMigrations();
return !appliedMigrations.isEmpty() && appliedMigrations.stream().anyMatch(x -> x.getType() == CoreMigrationType.SCHEMA);
}
public List<String> getSchemasCreatedByFlyway() {
if (!hasSchemasMarker()) {
return new ArrayList<>();
}
return allAppliedMigrations().stream()
.filter(x -> x.getType() == CoreMigrationType.SCHEMA)
.map(AppliedMigration::getScript)
.flatMap(script -> Arrays.stream(script.split(",")))
.map(result -> table.getDatabase().unQuote(result))
.collect(Collectors.toList());
}
/**
* Updates this applied migration to match this resolved migration.
*
* @param appliedMigration The applied migration to update.
* @param resolvedMigration The resolved migration to source the new values from.
*/
public abstract void update(AppliedMigration appliedMigration, ResolvedMigration resolvedMigration);
/**
* Update the schema history to mark this migration as DELETED
*
* @param appliedMigration The applied migration to mark as DELETED
*/
public abstract void delete(AppliedMigration appliedMigration);
/**
* Clears the applied migration cache.
*/
public void clearCache() {
// Do nothing by default.
}
/**
* Records a new applied migration.
*
* @param version The target version of this migration.
* @param description The description of the migration.
* @param type The type of migration (BASELINE, SQL, ...)
* @param script The name of the script to execute for this migration, relative to its classpath location.
* @param checksum The checksum of the migration. (Optional)
* @param executionTime The execution time (in millis) of this migration.
* @param success Flag indicating whether the migration was successful or not.
*/
public final void addAppliedMigration(MigrationVersion version, String description, MigrationType type,
String script, Integer checksum, int executionTime, boolean success) {
int installedRank = calculateInstalledRank(type);
doAddAppliedMigration(
installedRank,
version,
AbbreviationUtils.abbreviateDescription(description),
type,
AbbreviationUtils.abbreviateScript(script),
checksum,
executionTime,
success);
}
/**
* Calculates the installed rank for the new migration to be inserted.
*
* @param type The type of migration (SCHEMA, SQL, ...)
* @return The installed rank.
*/
protected int calculateInstalledRank(MigrationType type) {
List<AppliedMigration> appliedMigrations = allAppliedMigrations();
if (appliedMigrations.isEmpty()) {
return type == CoreMigrationType.SCHEMA ? 0 : 1;
}
return appliedMigrations.get(appliedMigrations.size() - 1).getInstalledRank() + 1;
}
protected abstract void doAddAppliedMigration(int installedRank, MigrationVersion version, String description,
MigrationType type, String script, Integer checksum,
int executionTime, boolean success);
@Override
public String toString() {
return table.toString();
}
}