org.dbunit.util.SQLHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xmlc-wml Show documentation
Show all versions of xmlc-wml Show documentation
old 2002 version of xmlc-xmlc-wml
The newest version!
/*
*
* The DbUnit Database Testing Framework
* Copyright (C)2005, DbUnit.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package org.dbunit.util;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Locale;
import org.dbunit.DatabaseUnitRuntimeException;
import org.dbunit.database.IMetadataHandler;
import org.dbunit.dataset.Column;
import org.dbunit.dataset.datatype.DataType;
import org.dbunit.dataset.datatype.DataTypeException;
import org.dbunit.dataset.datatype.IDataTypeFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper for SQL-related stuff.
*
* TODO: testcases, also think about refactoring so that methods are not static anymore (for better extensibility)
* @author Felipe Leme ([email protected])
* @version $Revision$
* @since Nov 5, 2005
*
*/
public class SQLHelper {
/**
* The database product name reported by Sybase JDBC drivers.
*/
public static final String DB_PRODUCT_SYBASE = "Sybase";
/**
* Logger for this class
*/
private static final Logger logger = LoggerFactory.getLogger(SQLHelper.class);
// class is "static"
private SQLHelper() {}
/**
* Gets the primary column for a table.
* @param conn connection with the database
* @param table table name
* @return name of primary column for a table (assuming it's just 1 column).
* @throws SQLException raised while getting the meta data
*/
public static String getPrimaryKeyColumn( Connection conn, String table ) throws SQLException {
logger.debug("getPrimaryKeyColumn(conn={}, table={}) - start", conn, table);
DatabaseMetaData metadata = conn.getMetaData();
ResultSet rs = metadata.getPrimaryKeys( null, null, table );
rs.next();
String pkColumn = rs.getString(4);
return pkColumn;
}
/**
* Close a result set and a prepared statement, checking for null references.
* @param rs result set to be closed
* @param stmt prepared statement to be closed
* @throws SQLException exception raised in either close() method
*/
public static void close(ResultSet rs, Statement stmt) throws SQLException {
logger.debug("close(rs={}, stmt={}) - start", rs, stmt);
try {
SQLHelper.close(rs);
} finally {
SQLHelper.close( stmt );
}
}
/**
* Close a SQL statement, checking for null references.
* @param stmt statement to be closed
* @throws SQLException exception raised while closing the statement
*/
public static void close(Statement stmt) throws SQLException {
logger.debug("close(stmt={}) - start", stmt);
if ( stmt != null ) {
stmt.close();
}
}
/**
* Closes the given result set in a null-safe way
* @param resultSet
* @throws SQLException
*/
public static void close(ResultSet resultSet) throws SQLException {
logger.debug("close(resultSet={}) - start", resultSet);
if(resultSet != null) {
resultSet.close();
}
}
/**
* Returns true
if the given schema exists for the given connection.
* @param connection The connection to a database
* @param schema The schema to be searched
* @return Returns true
if the given schema exists for the given connection.
* @throws SQLException
* @since 2.3.0
*/
public static boolean schemaExists(Connection connection, String schema)
throws SQLException
{
logger.trace("schemaExists(connection={}, schema={}) - start", connection, schema);
if(schema == null)
{
throw new NullPointerException("The parameter 'schema' must not be null");
}
DatabaseMetaData metaData = connection.getMetaData();
ResultSet rs = metaData.getSchemas(); //null, schemaPattern);
try
{
while(rs.next())
{
String foundSchema = rs.getString("TABLE_SCHEM");
if(foundSchema.equals(schema))
{
return true;
}
}
// Especially for MySQL check the catalog
if(catalogExists(connection, schema))
{
logger.debug("Found catalog with name {}. Returning true because DB is probably on MySQL", schema);
return true;
}
return false;
}
finally
{
rs.close();
}
}
/**
* Checks via {@link DatabaseMetaData#getCatalogs()} whether or not the given catalog exists.
* @param connection
* @param catalog
* @return
* @throws SQLException
* @since 2.4.4
*/
private static boolean catalogExists(Connection connection, String catalog) throws SQLException
{
logger.trace("catalogExists(connection={}, catalog={}) - start", connection, catalog);
if(catalog == null)
{
throw new NullPointerException("The parameter 'catalog' must not be null");
}
DatabaseMetaData metaData = connection.getMetaData();
ResultSet rs = metaData.getCatalogs();
try
{
while(rs.next())
{
String foundCatalog = rs.getString("TABLE_CAT");
if(foundCatalog.equals(catalog))
{
return true;
}
}
return false;
}
finally
{
rs.close();
}
}
/**
* Checks if the given table exists.
* @param metaData The database meta data
* @param schema The schema in which the table should be searched. If null
* the schema is not used to narrow the table name.
* @param tableName The table name to be searched
* @return Returns true
if the given table exists in the given schema.
* Else returns false
.
* @throws SQLException
* @since 2.3.0
* @deprecated since 2.4.5 - use {@link IMetadataHandler#tableExists(DatabaseMetaData, String, String)}
*/
public static boolean tableExists(DatabaseMetaData metaData, String schema,
String tableName)
throws SQLException
{
ResultSet tableRs = metaData.getTables(null, schema, tableName, null);
try
{
return tableRs.next();
}
finally
{
SQLHelper.close(tableRs);
}
}
/**
* Utility method for debugging to print all tables of the given metadata on the given stream
* @param metaData
* @param outputStream
* @throws SQLException
*/
public static void printAllTables(DatabaseMetaData metaData, PrintStream outputStream) throws SQLException
{
ResultSet rs = metaData.getTables(null, null, null, null);
try
{
while (rs.next())
{
String catalog = rs.getString("TABLE_CAT");
String schema = rs.getString("TABLE_SCHEM");
String table = rs.getString("TABLE_NAME");
StringBuffer tableInfo = new StringBuffer();
if(catalog!=null) tableInfo.append(catalog).append(".");
if(schema!=null) tableInfo.append(schema).append(".");
tableInfo.append(table);
// Print the info
outputStream.println(tableInfo);
}
outputStream.flush();
}
finally
{
SQLHelper.close(rs);
}
}
/**
* Returns the database and JDBC driver information as pretty formatted string
* @param metaData The JDBC database metadata needed to retrieve database information
* @return The database information as formatted string
*/
public static String getDatabaseInfo(DatabaseMetaData metaData)
{
StringBuffer sb = new StringBuffer();
sb.append("\n");
String dbInfo = null;
dbInfo = new ExceptionWrapper(){
public String wrappedCall(DatabaseMetaData metaData) throws Exception {
return metaData.getDatabaseProductName();
}
}.executeWrappedCall(metaData);
sb.append("\tdatabase product name=").append(dbInfo).append("\n");
dbInfo = new ExceptionWrapper(){
public String wrappedCall(DatabaseMetaData metaData) throws Exception {
return metaData.getDatabaseProductVersion();
}
}.executeWrappedCall(metaData);
sb.append("\tdatabase version=").append(dbInfo).append("\n");
dbInfo = new ExceptionWrapper(){
public String wrappedCall(DatabaseMetaData metaData) throws Exception {
return String.valueOf(metaData.getDatabaseMajorVersion());
}
}.executeWrappedCall(metaData);
sb.append("\tdatabase major version=").append(dbInfo).append("\n");
dbInfo = new ExceptionWrapper(){
public String wrappedCall(DatabaseMetaData metaData) throws Exception {
return String.valueOf(metaData.getDatabaseMinorVersion());
}
}.executeWrappedCall(metaData);
sb.append("\tdatabase minor version=").append(dbInfo).append("\n");
dbInfo = new ExceptionWrapper(){
public String wrappedCall(DatabaseMetaData metaData) throws Exception {
return metaData.getDriverName();
}
}.executeWrappedCall(metaData);
sb.append("\tjdbc driver name=").append(dbInfo).append("\n");
dbInfo = new ExceptionWrapper(){
public String wrappedCall(DatabaseMetaData metaData) throws Exception {
return metaData.getDriverVersion();
}
}.executeWrappedCall(metaData);
sb.append("\tjdbc driver version=").append(dbInfo).append("\n");
dbInfo = new ExceptionWrapper(){
public String wrappedCall(DatabaseMetaData metaData) throws Exception {
return String.valueOf(metaData.getDriverMajorVersion());
}
}.executeWrappedCall(metaData);
sb.append("\tjdbc driver major version=").append(dbInfo).append("\n");
dbInfo = new ExceptionWrapper(){
public String wrappedCall(DatabaseMetaData metaData) throws Exception {
return String.valueOf(metaData.getDriverMinorVersion());
}
}.executeWrappedCall(metaData);
sb.append("\tjdbc driver minor version=").append(dbInfo).append("\n");
return sb.toString();
}
/**
* Prints the database and JDBC driver information to the given output stream
* @param metaData The JDBC database metadata needed to retrieve database information
* @param outputStream The stream to which the information is printed
* @throws SQLException
*/
public static void printDatabaseInfo(DatabaseMetaData metaData, PrintStream outputStream) throws SQLException
{
String dbInfo = getDatabaseInfo(metaData);
try {
outputStream.println(dbInfo);
}
finally {
outputStream.flush();
}
}
/**
* Detects whether or not the given metadata describes the connection to a Sybase database
* or not.
* @param metaData The metadata to be checked whether it is a Sybase connection
* @return true
if and only if the given metadata belongs to a Sybase database.
* @throws SQLException
*/
public static boolean isSybaseDb(DatabaseMetaData metaData) throws SQLException
{
String dbProductName = metaData.getDatabaseProductName();
boolean isSybase = (dbProductName != null && dbProductName.equals(DB_PRODUCT_SYBASE));
return isSybase;
}
/**
* Utility method to create a {@link Column} object from a SQL {@link ResultSet} object.
*
* @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
* @param dataTypeFactory The factory used to lookup the {@link DataType} for this column
* @param datatypeWarning Whether or not a warning should be printed if the column could not
* be created because of an unknown datatype.
* @return The {@link Column} or null
if the column could not be initialized because of an
* unknown datatype.
* @throws SQLException
* @throws DataTypeException
* @since 2.4.0
*/
public static final Column createColumn(ResultSet resultSet,
IDataTypeFactory dataTypeFactory, boolean datatypeWarning)
throws SQLException, DataTypeException
{
String tableName = resultSet.getString(3);
String columnName = resultSet.getString(4);
int sqlType = resultSet.getInt(5);
//If Types.DISTINCT like SQL DOMAIN, then get Source Date Type of SQL-DOMAIN
if(sqlType == java.sql.Types.DISTINCT)
{
sqlType = resultSet.getInt("SOURCE_DATA_TYPE");
}
String sqlTypeName = resultSet.getString(6);
// int columnSize = resultSet.getInt(7);
int nullable = resultSet.getInt(11);
String remarks = resultSet.getString(12);
String columnDefaultValue = resultSet.getString(13);
// This is only available since Java 5 - so we can try it and if it does not work default it
String isAutoIncrement = Column.AutoIncrement.NO.getKey();
try {
isAutoIncrement = resultSet.getString(23);
}
catch (Exception e)
{
// Ignore this one here
final String msg =
"Could not retrieve the 'isAutoIncrement' property"
+ " because not yet running on Java 1.5 -"
+ " defaulting to NO. Table={}, Column={}";
logger.debug(msg, tableName, columnName, e);
}
// Convert SQL type to DataType
DataType dataType =
dataTypeFactory.createDataType(sqlType, sqlTypeName, tableName, columnName);
if (dataType != DataType.UNKNOWN)
{
Column column = new Column(columnName, dataType,
sqlTypeName, Column.nullableValue(nullable), columnDefaultValue, remarks,
Column.AutoIncrement.autoIncrementValue(isAutoIncrement));
return column;
}
else
{
if (datatypeWarning)
logger.warn(
tableName + "." + columnName +
" data type (" + sqlType + ", '" + sqlTypeName +
"') not recognized and will be ignored. See FAQ for more information.");
// datatype unknown - column not created
return null;
}
}
/**
* Checks if the given resultSet
matches the given schema and table name.
* The comparison is case sensitive.
* @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
* @param schema The name of the schema to check. If null
it is ignored in the comparison
* @param table The name of the table to check. If null
it is ignored in the comparison
* @param caseSensitive Whether or not the comparison should be case sensitive or not
* @return true
if the column metadata of the given resultSet
matches
* the given schema and table parameters.
* @throws SQLException
* @since 2.4.0
* @deprecated since 2.4.4 - use {@link IMetadataHandler#matches(ResultSet, String, String, String, String, boolean)}
*/
public static boolean matches(ResultSet resultSet,
String schema, String table, boolean caseSensitive)
throws SQLException
{
return matches(resultSet, null, schema, table, null, caseSensitive);
}
/**
* Checks if the given resultSet
matches the given schema and table name.
* The comparison is case sensitive.
* @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
* @param catalog The name of the catalog to check. If null
it is ignored in the comparison
* @param schema The name of the schema to check. If null
it is ignored in the comparison
* @param table The name of the table to check. If null
it is ignored in the comparison
* @param column The name of the column to check. If null
it is ignored in the comparison
* @param caseSensitive Whether or not the comparison should be case sensitive or not
* @return true
if the column metadata of the given resultSet
matches
* the given schema and table parameters.
* @throws SQLException
* @since 2.4.0
* @deprecated since 2.4.4 - use {@link IMetadataHandler#matches(ResultSet, String, String, String, String, boolean)}
*/
public static boolean matches(ResultSet resultSet,
String catalog, String schema,
String table, String column, boolean caseSensitive)
throws SQLException
{
String catalogName = resultSet.getString(1);
String schemaName = resultSet.getString(2);
String tableName = resultSet.getString(3);
String columnName = resultSet.getString(4);
// MYSQL provides only a catalog but no schema
if(schema != null && schemaName == null && catalog==null && catalogName != null){
logger.debug("Switching catalog/schema because the are mutually null");
schemaName = catalogName;
catalogName = null;
}
boolean areEqual =
areEqualIgnoreNull(catalog, catalogName, caseSensitive) &&
areEqualIgnoreNull(schema, schemaName, caseSensitive) &&
areEqualIgnoreNull(table, tableName, caseSensitive) &&
areEqualIgnoreNull(column, columnName, caseSensitive);
return areEqual;
}
/**
* Compares the given values and returns true if they are equal.
* If the first value is null
or empty String it always
* returns true
which is the way of ignoring null
s
* for this specific case.
* @param value1 The first value to compare. Is ignored if null or empty String
* @param value2 The second value to be compared
* @return true
if both values are equal or if the first value
* is null
or empty string.
* @since 2.4.4
*/
public static final boolean areEqualIgnoreNull(String value1, String value2, boolean caseSensitive)
{
if(value1==null || value1.equals(""))
{
return true;
}
else
{
if(caseSensitive && value1.equals(value2))
{
return true;
}
else if(!caseSensitive && value1.equalsIgnoreCase(value2))
{
return true;
}
else
{
return false;
}
}
}
/**
* Corrects the case of the given String according to the way in which the database stores metadata.
* @param databaseIdentifier A database identifier such as a table name or a schema name for
* which the case should be corrected.
* @param connection The connection used to lookup the database metadata. This is needed to determine
* the way in which the database stores its metadata.
* @return The database identifier in the correct case for the RDBMS
* @since 2.4.4
*/
public static final String correctCase(final String databaseIdentifier, Connection connection)
{
logger.trace("correctCase(tableName={}, connection={}) - start", databaseIdentifier, connection);
try
{
return correctCase(databaseIdentifier, connection.getMetaData());
}
catch (SQLException e)
{
throw new DatabaseUnitRuntimeException("Exception while trying to access database metadata", e);
}
}
/**
* Corrects the case of the given String according to the way in which the database stores metadata.
* @param databaseIdentifier A database identifier such as a table name or a schema name for
* which the case should be corrected.
* @param databaseMetaData The database metadata needed to determine the way in which the database stores
* its metadata.
* @return The database identifier in the correct case for the RDBMS
* @since 2.4.4
*/
public static final String correctCase(final String databaseIdentifier, DatabaseMetaData databaseMetaData)
{
logger.trace("correctCase(tableName={}, databaseMetaData={}) - start", databaseIdentifier, databaseMetaData);
if (databaseIdentifier == null) {
throw new NullPointerException(
"The parameter 'databaseIdentifier' must not be null");
}
if (databaseMetaData == null) {
throw new NullPointerException(
"The parameter 'databaseMetaData' must not be null");
}
try {
String resultTableName = databaseIdentifier;
String dbIdentifierQuoteString = databaseMetaData.getIdentifierQuoteString();
if(!isEscaped(databaseIdentifier, dbIdentifierQuoteString)){
if(databaseMetaData.storesLowerCaseIdentifiers())
{
resultTableName = databaseIdentifier.toLowerCase(Locale.ENGLISH);
}
else if(databaseMetaData.storesUpperCaseIdentifiers())
{
resultTableName = databaseIdentifier.toUpperCase(Locale.ENGLISH);
}
else
{
logger.debug("Database does not store upperCase or lowerCase identifiers. " +
"Will not correct case of the table names.");
}
}
else
{
if(logger.isDebugEnabled())
logger.debug("The tableName '{}' is escaped. Will not correct case.", databaseIdentifier);
}
return resultTableName;
}
catch (SQLException e)
{
throw new DatabaseUnitRuntimeException("Exception while trying to access database metadata", e);
}
}
/**
* Checks whether two given values are unequal and if so print a log message (level DEBUG)
* @param oldValue The old value of a property
* @param newValue The new value of a property
* @param message The message to be logged
* @param source The class which invokes this method - used for enriching the log message
* @since 2.4.4
*/
public static final void logInfoIfValueChanged(String oldValue, String newValue, String message, Class source)
{
if(logger.isInfoEnabled())
{
if(oldValue != null && !oldValue.equals(newValue))
logger.debug("{}. {} oldValue={} newValue={}", new Object[] {source, message, oldValue, newValue});
}
}
/**
* Checks whether two given values are unequal and if so print a log message (level DEBUG)
* @param oldValue The old value of a property
* @param newValue The new value of a property
* @param message The message to be logged
* @param source The class which invokes this method - used for enriching the log message
* @since 2.4.8
*/
public static final void logDebugIfValueChanged(String oldValue, String newValue, String message, Class source)
{
if (logger.isDebugEnabled())
{
if (oldValue != null && !oldValue.equals(newValue))
logger.debug("{}. {} oldValue={} newValue={}", new Object[] {source, message, oldValue, newValue});
}
}
/**
* @param tableName
* @param dbIdentifierQuoteString
* @return
* @since 2.4.4
*/
private static final boolean isEscaped(String tableName, String dbIdentifierQuoteString)
{
logger.trace("isEscaped(tableName={}, dbIdentifierQuoteString={}) - start", tableName, dbIdentifierQuoteString);
if (dbIdentifierQuoteString == null) {
throw new NullPointerException(
"The parameter 'dbIdentifierQuoteString' must not be null");
}
boolean isEscaped = tableName!=null && (tableName.startsWith(dbIdentifierQuoteString));
if(logger.isDebugEnabled())
logger.debug("isEscaped returns '{}' for tableName={} (dbIdentifierQuoteString={})",
new Object[]{Boolean.valueOf(isEscaped), tableName, dbIdentifierQuoteString} );
return isEscaped;
}
/**
* Performs a method invocation and catches all exceptions that occur during the invocation.
* Utility which works similar to a closure, just a bit less elegant.
* @author gommma (gommma AT users.sourceforge.net)
* @author Last changed by: $Author$
* @version $Revision$ $Date$
* @since 2.4.6
*/
static abstract class ExceptionWrapper{
public static final String NOT_AVAILABLE_TEXT = "";
/**
* Default constructor
*/
public ExceptionWrapper()
{
}
/**
* Executes the call and catches all exception that might occur.
* @param metaData
* @return The result of the call
*/
public final String executeWrappedCall(DatabaseMetaData metaData) {
try{
String result = wrappedCall(metaData);
return result;
}
catch(Exception e){
logger.trace("Problem retrieving DB information via DatabaseMetaData", e);
return NOT_AVAILABLE_TEXT;
}
}
/**
* Calls the method that might throw an exception to be handled
* @param metaData
* @return The result of the call as human readable string
* @throws Exception Any exception that might occur during the method invocation
*/
public abstract String wrappedCall(DatabaseMetaData metaData) throws Exception;
}
}