/*
* Copyright 2010-2020 Redgate 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.
*/
package org.flywaydb.core.internal.database.base;
import org.flywaydb.core.api.MigrationType;
import org.flywaydb.core.api.MigrationVersion;
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.internal.exception.FlywayDbUpgradeRequiredException;
import org.flywaydb.core.internal.exception.FlywaySqlException;
import org.flywaydb.core.internal.jdbc.DatabaseType;
import org.flywaydb.core.internal.jdbc.JdbcConnectionFactory;
import org.flywaydb.core.internal.jdbc.JdbcTemplate;
import org.flywaydb.core.internal.license.Edition;
import org.flywaydb.core.internal.license.FlywayEditionUpgradeRequiredException;
import org.flywaydb.core.internal.resource.StringResource;
import org.flywaydb.core.internal.sqlscript.Delimiter;
import org.flywaydb.core.internal.sqlscript.SqlScript;
import org.flywaydb.core.internal.sqlscript.SqlScriptFactory;
import org.flywaydb.core.internal.util.AbbreviationUtils;
import java.io.Closeable;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
Abstraction for database-specific functionality.
/**
* Abstraction for database-specific functionality.
*/
public abstract class Database<C extends Connection> implements Closeable {
private static final Log LOG = LogFactory.getLog(Database.class);
The type of database this is.
/**
* The type of database this is.
*/
protected final DatabaseType databaseType;
The Flyway configuration.
/**
* The Flyway configuration.
*/
protected final Configuration configuration;
The JDBC metadata to use.
/**
* The JDBC metadata to use.
*/
protected final DatabaseMetaData jdbcMetaData;
The main JDBC connection, without any wrapping.
/**
* The main JDBC connection, without any wrapping.
*/
protected final java.sql.Connection rawMainJdbcConnection;
The main connection to use.
/**
* The main connection to use.
*/
private C mainConnection;
The connection to use for migrations.
/**
* The connection to use for migrations.
*/
private C migrationConnection;
protected final JdbcConnectionFactory jdbcConnectionFactory;
The major.minor version of the database.
/**
* The major.minor version of the database.
*/
private MigrationVersion version;
The user who applied the migrations.
/**
* The user who applied the migrations.
*/
private String installedBy;
protected JdbcTemplate jdbcTemplate;
Creates a new Database instance with this JdbcTemplate.
Params: - configuration – The Flyway configuration.
/**
* Creates a new Database instance with this JdbcTemplate.
*
* @param configuration The Flyway configuration.
*/
public Database(Configuration configuration, JdbcConnectionFactory jdbcConnectionFactory
) {
this.databaseType = jdbcConnectionFactory.getDatabaseType();
this.configuration = configuration;
this.rawMainJdbcConnection = jdbcConnectionFactory.openConnection();
try {
this.jdbcMetaData = rawMainJdbcConnection.getMetaData();
} catch (SQLException e) {
throw new FlywaySqlException("Unable to get metadata for connection", e);
}
this.jdbcTemplate = new JdbcTemplate(rawMainJdbcConnection, databaseType);
this.jdbcConnectionFactory = jdbcConnectionFactory;
}
Retrieves a Flyway Connection for this JDBC connection.
Params: - connection – The JDBC connection to wrap.
Returns: The Flyway Connection.
/**
* Retrieves a Flyway Connection for this JDBC connection.
*
* @param connection The JDBC connection to wrap.
* @return The Flyway Connection.
*/
private C getConnection(java.sql.Connection connection) {
return doGetConnection(connection);
}
Retrieves a Flyway Connection for this JDBC connection.
Params: - connection – The JDBC connection to wrap.
Returns: The Flyway Connection.
/**
* Retrieves a Flyway Connection for this JDBC connection.
*
* @param connection The JDBC connection to wrap.
* @return The Flyway Connection.
*/
protected abstract C doGetConnection(java.sql.Connection connection);
Ensures Flyway supports this version of this database.
/**
* Ensures Flyway supports this version of this database.
*/
public abstract void ensureSupported();
Returns: The major.minor version of the database.
/**
* @return The major.minor version of the database.
*/
public final MigrationVersion getVersion() {
if (version == null) {
version = determineVersion();
}
return version;
}
protected final void ensureDatabaseIsRecentEnough(String oldestSupportedVersion) {
if (!getVersion().isAtLeast(oldestSupportedVersion)) {
throw new FlywayDbUpgradeRequiredException(databaseType, computeVersionDisplayName(getVersion()),
computeVersionDisplayName(MigrationVersion.fromVersion(oldestSupportedVersion)));
}
}
Ensures this database it at least at recent as this version otherwise suggest upgrade to this higher edition of
Flyway.
Params: - oldestSupportedVersionInThisEdition – The oldest supported version of the database by this edition of Flyway.
- editionWhereStillSupported – The edition of Flyway that still supports this version of the database,
in case it's too old.
/**
* Ensures this database it at least at recent as this version otherwise suggest upgrade to this higher edition of
* Flyway.
*
* @param oldestSupportedVersionInThisEdition The oldest supported version of the database by this edition of Flyway.
* @param editionWhereStillSupported The edition of Flyway that still supports this version of the database,
* in case it's too old.
*/
protected final void ensureDatabaseNotOlderThanOtherwiseRecommendUpgradeToFlywayEdition(String oldestSupportedVersionInThisEdition,
Edition editionWhereStillSupported) {
if (!getVersion().isAtLeast(oldestSupportedVersionInThisEdition)) {
throw new FlywayEditionUpgradeRequiredException(
editionWhereStillSupported,
databaseType,
computeVersionDisplayName(getVersion()));
}
}
protected final void recommendFlywayUpgradeIfNecessary(String newestSupportedVersion) {
if (getVersion().isNewerThan(newestSupportedVersion)) {
recommendFlywayUpgrade(newestSupportedVersion);
}
}
protected final void recommendFlywayUpgradeIfNecessaryForMajorVersion(String newestSupportedVersion) {
if (getVersion().isMajorNewerThan(newestSupportedVersion)) {
recommendFlywayUpgrade(newestSupportedVersion);
}
}
private void recommendFlywayUpgrade(String newestSupportedVersion) {
String message = "Flyway upgrade recommended: " + databaseType + " " + computeVersionDisplayName(getVersion())
+ " is newer than this version of Flyway and support has not been tested. "
+ "The latest supported version of " + databaseType + " is " + newestSupportedVersion + ".";
LOG.warn(message);
}
Compute the user-friendly display name for this database version.
Returns: The user-friendly display name.
/**
* Compute the user-friendly display name for this database version.
*
* @return The user-friendly display name.
*/
protected String computeVersionDisplayName(MigrationVersion version) {
return version.getVersion();
}
Returns: The default delimiter for this database.
/**
* @return The default delimiter for this database.
*/
public Delimiter getDefaultDelimiter() {
return Delimiter.SEMICOLON;
}
Returns: The current database user.
/**
* @return The current database user.
*/
public final String getCurrentUser() {
try {
return doGetCurrentUser();
} catch (SQLException e) {
throw new FlywaySqlException("Error retrieving the database user", e);
}
}
protected String doGetCurrentUser() throws SQLException {
return jdbcMetaData.getUserName();
}
Checks whether DDL transactions are supported by this database.
Returns: true
if DDL transactions are supported, false
if not.
/**
* Checks whether DDL transactions are supported by this database.
*
* @return {@code true} if DDL transactions are supported, {@code false} if not.
*/
public abstract boolean supportsDdlTransactions();
Whether to add the baseline marker directly as part of the create table statement for this database.
/**
* Whether to add the baseline marker directly as part of the create table statement for this database.
*/
public boolean useDirectBaseline() {
return false;
}
Returns: true
if this database supports changing a connection's current schema. false if not
.
/**
* @return {@code true} if this database supports changing a connection's current schema. {@code false if not}.
*/
public abstract boolean supportsChangingCurrentSchema();
Returns: The representation of the value true
in a boolean column.
/**
* @return The representation of the value {@code true} in a boolean column.
*/
public abstract String getBooleanTrue();
Returns: The representation of the value false
in a boolean column.
/**
* @return The representation of the value {@code false} in a boolean column.
*/
public abstract String getBooleanFalse();
Quote these identifiers for use in sql queries. Multiple identifiers will be quoted and separated by a dot.
Params: - identifiers – The identifiers to quote.
Returns: The fully qualified quoted identifiers.
/**
* Quote these identifiers for use in sql queries. Multiple identifiers will be quoted and separated by a dot.
*
* @param identifiers The identifiers to quote.
* @return The fully qualified quoted identifiers.
*/
public final String quote(String... identifiers) {
StringBuilder result = new StringBuilder();
boolean first = true;
for (String identifier : identifiers) {
if (!first) {
result.append(".");
}
first = false;
result.append(doQuote(identifier));
}
return result.toString();
}
Quote this identifier for use in sql queries.
Params: - identifier – The identifier to quote.
Returns: The fully qualified quoted identifier.
/**
* Quote this identifier for use in sql queries.
*
* @param identifier The identifier to quote.
* @return The fully qualified quoted identifier.
*/
protected abstract String doQuote(String identifier);
Returns: true
if this database use a catalog to represent a schema. false
if a schema is simply a schema.
/**
* @return {@code true} if this database use a catalog to represent a schema. {@code false} if a schema is simply a schema.
*/
public abstract boolean catalogIsSchema();
Returns: Whether to only use a single connection for both schema history table management and applying migrations.
/**
* @return Whether to only use a single connection for both schema history table management and applying migrations.
*/
public boolean useSingleConnection() {
return false;
}
public DatabaseMetaData getJdbcMetaData() {
return jdbcMetaData;
}
Returns: The main connection, used to manipulate the schema history.
/**
* @return The main connection, used to manipulate the schema history.
*/
public final C getMainConnection() {
if (mainConnection == null) {
this.mainConnection = getConnection(rawMainJdbcConnection);
}
return mainConnection;
}
Returns: The migration connection, used to apply migrations.
/**
* @return The migration connection, used to apply migrations.
*/
public final C getMigrationConnection() {
if (migrationConnection == null) {
if (useSingleConnection()) {
this.migrationConnection = getMainConnection();
} else {
this.migrationConnection = getConnection(jdbcConnectionFactory.openConnection());
}
}
return migrationConnection;
}
Returns: The major and minor version of the database.
/**
* @return The major and minor version of the database.
*/
protected MigrationVersion determineVersion() {
try {
return MigrationVersion.fromVersion(jdbcMetaData.getDatabaseMajorVersion() + "." + jdbcMetaData.getDatabaseMinorVersion());
} catch (SQLException e) {
throw new FlywaySqlException("Unable to determine the major version of the database", e);
}
}
Retrieves the script used to create the schema history table.
Params: - table – The table to create.
- baseline – Whether to include the creation of a baseline marker.
Returns: The script.
/**
* Retrieves the script used to create the schema history table.
*
* @param table The table to create.
* @param baseline Whether to include the creation of a baseline marker.
* @return The script.
*/
public final SqlScript getCreateScript(SqlScriptFactory sqlScriptFactory, Table table, boolean baseline) {
return sqlScriptFactory.createSqlScript(new StringResource(getRawCreateScript(table, baseline)), false, null);
}
public abstract String getRawCreateScript(Table table, boolean baseline);
public String getInsertStatement(Table table) {
return "INSERT INTO " + table
+ " (" + quote("installed_rank")
+ ", " + quote("version")
+ ", " + quote("description")
+ ", " + quote("type")
+ ", " + quote("script")
+ ", " + quote("checksum")
+ ", " + quote("installed_by")
+ ", " + quote("execution_time")
+ ", " + quote("success")
+ ")"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
}
public final String getBaselineStatement(Table table) {
return String.format(getInsertStatement(table).replace("?", "%s"),
1,
"'" + configuration.getBaselineVersion() + "'",
"'" + AbbreviationUtils.abbreviateDescription(configuration.getBaselineDescription()) + "'",
"'" + MigrationType.BASELINE + "'",
"'" + AbbreviationUtils.abbreviateScript(configuration.getBaselineDescription()) + "'",
"NULL",
"'" + installedBy + "'",
0,
getBooleanTrue()
);
}
public String getSelectStatement(Table table) {
return "SELECT " + quote("installed_rank")
+ "," + quote("version")
+ "," + quote("description")
+ "," + quote("type")
+ "," + quote("script")
+ "," + quote("checksum")
+ "," + quote("installed_on")
+ "," + quote("installed_by")
+ "," + quote("execution_time")
+ "," + quote("success")
+ " FROM " + table
+ " WHERE " + quote("installed_rank") + " > ?"
+ " ORDER BY " + quote("installed_rank");
}
public final String getInstalledBy() {
if (installedBy == null) {
installedBy = configuration.getInstalledBy() == null ? getCurrentUser() : configuration.getInstalledBy();
}
return installedBy;
}
public void close() {
if (!useSingleConnection() && migrationConnection != null) {
migrationConnection.close();
}
if (mainConnection != null) {
mainConnection.close();
}
}
public DatabaseType getDatabaseType() {
return databaseType;
}
Whether the database supports an empty string as a migration description.
/**
* Whether the database supports an empty string as a migration description.
*/
public boolean supportsEmptyMigrationDescription() { return true; }
Whether the database supports multi-statement transactions
/**
* Whether the database supports multi-statement transactions
*/
public boolean supportsMultiStatementTransactions() { return true; }
Cleans all the objects in this database that need to be done prior to cleaning schemas.
/**
* Cleans all the objects in this database that need to be done prior to cleaning schemas.
*/
public void cleanPreSchemas() {
try {
doCleanPreSchemas();
} catch (SQLException e) {
throw new FlywaySqlException("Unable to clean database " + this, e);
}
}
Cleans all the objects in this database that need to be done prior to cleaning schemas.
Throws: - SQLException – when the clean failed.
/**
* Cleans all the objects in this database that need to be done prior to cleaning schemas.
*
* @throws SQLException when the clean failed.
*/
protected void doCleanPreSchemas() throws SQLException {
// Default is to do nothing.
}
Cleans all the objects in this database that need to be done after cleaning schemas.
/**
* Cleans all the objects in this database that need to be done after cleaning schemas.
*/
public void cleanPostSchemas() {
try {
doCleanPostSchemas();
} catch (SQLException e) {
throw new FlywaySqlException("Unable to clean schema " + this, e);
}
}
Cleans all the objects in this database that need to be done after cleaning schemas.
Throws: - SQLException – when the clean failed.
/**
* Cleans all the objects in this database that need to be done after cleaning schemas.
*
* @throws SQLException when the clean failed.
*/
protected void doCleanPostSchemas() throws SQLException {
// Default is to do nothing
}
}