Merge pull request #731 from tdonohue/flyway_oracle_fixes

Flyway fixes for Oracle (DS-2244, DS-2250, DS-2251)
This commit is contained in:
Tim Donohue
2014-11-06 13:19:37 -06:00
5 changed files with 186 additions and 95 deletions

View File

@@ -67,11 +67,11 @@ public class DatabaseUtils
public static void main(String[] argv)
{
// Usage checks
if (argv.length != 1)
if (argv.length < 1)
{
System.out.println("\nDatabase action argument is missing.");
System.out.println("Valid actions include: 'test', 'info', 'migrate', 'migrate-ignored, 'repair' or 'clean'");
System.out.println("Or, type 'database help' for more information.\n");
System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair' or 'clean'");
System.out.println("\nOr, type 'database help' for more information.\n");
System.exit(1);
}
@@ -84,7 +84,7 @@ public class DatabaseUtils
// Get configured DB URL for reporting below
String url = ConfigurationManager.getProperty("db.url");
// Point Flyway API to our database
Flyway flyway = setupFlyway(dataSource);
@@ -92,7 +92,7 @@ public class DatabaseUtils
if(argv[0].equalsIgnoreCase("test"))
{
// Try to connect to the database
System.out.println("\nAttempting to connect to database: ");
System.out.println("\nAttempting to connect to database using these configurations: ");
System.out.println(" - URL: " + url);
System.out.println(" - Driver: " + ConfigurationManager.getProperty("db.driver"));
System.out.println(" - Username: " + ConfigurationManager.getProperty("db.username"));
@@ -124,6 +124,7 @@ public class DatabaseUtils
Connection connection = dataSource.getConnection();
DatabaseMetaData meta = connection.getMetaData();
System.out.println("\nDatabase URL: " + url);
System.out.println("Database Schema: " + getSchemaName(connection));
System.out.println("Database Software: " + meta.getDatabaseProductName() + " version " + meta.getDatabaseProductVersion());
System.out.println("Database Driver: " + meta.getDriverName() + " version " + meta.getDriverVersion());
@@ -140,8 +141,9 @@ public class DatabaseUtils
String dbVersion = determineDBVersion(connection);
if (dbVersion!=null)
{
System.out.println("Your database looks to be compatible with DSpace version " + dbVersion);
System.out.println("All upgrades *after* version " + dbVersion + " will be automatically run during the next migration.");
System.out.println("\nYour database looks to be compatible with DSpace version " + dbVersion);
System.out.println("All upgrades *after* version " + dbVersion + " will be run during the next migration.");
System.out.println("\nIf you'd like to upgrade now, simply run 'dspace database migrate'.");
}
}
connection.close();
@@ -150,23 +152,44 @@ public class DatabaseUtils
else if(argv[0].equalsIgnoreCase("migrate"))
{
System.out.println("\nDatabase URL: " + url);
System.out.println("Migrating database to latest version... (Check logs for details)");
// NOTE: This looks odd, but all we really need to do is ensure the
// DatabaseManager auto-initializes. It'll take care of the migration itself.
// Asking for our DB Name will ensure DatabaseManager.initialize() is called.
DatabaseManager.getDbName();
System.out.println("Done.");
}
// "migrate-ignored" = Manually run any "ignored" Database migrations (if any)
else if(argv[0].equalsIgnoreCase("migrate-ignored"))
{
System.out.println("\nDatabase URL: " + url);
System.out.println("Migrating database to latest version AND running previously \"Ignored\" migrations... (Check logs for details)");
Connection connection = dataSource.getConnection();
// Update the database, but set "outOfOrder=true"
updateDatabase(dataSource, connection, true);
connection.close();
// "migrate" allows for an OPTIONAL second argument:
// - "ignored" = Also run any previously "ignored" migrations during the migration
// - [version] = ONLY run migrations up to a specific DSpace version (ONLY FOR TESTING)
if(argv.length==2)
{
if(argv[1].equalsIgnoreCase("ignored"))
{
System.out.println("Migrating database to latest version AND running previously \"Ignored\" migrations... (Check logs for details)");
Connection connection = dataSource.getConnection();
// Update the database to latest version, but set "outOfOrder=true"
// This will ensure any old migrations in the "ignored" state are now run
updateDatabase(dataSource, connection, null, true);
connection.close();
}
else
{
// Otherwise, we assume "argv[1]" is a valid migration version number
// This is only for testing! Never specify for Production!
System.out.println("Migrating database ONLY to version " + argv[1] + " ... (Check logs for details)");
System.out.println("\nWARNING: It is highly likely you will see errors in your logs when the Metadata");
System.out.println("or Bitstream Format Registry auto-update. This is because you are attempting to");
System.out.println("use an OLD version " + argv[1] + " Database with a newer DSpace API. NEVER do this in a");
System.out.println("PRODUCTION scenario. The resulting old DB is only useful for migration testing.\n");
Connection connection = dataSource.getConnection();
// Update the database, to the version specified.
updateDatabase(dataSource, connection, argv[1], false);
connection.close();
}
}
else
{
System.out.println("Migrating database to latest version... (Check logs for details)");
// NOTE: This looks odd, but all we really need to do is ensure the
// DatabaseManager auto-initializes. It'll take care of the migration itself.
// Asking for our DB Name will ensure DatabaseManager.initialize() is called.
DatabaseManager.getDbName();
}
System.out.println("Done.");
}
// "repair" = Run Flyway repair script
@@ -182,10 +205,10 @@ public class DatabaseUtils
{
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
System.out.println("\nDatabase URL: " + url);
System.out.println("\nIf you continue, ALL DATA AND TABLES IN YOUR DATABASE WILL BE PERMANENTLY DELETED.\n");
System.out.println("There is NO turning back from this action. You should backup your database before continuing.");
System.out.println("\nWARNING: ALL DATA AND TABLES IN YOUR DATABASE WILL BE PERMANENTLY DELETED.\n");
System.out.println("There is NO turning back from this action. Backup your DB before continuing.");
System.out.println("If you are using Oracle, your RECYCLEBIN will also be PURGED.\n");
System.out.print("Are you sure you want to PERMANENTLY DELETE everything from your database? [y/n]: ");
System.out.print("Do you want to PERMANENTLY DELETE everything from your database? [y/n]: ");
String choiceString = input.readLine();
input.close();
@@ -199,13 +222,13 @@ public class DatabaseUtils
else
{
System.out.println("\nUsage: database [action]");
System.out.println("Valid actions include: 'test', 'info', 'migrate', 'migrate-ignored, 'repair' or 'clean'");
System.out.println(" - test = Test database connection is OK");
System.out.println(" - info = Describe basic info about database (type, version, driver, migrations run)");
System.out.println(" - migrate = Migrate the Database to the latest version");
System.out.println(" - migrate-ignored = If any migrations are \"Ignored\", run them AND migrate to the latest version");
System.out.println(" - repair = Attempt to repair any previously failed database migrations");
System.out.println(" - clean = Destroy all data and tables in Database (WARNING there is no going back!)");
System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair' or 'clean'");
System.out.println(" - test = Test database connection is OK");
System.out.println(" - info = Describe basic info about database, including migrations run");
System.out.println(" - migrate = Migrate the Database to the latest version");
System.out.println(" Optionally, specify \"ignored\" to also run \"Ignored\" migrations");
System.out.println(" - repair = Attempt to repair any previously failed database migrations");
System.out.println(" - clean = DESTROY all data and tables in Database (WARNING there is no going back!)");
System.out.println("");
}
@@ -303,8 +326,8 @@ public class DatabaseUtils
protected static synchronized void updateDatabase(DataSource datasource, Connection connection)
throws SQLException
{
// By default, never run migrations out of order
updateDatabase(datasource, connection, false);
// By default, upgrade to the *latest* version and never run migrations out-of-order
updateDatabase(datasource, connection, null, false);
}
/**
@@ -320,13 +343,16 @@ public class DatabaseUtils
* DataSource object (retrieved from DatabaseManager())
* @param connection
* Database connection
* @param targetVersion
* If specified, only migrate the database to a particular *version* of DSpace. This is mostly just useful for testing.
* If null, the database is migrated to the latest version.
* @param outOfOrder
* If true, Flyway will run any lower version migrations that were previously "ignored".
* If false, Flyway will only run new migrations with a higher version number.
* @throws SQLException
* If database cannot be upgraded.
*/
protected static synchronized void updateDatabase(DataSource datasource, Connection connection, boolean outOfOrder)
protected static synchronized void updateDatabase(DataSource datasource, Connection connection, String targetVersion, boolean outOfOrder)
throws SQLException
{
try
@@ -338,6 +364,13 @@ public class DatabaseUtils
// and Flyway ONLY runs migrations that have a higher version number.
flyway.setOutOfOrder(outOfOrder);
// If a target version was specified, tell Flyway to ONLY migrate to that version
// (i.e. all later migrations are left as "pending"). By default we always migrate to latest version.
if(!StringUtils.isBlank(targetVersion))
{
flyway.setTarget(targetVersion);
}
// Does the necessary Flyway table ("schema_version") exist in this database?
// If not, then this is the first time Flyway has run, and we need to initialize
// NOTE: search is case sensitive, as flyway table name is ALWAYS lowercase,
@@ -389,7 +422,7 @@ public class DatabaseUtils
throw new SQLException("Flyway migration error occurred", fe);
}
}
/**
* Clean the existing database, permanently removing all data and tables
* <P>
@@ -585,36 +618,24 @@ public class DatabaseUtils
*/
public static boolean tableExists(Connection connection, String tableName, boolean caseSensitive)
{
// Get the name of the Schema that the DSpace Database is using
// (That way we can search the right schema for this table)
String schema = ConfigurationManager.getProperty("db.schema");
if(StringUtils.isBlank(schema)){
schema = null;
}
boolean exists = false;
ResultSet results = null;
try
{
// Get the name of the Schema that the DSpace Database is using
// (That way we can search the right schema)
String schema = getSchemaName(connection);
// Get information about our database.
DatabaseMetaData meta = connection.getMetaData();
// If this is not a case sensitive search
if(!caseSensitive)
{
// Check how this database stores its table names, etc.
// i.e. lowercase vs uppercase (by default we assume mixed case)
if(meta.storesLowerCaseIdentifiers())
{
schema = (schema == null) ? null : StringUtils.lowerCase(schema);
tableName = StringUtils.lowerCase(tableName);
}
else if(meta.storesUpperCaseIdentifiers())
{
schema = (schema == null) ? null : StringUtils.upperCase(schema);
tableName = StringUtils.upperCase(tableName);
}
// Canonicalize everything to the proper case based on DB type
schema = canonicalize(connection, schema);
tableName = canonicalize(connection, tableName);
}
// Search for a table of the given name in our current schema
@@ -658,36 +679,23 @@ public class DatabaseUtils
*/
public static boolean tableColumnExists(Connection connection, String tableName, String columnName)
{
// Get the name of the Schema that the DSpace Database is using
// (That way we can search the right schema for this table)
String schema = ConfigurationManager.getProperty("db.schema");
if(StringUtils.isBlank(schema)){
schema = null;
}
boolean exists = false;
ResultSet results = null;
try
{
// Get the name of the Schema that the DSpace Database is using
// (That way we can search the right schema)
String schema = getSchemaName(connection);
// Canonicalize everything to the proper case based on DB type
schema = canonicalize(connection, schema);
tableName = canonicalize(connection, tableName);
columnName = canonicalize(connection, columnName);
// Get information about our database.
DatabaseMetaData meta = connection.getMetaData();
// Check how this database stores its table names, etc.
// i.e. lowercase vs uppercase (by default we assume mixed case)
if(meta.storesLowerCaseIdentifiers())
{
schema = (schema == null) ? null : StringUtils.lowerCase(schema);
tableName = StringUtils.lowerCase(tableName);
columnName = StringUtils.lowerCase(columnName);
}
else if(meta.storesUpperCaseIdentifiers())
{
schema = (schema == null) ? null : StringUtils.upperCase(schema);
tableName = StringUtils.upperCase(tableName);
columnName = StringUtils.upperCase(columnName);
}
// Search for a column of that name in the specified table & schema
results = meta.getColumns(null, schema, tableName, columnName);
if (results!=null && results.next())
@@ -727,13 +735,6 @@ public class DatabaseUtils
*/
public static boolean sequenceExists(Connection connection, String sequenceName)
{
// Get the name of the Schema that the DSpace Database is using
// (That way we can search the right schema)
String schema = ConfigurationManager.getProperty("db.schema");
if(StringUtils.isBlank(schema)){
schema = null;
}
boolean exists = false;
PreparedStatement statement = null;
ResultSet results = null;
@@ -742,6 +743,14 @@ public class DatabaseUtils
try
{
// Get the name of the Schema that the DSpace Database is using
// (That way we can search the right schema)
String schema = getSchemaName(connection);
// Canonicalize everything to the proper case based on DB type
schema = canonicalize(connection, schema);
sequenceName = canonicalize(connection, sequenceName);
// Different database types store sequence information in different tables
String dbtype = DatabaseManager.findDbKeyword(connection.getMetaData());
String sequenceSQL = null;
@@ -783,17 +792,17 @@ public class DatabaseUtils
{
// Run the query, passing it our parameters
statement = connection.prepareStatement(sequenceSQL);
statement.setString(1, StringUtils.upperCase(sequenceName));
statement.setString(1, sequenceName);
if(schemaFilter)
{
statement.setString(2, StringUtils.upperCase(schema));
statement.setString(2, schema);
}
results = statement.executeQuery();
// If results are non-zero, then this sequence exists!
if(results!=null && results.next())
if(results!=null && results.next() && results.getInt(1)>0)
{
exists = true;
exists = true;
}
}
}
@@ -852,6 +861,84 @@ public class DatabaseUtils
}
}
/**
* Get the Database Schema Name in use by this Connection, so that it can
* be used to limit queries in other methods (e.g. tableExists()).
* <P>
* For PostgreSQL, schema is simply what is configured in db.schema or "public"
* For Oracle, schema is actually the database *USER* or owner.
*
* @param connection
* Current Database Connection
* @return Schema name as a string, or "null" if cannot be determined or unspecified
*/
public static String getSchemaName(Connection connection)
throws SQLException
{
String schema = null;
DatabaseMetaData meta = connection.getMetaData();
// Determine our DB type
String dbType = DatabaseManager.findDbKeyword(meta);
if(dbType.equals(DatabaseManager.DBMS_POSTGRES))
{
// Get the schema name from "db.schema"
schema = ConfigurationManager.getProperty("db.schema");
// If unspecified, default schema is "public"
if(StringUtils.isBlank(schema)){
schema = "public";
}
}
else if (dbType.equals(DatabaseManager.DBMS_ORACLE))
{
// Schema is actually the user account
// See: http://stackoverflow.com/a/13341390
schema = meta.getUserName();
}
else
schema = null;
return schema;
}
/**
* Return the canonical name for a database identifier based on whether this
* database defaults to storing identifiers in uppercase or lowercase.
*
* @param connection
* Current Database Connection
* @param dbIdentifier
* Identifier to canonicalize (may be a table name, column name, etc)
* @return The canonical name of the identifier.
*/
public static String canonicalize(Connection connection, String dbIdentifier)
throws SQLException
{
// Avoid any null pointers
if(dbIdentifier==null)
return null;
DatabaseMetaData meta = connection.getMetaData();
// Check how this database stores its identifiers, etc.
// i.e. lowercase vs uppercase (by default we assume mixed case)
if(meta.storesLowerCaseIdentifiers())
{
return StringUtils.lowerCase(dbIdentifier);
}
else if(meta.storesUpperCaseIdentifiers())
{
return StringUtils.upperCase(dbIdentifier);
}
else // Otherwise DB doesn't care about case
{
return dbIdentifier;
}
}
/**
* Whether or not to tell Discovery to reindex itself based on the updated
* database.

View File

@@ -5,14 +5,14 @@
*
* http://www.dspace.org/license/
*/
package org.dspace.storage.rdbms.migration;
package org.dspace.storage.rdbms;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.commons.lang3.StringUtils;
import org.dspace.storage.rdbms.DatabaseManager;
import org.apache.commons.lang.StringUtils;
/**
* This Utility class offers utility methods which may be of use to perform
@@ -42,7 +42,10 @@ public class MigrationUtils
// First, in order to drop the appropriate Database constraint, we
// must determine the unique name of the constraint. As constraint
// naming is DB specific, this is dependent on our DB Type
String dbtype = DatabaseManager.getDbKeyword();
DatabaseMetaData meta = connection.getMetaData();
// NOTE: We use "findDbKeyword()" here as it won't cause
// DatabaseManager.initialize() to be called (which in turn re-calls Flyway)
String dbtype = DatabaseManager.findDbKeyword(meta);
String constraintName = null;
String constraintNameSQL = null;
switch(dbtype)

View File

@@ -10,7 +10,7 @@ package org.dspace.storage.rdbms.migration;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import org.dspace.storage.rdbms.migration.MigrationUtils;
import org.dspace.storage.rdbms.MigrationUtils;
import org.flywaydb.core.api.migration.MigrationChecksumProvider;
import org.flywaydb.core.api.migration.jdbc.JdbcMigration;
import org.slf4j.Logger;

View File

@@ -10,7 +10,7 @@ package org.dspace.storage.rdbms.migration;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import org.dspace.storage.rdbms.migration.MigrationUtils;
import org.dspace.storage.rdbms.MigrationUtils;
import org.flywaydb.core.api.migration.MigrationChecksumProvider;
import org.flywaydb.core.api.migration.jdbc.JdbcMigration;
import org.slf4j.Logger;

View File

@@ -10,6 +10,7 @@ package org.dspace.storage.rdbms.migration;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import org.dspace.storage.rdbms.MigrationUtils;
import org.flywaydb.core.api.migration.MigrationChecksumProvider;
import org.flywaydb.core.api.migration.jdbc.JdbcMigration;
import org.slf4j.Logger;