package com.philmcrew.utility.db; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; import java.util.HashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Implements a number of more complicated database actions than what is supported by jdbc * in a way that will work for most datbases. To override a for a particular database you * just extend this class, and change DbTransfer.getDbSpecific in order to pick which DbSpecific class * to use. * * @author Mitchell J. Friedman */ public class DbSpecificBase implements DbSpecific { private final static Log LOG = LogFactory.getLog( DbSpecificBase.class ); private Connection connection; private DatabaseMetaData metaData; public DbSpecificBase( Connection connection ) throws SQLException { this.setConnection( connection ); this.setMetaData( connection.getMetaData() ); } public DatabaseMetaData getMetaData() { return metaData; } /* *
    *
  1. TABLE_CAT String => table catalog (may be null) *
  2. TABLE_SCHEM String => table schema (may be null) *
  3. TABLE_NAME String => table name *
  4. COLUMN_NAME String => column name *
  5. DATA_TYPE int => SQL type from java.sql.Types *
  6. TYPE_NAME String => Data source dependent type name, * for a UDT the type name is fully qualified *
  7. COLUMN_SIZE int => column size. For char or date * types this is the maximum number of characters, for numeric or * decimal types this is precision. *
  8. BUFFER_LENGTH is not used. *
  9. DECIMAL_DIGITS int => the number of fractional digits *
  10. NUM_PREC_RADIX int => Radix (typically either 10 or 2) *
  11. NULLABLE int => is NULL allowed. * *
  12. REMARKS String => comment describing column (may be null) *
  13. COLUMN_DEF String => default value (may be null) *
  14. SQL_DATA_TYPE int => unused *
  15. SQL_DATETIME_SUB int => unused *
  16. CHAR_OCTET_LENGTH int => for char types the * maximum number of bytes in the column *
  17. ORDINAL_POSITION int => index of column in table * (starting at 1) *
  18. IS_NULLABLE String => "NO" means column definitely * does not allow NULL values; "YES" means the column might * allow NULL values. An empty string means nobody knows. *
  19. SCOPE_CATLOG String => catalog of table that is the scope * of a reference attribute (null if DATA_TYPE isn't REF) *
  20. SCOPE_SCHEMA String => schema of table that is the scope * of a reference attribute (null if the DATA_TYPE isn't REF) *
  21. SCOPE_TABLE String => table name that this the scope * of a reference attribure (null if the DATA_TYPE isn't REF) *
  22. SOURCE_DATA_TYPE short => source type of a distinct type or user-generated * Ref type, SQL type from java.sql.Types (null if DATA_TYPE * isn't DISTINCT or user-generated REF) *
*/ public void close( Statement statement ) { if( null != statement ) { try { statement.close(); } catch( SQLException e ) { LOG.error( "Error closing statement", e ); } } } public void close( ResultSet resultSet ) { if( null != resultSet ) { try { resultSet.close(); } catch( SQLException e ) { LOG.error( "Error closing result set", e ); } } } public int createTableQuery( ResultSet columnMetaData, StringBuffer createQuery, HashMap columnMap ) throws SQLException { int col = 0; DbColumnInfo dbColumnInfo; // call with source and with dest - perhaps overriding cat or schema // then compare two schemas if different than drop dest table and build from source if( null != columnMetaData ) { String columnName; for( col = 0; columnMetaData.next(); col++ ) { dbColumnInfo = new DbColumnInfo( columnMetaData ); if( 0 == col ) { createQuery.append( "CREATE TABLE " ); /* buf.append( columnMetaData.getString( "TABLE_CAT" ) ); buf.append( "." ); buf.append( columnMetaData.getString( "TABLE_SCHEM" ) ); buf.append( "." ); */ createQuery.append( dbColumnInfo.getTableName().toLowerCase() ); createQuery.append( "\n(\n" ); } else { createQuery.append( ", \n" ); } createQuery.append( "\t" ); columnName = dbColumnInfo.getColumnName().toLowerCase(); createQuery.append( columnName ); createQuery.append( " " ); createQuery.append( buildDeclaration( dbColumnInfo ) ); createQuery.append( buildNullable( dbColumnInfo ) ); if( null != columnMap ) { columnMap.put( columnName, dbColumnInfo ); } } if( col > 0 ) createQuery.append( "\n);\n" ); } return col; } /** * By default lower-casing column name. Probably this will need to be removed from base * at some time. Microsoft SQL Server requires that certain column names be avoided - the * implementation in DbSpecificMicrosoftSqlServer appends a value to the column name. * * @param columnName * @return column name converted if necessary */ public String buildColumnName( String columnName ) { columnName = columnName.toLowerCase(); return columnName; } /** * Creates nullable phrase for use in create using column information * * @param dbColumnInfo * @return nullable phrase suitable for using in create */ public String buildNullable( DbColumnInfo dbColumnInfo ) { String ret; if( dbColumnInfo.getNullable() != DatabaseMetaData.columnNullable && dbColumnInfo.getIsNullable().equals( "NO" ) ) { ret = " not null"; } else ret = ""; return ret; } public String buildDeclaration( DbColumnInfo dbColumnInfo ) { StringBuffer buf = new StringBuffer(); switch( dbColumnInfo.getDataType() ) { case Types.BIGINT: buf.append( "bigint" ); break; case Types.BIT: buf.append( "bit" ); break; case Types.BLOB: buf.append( "image" ); break; case Types.BOOLEAN: buf.append( "boolean" ); break; case Types.CLOB: buf.append( "text" ); break; case Types.DATE: buf.append( "date" ); break; case Types.DECIMAL: buf.append( "decimal(" ); buf.append( dbColumnInfo.getColumnSize() ); buf.append( ", " ); buf.append( dbColumnInfo.getDecimalDigits() ); buf.append( ")" ); break; case Types.DOUBLE: buf.append( "float" ); break; case Types.INTEGER: buf.append( "int" ); break; case Types.FLOAT: buf.append( "float" ); break; case Types.NUMERIC: buf.append( "numeric(" ); buf.append( dbColumnInfo.getNumPrecRadix() ); buf.append( ", " ); buf.append( dbColumnInfo.getDecimalDigits() ); buf.append( ")" ); break; case Types.REAL: buf.append( "real" ); break; case Types.SMALLINT: buf.append( "smallint" ); break; case Types.TIME: buf.append( "time" ); break; case Types.TIMESTAMP: buf.append( "timestamp" ); break; case Types.TINYINT: buf.append( "tinyint" ); break; case Types.CHAR: case Types.NULL: if( dbColumnInfo.getColumnSize() < 8000 ) { buf.append( "char(" ); buf.append( dbColumnInfo.getColumnSize() ); buf.append( ")" ); break; } // drop through if too big for char case Types.LONGVARCHAR: case Types.VARCHAR: if( dbColumnInfo.getColumnSize() <= 8000 ) { buf.append( "varchar(" ); buf.append( dbColumnInfo.getColumnSize() ); buf.append( ")" ); } else { buf.append( "text" ); } break; case Types.BINARY: if( dbColumnInfo.getCharOctetLength() <= 8000 ) { buf.append( "binary" ); if( dbColumnInfo.getCharOctetLength() > 1 ) { buf.append( "(" ); buf.append( dbColumnInfo.getCharOctetLength() ); buf.append( ")" ); } break; } // drop through if too big for char case Types.VARBINARY: case Types.LONGVARBINARY: if( dbColumnInfo.getCharOctetLength() <= 8000 ) { buf.append( "varbinary" ); if( dbColumnInfo.getCharOctetLength() > 1 ) { buf.append( "(" ); buf.append( dbColumnInfo.getCharOctetLength() ); buf.append( ")" ); } } else { buf.append( "image" ); } break; case Types.ARRAY: case Types.DATALINK: case Types.DISTINCT: case Types.JAVA_OBJECT: case Types.OTHER: case Types.STRUCT: case Types.REF: default: buf.append( "image" ); break; } return buf.toString(); } public boolean drop( String catTableName, String schemaTableName, String tableName ) throws SQLException { boolean ret = false; Statement statement = null; ResultSet resultSet = null; try { statement = getConnection().createStatement(); resultSet = query( statement, "drop table " + tableName ); ret = null != resultSet && resultSet.next(); } catch( SQLException e ) { LOG.error( "Dropping " + tableName, e ); } finally { close( resultSet ); close( statement ); } return ret; } public boolean delete( String catTableName, String schemaTableName, String tableName ) throws SQLException { boolean ret = false; Statement statement = null; ResultSet resultSet = null; try { statement = getConnection().createStatement(); resultSet = query( statement, "delete from " + tableName ); ret = null != resultSet && resultSet.next(); } catch( SQLException e ) { LOG.error( "Deleting " + tableName, e ); } finally { close( resultSet ); close( statement ); } return ret; } public ResultSet query( Statement statement, String query ) throws SQLException { ResultSet resultSet = null; try { if( statement.execute( query ) ) resultSet = statement.getResultSet(); } catch( SQLException e ) { LOG.error( "Error running query " + query, e ); Exception e2 = e.getNextException(); if( null != e2 ) { LOG.error( "Query Error: cause=" + e2.getCause() + ";msg=" + e2.getMessage(), e2 ); } throw e; } return resultSet; } public Connection getConnection() { return connection; } public void setConnection( Connection connection ) { this.connection = connection; } public void setMetaData( DatabaseMetaData metaData ) { this.metaData = metaData; } public String connectMessage() { DatabaseMetaData dmd = getMetaData(); String tmp = ""; int tmpi; StringBuffer buf = new StringBuffer(); buf.append( "Connected to " ); try { tmp = dmd.getConnection().getCatalog(); buf.append( tmp ); } catch( SQLException e ) { } try { tmp = dmd.getURL(); buf.append( " " ); buf.append( tmp ); } catch( SQLException e ) { } buf.append( " - " ); try { tmp = dmd.getDatabaseProductName(); buf.append( tmp ); } catch( SQLException e ) { } try { tmp = dmd.getDatabaseProductVersion(); buf.append( " " ); buf.append( tmp ); } catch( SQLException e ) { } try { tmpi = dmd.getDatabaseMajorVersion(); buf.append( " " ); buf.append( tmpi ); } catch( SQLException e ) { } catch( AbstractMethodError e ) { // happened in sql server - pretty weird } try { tmpi = dmd.getDatabaseMinorVersion(); buf.append( "." ); buf.append( tmpi ); } catch( SQLException e ) { } catch( AbstractMethodError e ) { // happened in sql server - pretty weird } try { buf.append( " using " ); tmp = dmd.getDriverName(); buf.append( tmp ); } catch( SQLException e ) { } try { tmp = dmd.getDriverVersion(); buf.append( " " ); buf.append( tmp ); } catch( SQLException e ) { } tmpi = dmd.getDriverMajorVersion(); buf.append( " " ); buf.append( tmpi ); tmpi = dmd.getDriverMinorVersion(); buf.append( "." ); buf.append( tmp ); return buf.toString(); } public void setObject( PreparedStatement preparedStatement, int parameterIndex, Object object, int sourceColumnType, DbColumnInfo destColumnInfo ) throws SQLException { preparedStatement.setObject( parameterIndex, object ); } /** * Build Statistical query for column info. This query is probably not suitable for any database * since getting a min, max, avg and stddev don't typically work on all field types. * * @param dbColumnInfo * @return query to get statistical information. */ public String getStatisticsQuery( DbColumnInfo dbColumnInfo ) { String rowName = dbColumnInfo.getColumnName(); StringBuffer buf = new StringBuffer(); buf.append( "select count(distinct " ); buf.append( rowName ); buf.append( "), min(" ); buf.append( rowName ); buf.append( "), max(" ); buf.append( rowName ); buf.append( "), avg(" ); buf.append( rowName ); buf.append( "), stddev(" ); buf.append( rowName ); buf.append( ") " ); return buf.toString(); } }