NativeConnectorsDatabase.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.nc;
import java.util.List;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.flywaydb.core.api.configuration.Configuration;
import org.flywaydb.core.api.logging.Log;
import org.flywaydb.core.api.output.CleanResult;
import org.flywaydb.core.internal.nc.schemahistory.SchemaHistoryItem;
import org.flywaydb.core.internal.nc.schemahistory.SchemaHistoryModel;
import org.flywaydb.core.extensibility.Plugin;
import org.flywaydb.core.internal.configuration.models.ResolvedEnvironment;
import org.flywaydb.core.internal.parser.Parser;
import org.flywaydb.core.internal.parser.ParsingContext;
import org.flywaydb.core.internal.util.StopWatch;
import org.flywaydb.core.internal.util.TimeFormat;
/**
* Interface to define new experimental database plugins.
*/
public sealed interface NativeConnectorsDatabase<T> extends Plugin, AutoCloseable permits
AbstractNativeConnectorsDatabase {
Log LOG = org.flywaydb.core.api.logging.LogFactory.getLog(NativeConnectorsDatabase.class);
String APPLICATION_NAME = "Flyway by Redgate";
/**
* Check for if this database type supports the provided URL/Connection String.
* @param url URL or Connection String for the database being connected to. This will be obtained from a resolved environment.
* @return A {@link DatabaseSupport] object containing confirmation that the database type is supported
*/
DatabaseSupport supportsUrl(String url);
default boolean canCreateJdbcDataSource() {
return false;
}
default boolean isOnByDefault(final Configuration configuration) {
return false;
}
List<String> supportedVerbs();
boolean supportsTransactions();
/**
* To initialize the connection to the database. This function will vary between the connection types.
* For example, a JDBC connection will establish a connection object.
* However, an API connection may require this function to create an authentication object instead.
* @param environment The resolved environment to connect to.
*/
void initialize(ResolvedEnvironment environment, Configuration configuration);
void doExecute(T executionUnit, final boolean outputQueryResults);
/**
* Gets connection important metadata from the database.
* This metadata will be used primarily to confirm if the current database connection is right for the database variant connected to.
* This is based off existing Flyway logic.
* @return A {@link MetaData} object containing connection important metadata
*/
MetaData getDatabaseMetaData();
/**
* Creates a schema history table against the configured database.
* The implementation details will be determined per database but will adhere to a Flyway standard.
* @param tableName The name of the schema history table to create.
*/
void createSchemaHistoryTable(Configuration configuration);
boolean schemaHistoryTableExists(String tableName);
/**
* Get a model representation of the schema history table and its content.
* @param tableName The name of the schema history table.
* @return A model representation of the schema history table and its content.
*/
SchemaHistoryModel getSchemaHistoryModel(String tableName);
void appendSchemaHistoryItem(SchemaHistoryItem item, String tableName);
/**
* Quotes this identifier for use in SQL queries.
*/
default String doQuote(final String identifier) {
return getOpenQuote() + identifier + getCloseQuote();
}
default String getOpenQuote() {
return "\"";
}
default String getCloseQuote() {
return "\"";
}
/**
* Quotes these identifiers for use in SQL queries. Multiple identifiers will be quoted and separated by a dot.
*/
default String quote(String... identifiers) {
final StringBuilder result = new StringBuilder();
boolean first = true;
for (final String identifier : identifiers) {
if (!first) {
result.append(".");
}
first = false;
result.append(doQuote(identifier));
}
return result.toString();
}
String getCurrentSchema();
/**
* Checks if all schemas are empty.
* @return True if all schemas are empty, false otherwise.
*/
default Boolean allSchemasEmpty(String[] schemas) {
for (String schema: schemas) {
if (!isSchemaEmpty(schema)) {
return false;
}
}
return true;
}
boolean isSchemaEmpty(String schema);
boolean isSchemaExists(String schema);
void createSchemas(String... schemas);
String getDatabaseType();
BiFunction<Configuration, ParsingContext, Parser> getParser();
void addToBatch(String executionUnit);
/**
* Executes the current batch against the database.
*/
void doExecuteBatch();
int getBatchSize();
String getCurrentUser();
void startTransaction();
void commitTransaction();
void rollbackTransaction();
default void doClean(final List<String> schemas, final List<String> schemasToDrop, final CleanResult cleanResult) {
final StopWatch watch = new StopWatch();
for (final String schema : schemas) {
try {
watch.start();
doCleanSchema(schema);
watch.stop();
LOG.info(String.format("Successfully cleaned schema %s (execution time %s)",
quote(schema),
TimeFormat.format(watch.getTotalTimeMillis())));
if (!schemasToDrop.contains(schema)) {
cleanResult.schemasCleaned.add(schema);
}
} catch (final Exception ignored) {
}
}
for (final String schema : schemasToDrop) {
try {
watch.start();
doDropSchema(schema);
watch.stop();
LOG.info(String.format("Successfully dropped schema %s (execution time %s)",
quote(schema),
TimeFormat.format(watch.getTotalTimeMillis())));
cleanResult.schemasDropped.add(schema);
} catch (final Exception e) {
LOG.debug(e.getMessage());
LOG.warn("Unable to drop schema " + schema + ". It was cleaned instead.");
cleanResult.schemasCleaned.add(schema);
}
}
}
void doCleanSchema(String schema);
void doDropSchema(String schema);
default String getInstalledBy(final Configuration configuration) {
final String installedBy = configuration.getInstalledBy();
return installedBy == null ? getCurrentUser() : installedBy;
}
default void createSchemaHistoryTableIfNotExists(final Configuration configuration) {
if (!schemaHistoryTableExists(configuration.getTable())) {
LOG.info("Creating Schema History table "
+ quote(getCurrentSchema(), configuration.getTable())
+ " ...");
createSchemaHistoryTable(configuration);
}
}
void removeFailedSchemaHistoryItems(final String tableName);
void updateSchemaHistoryItem(SchemaHistoryItem item, final String tableName);
default String redactUrl(final String url) {
String redactedUrl = url;
for (final Pattern pattern : getUrlRedactionPatterns()) {
final Matcher matcher = pattern.matcher(url);
if (matcher.find()) {
final String password = matcher.group(1);
redactedUrl = redactedUrl.replace(password, "********");
}
}
return redactedUrl;
}
default Pattern[] getUrlRedactionPatterns() {
return new Pattern[] {Pattern.compile("password=([^;&]*).*", Pattern.CASE_INSENSITIVE),
Pattern.compile("(?:jdbc:)?[^:]+://[^:]+:([^@]+)@.*", Pattern.CASE_INSENSITIVE)};
}
boolean isClosed();
}