BaselineVerbExtension.java
/*-
* ========================LICENSE_START=================================
* flyway-verb-baseline
* ========================================================================
* Copyright (C) 2010 - 2026 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.verb.baseline;
import java.util.List;
import lombok.CustomLog;
import org.flywaydb.core.api.CoreMigrationType;
import org.flywaydb.core.api.FlywayException;
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.output.BaselineResult;
import org.flywaydb.core.internal.Topic;
import org.flywaydb.core.api.output.OperationResult;
import org.flywaydb.core.internal.nc.NativeConnectorsDatabase;
import org.flywaydb.core.internal.nc.schemahistory.SchemaHistoryItem;
import org.flywaydb.core.extensibility.CachingVerbExtension;
import org.flywaydb.core.extensibility.ConfigurationParameter;
import org.flywaydb.core.internal.license.VersionPrinter;
import org.flywaydb.core.internal.util.FlywayDbWebsiteLinks;
import org.flywaydb.nc.callbacks.CallbackManager;
import org.flywaydb.nc.preparation.PreparationContext;
import org.flywaydb.verb.schemas.SchemasVerbExtension;
@CustomLog
public class BaselineVerbExtension extends CachingVerbExtension {
private static final String COMMAND = "baseline";
@Override
public String getCommand() {
return COMMAND;
}
@Override
public String getDescription() {
return "Baselines an existing database at the baselineVersion";
}
@Override
public List<ConfigurationParameter> getConfigurationParameters() {
return List.of(new ConfigurationParameter("baselineVersion",
"Version to tag schema with when executing baseline",
false),
new ConfigurationParameter("baselineDescription",
"Description to tag schema with when executing baseline",
false),
new ConfigurationParameter("createSchemas",
"Whether Flyway should attempt to create the schemas specified in the schemas property",
false));
}
@Override
public String getExample() {
return "flyway baseline -baselineVersion=1.0 -baselineDescription=\"Base Migration\"";
}
@Override
public OperationResult executeVerb(final Configuration configuration) {
final PreparationContext context = PreparationContext.get(configuration, cached);
final NativeConnectorsDatabase database = context.getDatabase();
final CallbackManager callbackManager = new CallbackManager(configuration,
context.getCallbackResources(),
Event::fromId);
final BaselineResult baselineResult = new BaselineResult(VersionPrinter.getVersion(),
database.getDatabaseMetaData().databaseName());
final MigrationVersion baselineVersion = configuration.getBaselineVersion();
final String schemaHistoryName = configuration.getTable();
final boolean schemaHistoryTableExists = database.schemaHistoryTableExists(schemaHistoryName);
if (configuration.isCreateSchemas()) {
final SchemasVerbExtension schemasVerbExtension = new SchemasVerbExtension();
schemasVerbExtension.useCaching();
schemasVerbExtension.executeVerb(configuration);
context.refresh(configuration);
} else {
LOG.warn("""
The configuration option 'createSchemas' is false.
Even though Flyway is configured not to create any schemas, the schema history table still needs a schema to reside in.
You must manually create a schema for the schema history table to reside in.
See\s""" + FlywayDbWebsiteLinks.MIGRATIONS);
}
callbackManager.handleEvent(Event.BEFORE_BASELINE, database, configuration, context.getParsingContext());
try {
if (!schemaHistoryTableExists) {
createBaselineMarker(configuration, database, baselineResult);
callbackManager.handleEvent(Event.AFTER_BASELINE, database, configuration, context.getParsingContext());
return baselineResult;
}
final String schemaHistoryText = database.quote(database.getCurrentSchema()) + "." + database.quote(
schemaHistoryName);
final boolean baselinePresent = context.getSchemaHistoryModel()
.getSchemaHistoryItems()
.stream()
.anyMatch(x -> CoreMigrationType.BASELINE.name().equals(x.getType()));
final boolean onlySchemas = context.getSchemaHistoryModel()
.getSchemaHistoryItems()
.stream()
.allMatch(x -> CoreMigrationType.SCHEMA.name().equals(x.getType()));
if (baselinePresent) {
final String baselineDescription = configuration.getBaselineDescription();
final SchemaHistoryItem baselineMarker = context.getSchemaHistoryModel()
.getSchemaHistoryItems()
.stream()
.filter(x -> CoreMigrationType.BASELINE.name().equals(x.getType()))
.findFirst()
.get();
if (baselineVersion.getVersion().equals(baselineMarker.getVersion()) && baselineDescription.equals(
baselineMarker.getDescription())) {
LOG.info("Schema history table "
+ schemaHistoryText
+ " already initialized with ("
+ baselineVersion
+ ","
+ baselineDescription
+ "). Skipping.");
baselineResult.successfullyBaselined = true;
baselineResult.baselineVersion = baselineVersion.toString();
} else {
throw new FlywayException("Unable to baseline schema history table "
+ schemaHistoryText
+ " with ("
+ baselineVersion
+ ","
+ baselineDescription
+ ") as it has already been baselined with ("
+ baselineMarker.getVersion()
+ ","
+ baselineMarker.getDescription()
+ ")\n"
+ "Need to reset your baseline? Learn more: "
+ FlywayDbWebsiteLinks.getRedirectLinkFromTopic(Topic.REBASELINING));
}
} else {
final boolean schemaPresent = context.getSchemaHistoryModel()
.getSchemaHistoryItems()
.stream()
.anyMatch(x -> CoreMigrationType.SCHEMA.name().equals(x.getType()));
if (schemaPresent && baselineVersion.equals(MigrationVersion.fromVersion("0"))) {
throw new FlywayException("Unable to baseline schema history table "
+ schemaHistoryText
+ " with version 0 as this version was used for schema creation");
}
final boolean nonSyntheticMigrations = context.getSchemaHistoryModel()
.getSchemaHistoryItems()
.stream()
.anyMatch(x -> !CoreMigrationType.fromString(x.getType()).isSynthetic());
if (nonSyntheticMigrations) {
throw new FlywayException("Unable to baseline schema history table "
+ schemaHistoryText
+ " as it already contains migrations\n"
+ "Need to reset your baseline? Learn more: "
+ FlywayDbWebsiteLinks.getRedirectLinkFromTopic(Topic.REBASELINING));
}
if (context.getSchemaHistoryModel().getSchemaHistoryItems().isEmpty()) {
throw new FlywayException("Unable to baseline schema history table "
+ schemaHistoryText
+ " as it already exists, and is empty.\n"
+ "Delete the schema history table, and run baseline again.");
}
if (onlySchemas) {
createBaselineMarker(configuration, database, baselineResult);
} else {
throw new FlywayException("Unable to baseline schema history table "
+ schemaHistoryText
+ " as it already contains migrations.\n"
+ "Delete the schema history table, and run baseline again.\n"
+ "Need to reset your baseline? Learn more: "
+ FlywayDbWebsiteLinks.getRedirectLinkFromTopic(Topic.REBASELINING));
}
}
} catch (final FlywayException e) {
callbackManager.handleEvent(Event.AFTER_BASELINE_ERROR,
database,
configuration,
context.getParsingContext());
baselineResult.successfullyBaselined = false;
throw e;
}
callbackManager.handleEvent(Event.AFTER_BASELINE, database, configuration, context.getParsingContext());
return baselineResult;
}
private static void createBaselineMarker(final Configuration configuration,
final NativeConnectorsDatabase database,
final BaselineResult baselineResult) {
database.createSchemaHistoryTableIfNotExists(configuration);
final String baselineVersion = configuration.getBaselineVersion().getVersion();
database.appendSchemaHistoryItem(SchemaHistoryItem.builder()
.description(configuration.getBaselineDescription())
.installedRank(1)
.type("BASELINE")
.script("<< Flyway Baseline >>")
.installedBy(database.getInstalledBy(configuration))
.version(baselineVersion)
.executionTime(0)
.success(true)
.build(), configuration.getTable());
LOG.info("Successfully baselined schema with version: " + baselineVersion);
baselineResult.successfullyBaselined = true;
baselineResult.baselineVersion = baselineVersion;
}
}