/*
* Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
* Copyright [2016-2017] EMBL-European Bioinformatics Institute
*
* 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.ensembl.healthcheck.testcase.generic;
import static org.ensembl.healthcheck.util.CollectionUtils.createArrayList;
import java.io.File;
import java.io.FileNotFoundException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.ensembl.healthcheck.DatabaseRegistry;
import org.ensembl.healthcheck.DatabaseRegistryEntry;
import org.ensembl.healthcheck.DatabaseType;
import org.ensembl.healthcheck.ReportManager;
import org.ensembl.healthcheck.TestRunner;
import org.ensembl.healthcheck.testcase.MultiDatabaseTestCase;
import org.ensembl.healthcheck.util.ConnectionBasedSqlTemplateImpl;
import org.ensembl.healthcheck.util.DBUtils;
import org.ensembl.healthcheck.util.DefaultObjectRowMapper;
import org.ensembl.healthcheck.util.PoorLruMap;
import org.ensembl.healthcheck.util.RowMapper;
/**
* A re-implementation of the {@link CompareSchema} health-check code but the
* intention is to allow for post modifications in the various extension classes
* to support all types of schema comparisons.
*
* It has several ways of deciding which schema to use as the "master" to
* compare all the others against:
* <p>
* <ol>
* <li>If the property getDefinitionFileKey() in database.properties
* exists, the table.sql file it points to</li>
* <li>If this is not present, the schema named by the property
* {@link #getMasterSchemaKey()} is used</li>
* <li>If neither of the above properties are present, an arbitrary first schema
* is used as the master</li>
* </ol>
*
* @author ayates
*/
public abstract class AbstractCompareSchema extends MultiDatabaseTestCase {
private static final int MAX_CACHE_SIZE = 3;
private boolean usingTemporaryDatabase;
private String masterShortName;
/* comparison flags */
private static final int COMPARE_LEFT = 0;
private static final int COMPARE_RIGHT = 1;
private static final int COMPARE_BOTH = 2;
/**
* An enum to contain the types of tests we allow a compare schema to perform.
* All should be self-explanatory.
*
*/
public static enum TestTypes {
IGNORE_AUTOINCREMENT_OPTION, AVG_ROW_LENGTH, MAX_ROWS, CHARSET, ENGINE
};
private Set<TestTypes> testTypes = new HashSet<TestTypes>();
private Map<String, Set<String>> views = new PoorLruMap<String, Set<String>>(
MAX_CACHE_SIZE);
private Map<String, Map<String, Set<Column>>> columns = new PoorLruMap<String, Map<String, Set<Column>>>(
MAX_CACHE_SIZE);
private Map<String, String> createTables = new PoorLruMap<String, String>(
MAX_CACHE_SIZE);
private Map<String, Set<String>> tables = new PoorLruMap<String, Set<String>>(
MAX_CACHE_SIZE);
public AbstractCompareSchema() {
addDescription();
addResponsible();
addTestTypes();
}
protected void addDescription() {
setDescription("Compare two databases (table names, column names " +
"and types, and indexes. Note that, in the case of core " +
"databases, there are occasionally tables (such as runnable, " +
"job, job_status etc) that are still present after the " +
"genebuild handover because pipelines are still running. The " +
"genebuilders are responsible for deleting these before the release.");
}
/**
* Override to set the test's responsible teams
*/
protected abstract void addResponsible();
/**
* Override to add the various types of tests you wish to apply. See
* {@link TestTypes} for more information
*/
protected abstract void addTestTypes();
/**
* Defaults to true which means we will stop checking schemas if the master
* and target schemas do not contain the same tables
*/
protected boolean skipCheckingIfTablesAreUnequal() {
return true;
}
public abstract void types();
/**
* Should return the property key used to locate a target schema file
*/
protected abstract String getDefinitionFileKey();
/**
* Should return the property key used to locate a target master schema
*/
protected abstract String getMasterSchemaKey();
/**
* Returns the Set which controls the extra tests run
*/
public Set<TestTypes> getTestTypes() {
return testTypes;
}
/**
* States if we want to run a paritcular type of test
*/
public boolean applyTest(TestTypes type) {
return testTypes.contains(type);
}
public boolean isUsingTemporaryDatabase() {
return usingTemporaryDatabase;
}
public void setUsingTemporaryDatabase(boolean usingTemporaryDatabase) {
this.usingTemporaryDatabase = usingTemporaryDatabase;
}
public String getMasterShortName() {
return masterShortName;
}
public void setMasterShortName(String masterShortName) {
this.masterShortName = masterShortName;
}
/**
* Compare each database with the master.
*
* @param dbr
* The database registry containing all the specified databases.
* @return true if the test passed.
*/
public boolean run(DatabaseRegistry dbr) {
boolean result = true;
String testName = getClass().getSimpleName();
Connection masterCon = null;
String definitionFile = null;
String masterSchema = null;
String definitionFileKey = getDefinitionFileKey();
String masterSchemaKey = getMasterSchemaKey();
// Make sure that something is tested at some point. Don't allow a
// database to weasel through this healthcheck without having been
// checked.
boolean somethingWasCompared = false;
DatabaseRegistryEntry[] databases = dbr.getAll();
// Check for definition file
// Ensure it also points to an existing file
definitionFile = System.getProperty(definitionFileKey);
if (definitionFile != null && !definitionFile.isEmpty()) {
logger.info("About to import " + definitionFile);
try {
masterCon = importSchema(definitionFile);
logger.info("Got connection to " + DBUtils.getShortDatabaseName(masterCon));
setUsingTemporaryDatabase(definitionFile != null && !definitionFile.isEmpty());
} catch (FileNotFoundException e) {
logger.warning(testName + ": Could not import schema file " + definitionFile + ". Check your settings in " + TestRunner.getPropertiesFile());
}
}
// Check master schema from configuration
// Skip if temporary database already created
if (masterSchemaKey != null && !masterSchemaKey.isEmpty() && !isUsingTemporaryDatabase()) {
masterSchema = System.getProperty(masterSchemaKey);
if (masterSchema != null) {
// add the named master schema to the master registry so that it can be
// accessed
List<String> regexps = new ArrayList<String>();
regexps.add(masterSchema);
DatabaseRegistry masterDBR = new DatabaseRegistry(regexps, null, null, false);
DatabaseRegistryEntry masterDBRE = masterDBR.getByExactName(masterSchema);
if (masterDBRE == null) {
ReportManager.problem(this, (Connection) null, "Could no connect to database " + masterSchema + ". Check your settings in " + TestRunner.getPropertiesFile());
return false;
}
dbr.add(masterDBRE);
masterCon = getSchemaConnection(masterSchema);
logger.info("Opened connection to master schema in " + DBUtils.getShortDatabaseName(masterCon) + " using " + masterSchema);
} else {
ReportManager.problem(this, (Connection) null, "Could no connect to database " + masterSchema + ". Check your settings in " + TestRunner.getPropertiesFile());
return false;
}
}
setMasterShortName(DBUtils.getShortDatabaseName(masterCon));
for (DatabaseRegistryEntry dbre : databases) {
DatabaseType type = dbre.getType();
if (appliesToType(type)) {
Connection checkCon = dbre.getConnection();
String checkShortName = DBUtils.getShortDatabaseName(checkCon);
if (checkCon != masterCon) {
logger.info("Comparing " + checkShortName + " with " + getMasterShortName());
// check that both schemas have the same tables
somethingWasCompared = true;
int directionFlag = COMPARE_BOTH;
boolean ignoreBackupTables = false;
if (type == DatabaseType.SANGER_VEGA) {
directionFlag = COMPARE_RIGHT;
ignoreBackupTables = true;
}
// for sangervega, ignore backup tables. If not the same, this
// method will generate a report
try {
if (!compareTableEquality(masterCon, dbre, ignoreBackupTables, directionFlag)) {
result = false;
if(skipCheckingIfTablesAreUnequal()) {
String msg;
if(searchForTemporaryTables(checkCon)) {
msg = String.format(
"Table name discrepancy detected but temporary tables " +
"were found in the schema '%s'. Try running " +
"ensembl/misc-scripts/db/cleanup_tmp_tables.pl",
checkShortName
);
}
else {
msg = "Table name discrepancy detected, skipping rest of checks";
}
ReportManager.problem(this, checkCon, msg);
continue;
}
else {
ReportManager.problem(this, checkCon,
"Table name discrepancy detected but continuing with table checks");
}
}
for (String table : getTableNames(masterCon)) {
result &= compareTable(masterCon, dbre, table);
}
}
catch (SQLException e) {
logger.severe(e.getMessage());
}
} // if checkCon != masterCon
} // if appliesToType
} // for database
// avoid leaving temporary DBs lying around if something bad happens
if (isUsingTemporaryDatabase() && masterCon != null) {
// double-check to make sure the DB we're going to remove is a temp one
String dbName = DBUtils.getShortDatabaseName(masterCon);
if (dbName.indexOf("_temp_") > -1) {
removeDatabase(masterCon);
logger.info("Removed " + DBUtils.getShortDatabaseName(masterCon));
}
}
if (!somethingWasCompared) {
ReportManager.problem(this, (Connection) null, "No schema was compared. Please make sure you have configured this test correctly.");
return false;
}
if (result) {
ReportManager.correct(this, masterCon, "CompareSchema was run, no issues found");
}
return result;
}
/**
* Currently delegates onto
* {@link #compareTablesInSchema(Connection, Connection, boolean, int)} but
* can be over-ridden if required.
*/
protected boolean compareTableEquality(Connection masterConn, DatabaseRegistryEntry target, boolean ignoreBackupTables, int directionFlag) {
Connection targetConn = target.getConnection();
return compareTablesInSchema(targetConn, masterConn, ignoreBackupTables, directionFlag);
}
// -------------------------------------------------------------------------
/**
* Compare two schemas to see if they have the same tables. The comparison
* is done in both directions, so will return false if a table exists in
* schema1 but not in schema2, <em>or</em> if a table exists in schema2 but
* not in schema2.
*
* @param schema1
* The first schema to compare.
* @param schema2
* The second schema to compare.
* @return true if all tables in schema1 exist in schema2, and vice-versa.
*/
public boolean compareTablesInSchema(Connection schema1, Connection schema2) {
return compareTablesInSchema(schema1, schema2, false, COMPARE_BOTH);
}
/**
* Compare two schemas to see if they have the same tables. The comparison
* can be done in in one direction or both directions.
*
* @param schema1
* The first schema to compare.
* @param schema2
* The second schema to compare.
* @param ignoreBackupTables
* Should backup tables be excluded form this check?
* @param directionFlag
* The direction to perform comparison in, either
* EnsTestCase.COMPARE_RIGHT, EnsTestCase.COMPARE_LEFT or
* EnsTestCase.COMPARE_BOTH
* @return for left comparison: all tables in schema1 exist in schema2 for
* right comparison: all tables in schema1 exist in schema2 for
* both: if all tables in schema1 exist in schema2, and vice-versa
*/
public boolean compareTablesInSchema(Connection schema1,
Connection schema2, boolean ignoreBackupTables, int directionFlag) {
boolean result = true;
if (directionFlag == COMPARE_RIGHT || directionFlag == COMPARE_BOTH) {
// perform right compare if required
//
result = compareTablesInSchema(schema2, schema1, ignoreBackupTables, COMPARE_LEFT);
}
if (directionFlag == COMPARE_LEFT || directionFlag == COMPARE_BOTH) {
// perform left compare if required
//
String name1 = getDbNameForMsg(schema1);
String name2 = getDbNameForMsg(schema2);
// check each table in turn
String[] tables = getTableNames(schema1);
for (int i = 0; i < tables.length; i++) {
String table = tables[i];
if (!ignoreBackupTables || !table.contains(backupIdentifier)) {
if (!DBUtils.checkTableExists(schema2, table)) {
ReportManager.problem(this, getConnectionForReportManager(schema1), "Table " + table + " exists in " + name1 + " but not in " + name2);
result = false;
}
}
}
}
return result;
}
/**
* A healthcheck has to report the same error message for the same
* problem. The error can't be manually overridden by annotating it
* as ok on the admin site.
*
* When a temporary database is used, it gets a unique name every
* time. This makes the same error produce slightly different error
* messages every time. If the user annotates it as ok, it will appear
* as a new error the next time, because the name of the temporary
* database has changed.
*
* Therefore the messages the test produces are rid of the name of the
* master database, if the master is a temporary database.
*/
protected String getMasterNameForMsg(Connection master) {
String masterNameForMsg;
if (isUsingTemporaryDatabase()) {
File f = new File(System.getProperty(getDefinitionFileKey()));
masterNameForMsg = f.getAbsolutePath();
} else {
masterNameForMsg = DBUtils.getShortDatabaseName(master);
}
return masterNameForMsg;
}
protected String getDbNameForMsg(Connection db) {
String dbShortName = DBUtils.getShortDatabaseName(db);
if (dbShortName.equals(getMasterShortName())) {
return getMasterNameForMsg(db);
}
return dbShortName;
}
/**
*
* See comment for {@link #getMasterNameForMsg(Connection)}
*
*/
protected Connection getMasterForReportManager(Connection master) {
return (isUsingTemporaryDatabase()) ? null : master;
}
protected Connection getConnectionForReportManager(Connection db) {
String dbShortName = DBUtils.getShortDatabaseName(db);
if (dbShortName.equals(getMasterShortName())) {
return getMasterForReportManager(db);
}
return db;
}
/**
* Performs tests on the equivalent sets of columns in the given table, the
* indexes on the given table, the type of the table (view or not) as well as
* other creation parameters e.g. <em>AVG_ROW_LENGTH</em>. Many of these are
* controlled by the {@link TestTypes} enum.
*
* To help with speed we first compare a <em>SHOW CREATE TABLE</em> with some
* post modification.
*/
protected boolean compareTable(Connection master, DatabaseRegistryEntry targetDbre,
String table) throws SQLException {
Connection target = targetDbre.getConnection();
// If either schema did not contain this table then just return early
// because we will have warned about it earlier on. This could only happen
// if the skipCheckingIfTablesAreUnequal() method was returning false
if( !getTables(master).contains(table) || !getTables(target).contains(table) ) {
return false;
}
// - test show create table as it's the fastest ... apparently
if (getCreateTable(master, table).equals(getCreateTable(target, table))) {
return true;
}
boolean okay = true;
String targetName = DBUtils.getShortDatabaseName(target);
String masterNameForMsg = getMasterNameForMsg(master);
Connection masterForReportManager = getMasterForReportManager(master);
// Compare table structure
Set<Column> masterMinusTargetColumns = getColumns(master, table);
masterMinusTargetColumns.removeAll(getColumns(target, table));
Set<Column> columnIssuesCalled = new HashSet<Column>();
// report that the target is missing columns deinfod in the master
if (!masterMinusTargetColumns.isEmpty()) {
for (Column col : masterMinusTargetColumns) {
String message = String.format(
"`%s` `%s` does not have the same definition as `%s`. Column `%s` was different. Check table structures",
targetName, table, masterNameForMsg, col);
ReportManager.problem(this, target, message);
columnIssuesCalled.add(col);
}
okay = false;
}
Set<Column> targetMinusMasterColumns = getColumns(target, table);
Set<Column> localMaster = getColumns(master, table);
localMaster.removeAll(columnIssuesCalled);
targetMinusMasterColumns.removeAll(localMaster);
// report that a target table columns which the master lacks
if (!targetMinusMasterColumns.isEmpty()) {
for (Column col : targetMinusMasterColumns) {
if (columnIssuesCalled.contains(col)) {
continue;
}
String message = String.format(
"`%s` `%s` does not have the same definition as `%s`. Column `%s` was different. Check table structures",
masterNameForMsg, table, targetName, col);
ReportManager.problem(this, masterForReportManager, message);
}
okay = false;
}
boolean masterView = getViews(master).contains(table);
boolean targetView = getViews(target).contains(table);
if (masterView != targetView) {
String masterType = (masterView) ? "VIEW" : "TABLE";
String targetType = (targetView) ? "VIEW" : "TABLE";
String msg = String.format("`%s` is a %s in `%s` but a %s in `%s`",
table, masterType, masterNameForMsg, targetType, targetName);
ReportManager.problem(this, target, msg);
}
// Compare index structure if it wasn't a view
if (!getViews(target).contains(table)) {
Set<Index> masterIndexes = getIndexes(master, table);
Set<Index> targetIndexes = getIndexes(target, table);
Set<Index> masterMinusTargetIndexes = new HashSet<Index>(masterIndexes);
masterMinusTargetIndexes.removeAll(targetIndexes);
// report that target is missing indexes deinfod in master
if (!masterMinusTargetIndexes.isEmpty()) {
for (Index index : masterMinusTargetIndexes) {
String message = String.format(
"`%s` `%s` does not have the index `%s` which is present in `%s`. Check table structures",
targetName, table, index, masterNameForMsg);
ReportManager.problem(this, target, message);
}
okay = false;
}
Set<Index> targetMinusMasterIndexes = new HashSet<Index>(targetIndexes);
targetMinusMasterIndexes.removeAll(masterIndexes);
// report that target has indexes not deinfod in master
if (!targetMinusMasterIndexes.isEmpty()) {
for (Index index : targetMinusMasterIndexes) {
String message = String.format(
"`%s` `%s` does not have the index `%s` which is present in `%s`. Check table structures",
masterNameForMsg, table, index, targetName);
ReportManager.problem(this, masterForReportManager, message);
}
okay = false;
}
}
// Compare avg_row_length
if (applyTest(TestTypes.AVG_ROW_LENGTH)) {
boolean result = regexCreateTable(master, target, table,
"AVG_ROW_LENGTH=(\\d+)", Integer.class, TestTypes.AVG_ROW_LENGTH);
if (!result) {
okay = false;
}
}
// Compare max rows
if (applyTest(TestTypes.MAX_ROWS)) {
boolean result = regexCreateTable(master, target, table,
"MAX_ROWS=(\\d+)", Integer.class, TestTypes.MAX_ROWS);
if (!result) {
okay = false;
}
}
// Compare charset
if (applyTest(TestTypes.CHARSET)) {
boolean result = regexCreateTable(master, target, table,
"DEFAULT CHARSET=([a-zA-Z0-9]+)", String.class, TestTypes.CHARSET);
if (!result) {
okay = false;
}
}
// Compare engine
if (applyTest(TestTypes.ENGINE)) {
boolean result = regexCreateTable(master, target, table,
"ENGINE=([a-zA-Z0-9]+)", String.class, TestTypes.ENGINE);
if (!result) {
okay = false;
}
}
return okay;
}
protected boolean regexCreateTable(Connection master, Connection target,
String table, String regex, Class<?> type, TestTypes testing)
throws SQLException {
Pattern p = Pattern.compile(regex);
Object masterValue = regex(p, getCreateTable(master, table), type);
Object targetValue = regex(p, getCreateTable(target, table), type);
if (masterValue.equals(targetValue)) {
return true;
}
String message = String.format(
"%s in `%s` had different values. `%s` contained '%s'. `%s` contained '%s'",
testing.toString(), table, getMasterNameForMsg(master),
masterValue, DBUtils.getShortDatabaseName(target), targetValue);
ReportManager.problem(this, target, message);
return false;
}
protected Object regex(Pattern p, CharSequence target, Class<?> type) {
final Object o;
Matcher matcher = p.matcher(target);
if (matcher.find()) {
if (Integer.class.equals(type)) {
o = Integer.valueOf(matcher.group(1));
}
else {
o = matcher.group(1);
}
}
else {
o = "";
}
return o;
}
protected String getCreateTable(Connection conn, String table)
throws SQLException {
String key = conn.getMetaData().getURL() + ":" + table;
if (createTables.containsKey(key)) {
return createTables.get(key);
}
String sql = "SHOW CREATE TABLE " + table;
RowMapper<String> mapper = new DefaultObjectRowMapper<String>(String.class, 2);
String createTable = new ConnectionBasedSqlTemplateImpl(conn).queryForObject(sql, mapper);
if (applyTest(TestTypes.IGNORE_AUTOINCREMENT_OPTION)) {
createTable = createTable.replaceFirst("AUTO_INCREMENT=\\d+\\s*", "");
}
createTables.put(key, createTable);
return createTable;
}
/**
* Used to cache the current known set of views for each distinct JDBC URL
* retrieved from {@link Connection#getMetaData()}.
*
* @param conn
* Connection to query with
* @return {@link Set} of all views known of in the given connection
* @throws SQLException
* Thrown if there is an issue with MetaData retrieval
*/
private Set<String> getViews(Connection conn) throws SQLException {
String dbmsUrl = conn.getMetaData().getURL();
if (!views.containsKey(dbmsUrl)) {
List<String> dbViews = DBUtils.getViews(conn);
views.put(dbmsUrl, new HashSet<String>(dbViews));
}
return views.get(dbmsUrl);
}
/**
* Returns a copy of the columns we currently hold for this table
*/
protected Set<Column> getColumns(Connection conn, String table)
throws SQLException {
return new HashSet<Column>(getColumns(conn).get(table));
}
/**
* Used to cache the columns from a single schema.
*
* @param conn
* Connection to query with
* @return {@link Set} of all columns in a given connection keyed by the table
* name
* @throws SQLException
* Thrown if there is an issue with MetaData retrieval
*/
protected Map<String, Set<Column>> getColumns(Connection conn)
throws SQLException {
String dbmsUrl = conn.getMetaData().getURL();
if (!columns.containsKey(dbmsUrl)) {
Map<String, Set<Column>> dbColumns = new LinkedHashMap<String, Set<Column>>();
ResultSet rs = conn.getMetaData().getColumns(null,
DBUtils.getShortDatabaseName(conn), "%", "%");
try {
boolean processAutoIncrement = DBUtils.resultSetContainsColumn(rs, "IS_AUTOINCREMENT");
while (rs.next()) {
String table = rs.getString("TABLE_NAME");
// Get columns list
Set<Column> tableColumns = dbColumns.get(table);
if (tableColumns == null) {
tableColumns = new LinkedHashSet<Column>();
dbColumns.put(table, tableColumns);
}
boolean autoIncrement = (processAutoIncrement) ? rs.getBoolean("IS_AUTOINCREMENT") : false;
Column columnInstance = new Column(rs.getString("COLUMN_NAME"),
rs.getInt("DATA_TYPE"), rs.getInt("COLUMN_SIZE"),
rs.getInt("DECIMAL_DIGITS"), rs.getBoolean("NULLABLE"),
rs.getString("COLUMN_DEF"), rs.getInt("CHAR_OCTET_LENGTH"),
autoIncrement);
tableColumns.add(columnInstance);
}
}
finally {
DBUtils.closeQuietly(rs);
}
columns.put(dbmsUrl, dbColumns);
}
return columns.get(dbmsUrl);
}
/**
* Used to return all known indexed columns for a database
*
* @param conn
* Connection to query
* @param table
* Table to query
* @return {@link Set} of index objects ordered by their discovery
* @throws SQLException
* Thrown if there is a problem with MetaData
*/
protected Set<Index> getIndexes(Connection conn, String table)
throws SQLException {
Map<String, Index> indexes = new HashMap<String, Index>();
ResultSet rs = conn.getMetaData().getIndexInfo(null,
DBUtils.getShortDatabaseName(conn), table, false, false);
try {
while (rs.next()) {
String indexName = rs.getString("INDEX_NAME");
Index index = indexes.get(indexName);
if (index == null) {
index = new Index(indexName, rs.getBoolean("NON_UNIQUE"),
rs.getInt("TYPE"));
indexes.put(indexName, index);
}
index.addColumn(rs.getString("COLUMN_NAME"));
}
}
finally {
DBUtils.closeQuietly(rs);
}
return new LinkedHashSet<Index>(indexes.values());
}
/**
* Returns a locally cached Set of table names in the given schema
*/
protected Set<String> getTables(Connection conn) throws SQLException {
String url = conn.getMetaData().getURL();
if(! tables.containsKey(url)) {
String[] array = getTableNames(conn);
tables.put(url, new HashSet<String>(Arrays.asList(array)));
}
return tables.get(url);
}
private boolean searchForTemporaryTables(Connection conn) throws SQLException {
boolean temporaryTables = false;
Set<String> tables = getTables(conn);
List<String> searchValues = createArrayList("MTMP_", "tmp", "temp", "bak", "backup");
for(String table: tables) {
for(String search: searchValues) {
if(table.contains(search)) {
temporaryTables = true;
break;
}
}
}
return temporaryTables;
}
/**
* Internal class used to represent a column
*/
private static class Column {
private String name;
private int dataType;
private int columnSize;
private int decimalDigits;
private boolean nullable;
private String columnDefault;
private int charOctetLength;
private boolean autoIncrement;
public Column(String name, int dataType, int columnSize, int decimalDigits,
boolean nullable, String columnDefault, int charOctetLength, boolean autoIncrement) {
super();
this.name = name;
this.dataType = dataType;
this.columnSize = columnSize;
this.decimalDigits = decimalDigits;
this.nullable = nullable;
this.columnDefault = columnDefault;
this.charOctetLength = charOctetLength;
this.autoIncrement = autoIncrement;
}
public String getName() {
return name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (autoIncrement ? 1231 : 1237);
result = prime * result + charOctetLength;
result = prime * result + ((columnDefault == null) ? 0 : columnDefault.hashCode());
result = prime * result + columnSize;
result = prime * result + dataType;
result = prime * result + decimalDigits;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + (nullable ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Column other = (Column) obj;
if (autoIncrement != other.autoIncrement)
return false;
if (charOctetLength != other.charOctetLength)
return false;
if (columnDefault == null) {
if (other.columnDefault != null)
return false;
}
else if (!columnDefault.equals(other.columnDefault))
return false;
if (columnSize != other.columnSize)
return false;
if (dataType != other.dataType)
return false;
if (decimalDigits != other.decimalDigits)
return false;
if (name == null) {
if (other.name != null)
return false;
}
else if (!name.equals(other.name))
return false;
if (nullable != other.nullable)
return false;
return true;
}
@Override
public String toString() {
return getName();
}
}
/**
* Represents an Index with an equality and hashcode method which does not
* take into account name which is why a List would not suffice
*/
private static class Index {
private String name;
private List<String> columns = new ArrayList<String>();
private boolean nonUnique;
private int type;
public Index(String name, boolean nonUnique, int type) {
super();
this.name = name;
this.nonUnique = nonUnique;
this.type = type;
}
public void addColumn(String col) {
columns.add(col);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((columns == null) ? 0 : columns.hashCode());
result = prime * result + (nonUnique ? 1231 : 1237);
result = prime * result + type;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Index other = (Index) obj;
if (columns == null) {
if (other.columns != null)
return false;
}
else if (!columns.equals(other.columns))
return false;
if (nonUnique != other.nonUnique)
return false;
if (type != other.type)
return false;
return true;
}
@Override
public String toString() {
return name + "=[" + StringUtils.join(columns, ',') + "]";
}
}
}