blob: 9d717bae6c380006d357a32060455c6dc5361468 [file] [log] [blame]
/*
*
* Derby - Class org.apache.derbyTesting.junit.JDBC
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.derbyTesting.junit;
import java.io.IOException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import junit.framework.Assert;
import junit.framework.AssertionFailedError;
/**
* JDBC utility methods for the JUnit tests.
* Note that JSR 169 is a subset of JDBC 3 and
* JDBC 3 is a subset of JDBC 4.
* The base level for the Derby tests is JSR 169.
*/
public class JDBC {
/**
* Helper class whose <code>equals()</code> method returns
* <code>true</code> for all strings on this format: SQL061021105830900
*/
public static class GeneratedId {
public boolean equals(Object o) {
String tmpstr = (String)o;
boolean b = true;
if (!(o instanceof String))
b = false;
if (!(tmpstr.startsWith("SQL")))
b = false;
if (tmpstr.length() != 18)
b = false;
for (int i=3 ; i<18 ; i++)
{
if (Character.isDigit(tmpstr.charAt(i)))
continue;
else
{
b = false;
break;
}
}
return b;
}
public String toString() {
return "xxxxGENERATED-IDxxxx";
}
}
/**
* Constant to pass to DatabaseMetaData.getTables() to fetch
* just tables.
*/
public static final String[] GET_TABLES_TABLE = new String[] {"TABLE"};
/**
* Constant to pass to DatabaseMetaData.getTables() to fetch
* just views.
*/
public static final String[] GET_TABLES_VIEW = new String[] {"VIEW"};
/**
* Constant to pass to DatabaseMetaData.getTables() to fetch
* just synonyms.
*/
public static final String[] GET_TABLES_SYNONYM =
new String[] {"SYNONYM"};
/**
* Types.SQLXML value without having to compile with JDBC4.
*/
public static final int SQLXML = 2009;
/**
* Tell if we are allowed to use DriverManager to create database
* connections.
*/
private static final boolean HAVE_DRIVER
= haveClass("java.sql.Driver");
/**
* Does the Savepoint class exist, indicates
* JDBC 3 (or JSR 169).
*/
private static final boolean HAVE_SAVEPOINT
= haveClass("java.sql.Savepoint");
/**
* Does the java.sql.SQLXML class exist, indicates JDBC 4.
*/
private static final boolean HAVE_SQLXML
= haveClass("java.sql.SQLXML");
/** Is the Lucene core jar file on the classpath */
public static final boolean HAVE_LUCENE_CORE =
haveClass( "org.apache.lucene.analysis.Analyzer" );
/** Is the Lucene analyzer jar file on the classpath */
public static final boolean HAVE_LUCENE_ANALYZERS =
haveClass( "org.apache.lucene.analysis.en.EnglishAnalyzer" );
/** Is the Lucene queryparser jar file on the classpath */
public static final boolean HAVE_LUCENE_QUERYPARSER =
haveClass( "org.apache.lucene.queryparser.surround.parser.QueryParser" );
/** Is the json-simple core jar file on the classpath */
public static final boolean HAVE_JSON_SIMPLE =
haveClass( "org.json.simple.JSONArray" );
/**
* Does java.sql.ResultSet implement java.lang.AutoCloseable?
* Indicates JDBC 4.1.
*/
private static final boolean HAVE_AUTO_CLOSEABLE_RESULT_SET;
static {
boolean autoCloseable;
try {
Class<?> acClass = Class.forName("java.lang.AutoCloseable");
autoCloseable = acClass.isAssignableFrom(ResultSet.class);
} catch (Throwable t) {
autoCloseable = false;
}
HAVE_AUTO_CLOSEABLE_RESULT_SET = autoCloseable;
}
private static final boolean HAVE_REFERENCEABLE;
static {
boolean ok = false;
try {
Class.forName("javax.naming.Referenceable");
ok = true;
} catch (Throwable t) {
}
HAVE_REFERENCEABLE = ok;
}
private static final boolean HAVE_SQLTYPE;
static {
boolean ok = false;
try {
Class.forName("java.sql.SQLType");
ok = true;
} catch (Throwable t) {
}
HAVE_SQLTYPE = ok;
}
/**
* Is javax.management.MBeanServer available? Indicates whether the
* JVM supports the Java Management Extensions (JMX).
*/
private static final boolean HAVE_MBEAN_SERVER =
haveClass("javax.management.MBeanServer");
/**
* Can we load a specific class, use this to determine JDBC level.
* @param className Class to attempt load on.
* @return true if class can be loaded, false otherwise.
*/
static boolean haveClass(String className)
{
try {
Class.forName(className);
return true;
} catch (Throwable e) {
return false;
}
}
/**
* Return true if the virtual machine environment supports JDBC 4.2 or
* later.
*/
public static boolean vmSupportsJDBC42() {
return vmSupportsJDBC41() && HAVE_SQLTYPE;
}
/**
* Return true if the virtual machine environment supports JDBC 4.1 or
* later. JDBC 4.1 is a superset of JDBC 4.0 and of JSR-169.
*/
public static boolean vmSupportsJDBC41() {
return vmSupportsJDBC4() && HAVE_AUTO_CLOSEABLE_RESULT_SET;
}
/**
* Return true if the virtual machine environment
* supports JDBC4 or later. JDBC 4 is a superset
* of JDBC 3 and of JSR169.
* <BR>
* This method returns true in a JDBC 4 environment
* and false in a JDBC 3 or JSR 169 environment.
*/
public static boolean vmSupportsJDBC4()
{
return HAVE_DRIVER
&& HAVE_SQLXML;
}
/**
* Return true if the virtual machine environment
* supports JDBC3 or later. JDBC 3 is a super-set of JSR169
* and a subset of JDBC 4.
* <BR>
* This method will return true in a JDBC 3 or JDBC 4
* environment, but false in a JSR169 environment.
*/
public static boolean vmSupportsJDBC3()
{
return HAVE_DRIVER
&& HAVE_SAVEPOINT;
}
/**
* Return true if the virtual machine environment
* supports JSR169. JSR169 is a subset of JDBC 3
* and hence a subset of JDBC 4 as well.
* <BR>
* This method returns true only in a JSR 169
* environment.
*/
public static boolean vmSupportsJSR169()
{
return !HAVE_DRIVER
&& HAVE_SAVEPOINT;
}
/**
* @return {@code true} if JNDI is available.
*/
public static boolean vmSupportsJNDI() {
return HAVE_REFERENCEABLE;
}
/**
* Return true if the JVM supports the Java Management Extensions (JMX).
*/
public static boolean vmSupportsJMX() {
return HAVE_MBEAN_SERVER;
}
/**
* Rollback and close a connection for cleanup.
* Test code that is expecting Connection.close to succeed
* normally should just call conn.close().
*
* <P>
* If conn is not-null and isClosed() returns false
* then both rollback and close will be called.
* If both methods throw exceptions
* then they will be chained together and thrown.
* @throws SQLException Error closing connection.
*/
public static void cleanup(Connection conn) throws SQLException
{
if (conn == null)
return;
if (conn.isClosed())
return;
SQLException sqle = null;
try {
conn.rollback();
} catch (SQLException e) {
sqle = e;
}
try {
conn.close();
} catch (SQLException e) {
if (sqle == null)
sqle = e;
else
sqle.setNextException(e);
throw sqle;
}
}
/**
* Drop a database schema by dropping all objects in it
* and then executing DROP SCHEMA. If the schema is
* APP it is cleaned but DROP SCHEMA is not executed.
*
* TODO: Handle dependencies by looping in some intelligent
* way until everything can be dropped.
*
*
* @param dmd DatabaseMetaData object for database
* @param schema Name of the schema
* @throws SQLException database error
*/
public static void dropSchema(DatabaseMetaData dmd, String schema) throws SQLException
{
Connection conn = dmd.getConnection();
Assert.assertFalse(conn.getAutoCommit());
Statement s = dmd.getConnection().createStatement();
// Triggers
PreparedStatement pstr = conn.prepareStatement(
"SELECT TRIGGERNAME FROM SYS.SYSSCHEMAS S, SYS.SYSTRIGGERS T "
+ "WHERE S.SCHEMAID = T.SCHEMAID AND SCHEMANAME = ?");
pstr.setString(1, schema);
ResultSet trrs = pstr.executeQuery();
while (trrs.next()) {
String trigger = trrs.getString(1);
s.execute("DROP TRIGGER " + JDBC.escape(schema, trigger));
}
trrs.close();
pstr.close();
// Functions - not supported by JDBC meta data until JDBC 4
// Need to use the CHAR() function on A.ALIASTYPE
// so that the compare will work in any schema.
PreparedStatement psf = conn.prepareStatement(
"SELECT ALIAS FROM SYS.SYSALIASES A, SYS.SYSSCHEMAS S" +
" WHERE A.SCHEMAID = S.SCHEMAID " +
" AND CHAR(A.ALIASTYPE) = ? " +
" AND S.SCHEMANAME = ?");
psf.setString(1, "F" );
psf.setString(2, schema);
ResultSet rs = psf.executeQuery();
dropUsingDMD(s, rs, schema, "ALIAS", "FUNCTION");
// Procedures
rs = dmd.getProcedures((String) null,
schema, (String) null);
dropUsingDMD(s, rs, schema, "PROCEDURE_NAME", "PROCEDURE");
// Views
rs = dmd.getTables((String) null, schema, (String) null,
GET_TABLES_VIEW);
dropUsingDMD(s, rs, schema, "TABLE_NAME", "VIEW");
// Tables
rs = dmd.getTables((String) null, schema, (String) null,
GET_TABLES_TABLE);
dropUsingDMD(s, rs, schema, "TABLE_NAME", "TABLE");
// At this point there may be tables left due to
// foreign key constraints leading to a dependency loop.
// Drop any constraints that remain and then drop the tables.
// If there are no tables then this should be a quick no-op.
ResultSet table_rs = dmd.getTables((String) null, schema, (String) null,
GET_TABLES_TABLE);
while (table_rs.next()) {
String tablename = table_rs.getString("TABLE_NAME");
rs = dmd.getExportedKeys((String) null, schema, tablename);
while (rs.next()) {
short keyPosition = rs.getShort("KEY_SEQ");
if (keyPosition != 1)
continue;
String fkName = rs.getString("FK_NAME");
// No name, probably can't happen but couldn't drop it anyway.
if (fkName == null)
continue;
String fkSchema = rs.getString("FKTABLE_SCHEM");
String fkTable = rs.getString("FKTABLE_NAME");
String ddl = "ALTER TABLE " +
JDBC.escape(fkSchema, fkTable) +
" DROP FOREIGN KEY " +
JDBC.escape(fkName);
s.executeUpdate(ddl);
}
rs.close();
}
table_rs.close();
conn.commit();
// Tables (again)
rs = dmd.getTables((String) null, schema, (String) null,
GET_TABLES_TABLE);
dropUsingDMD(s, rs, schema, "TABLE_NAME", "TABLE");
// drop UDTs
psf.setString(1, "A" );
psf.setString(2, schema);
rs = psf.executeQuery();
dropUsingDMD(s, rs, schema, "ALIAS", "TYPE");
// drop aggregates
psf.setString(1, "G" );
psf.setString(2, schema);
rs = psf.executeQuery();
dropUsingDMD(s, rs, schema, "ALIAS", "DERBY AGGREGATE");
psf.close();
// Synonyms - need work around for DERBY-1790 where
// passing a table type of SYNONYM fails.
rs = dmd.getTables((String) null, schema, (String) null,
GET_TABLES_SYNONYM);
dropUsingDMD(s, rs, schema, "TABLE_NAME", "SYNONYM");
// sequences
if ( sysSequencesExists( conn ) )
{
psf = conn.prepareStatement
(
"SELECT SEQUENCENAME FROM SYS.SYSSEQUENCES A, SYS.SYSSCHEMAS S" +
" WHERE A.SCHEMAID = S.SCHEMAID " +
" AND S.SCHEMANAME = ?");
psf.setString(1, schema);
rs = psf.executeQuery();
dropUsingDMD(s, rs, schema, "SEQUENCENAME", "SEQUENCE");
psf.close();
}
// Finally drop the schema if it is not APP
if (!schema.equals("APP")) {
s.executeUpdate("DROP SCHEMA " + JDBC.escape(schema) + " RESTRICT");
}
conn.commit();
s.close();
}
/**
* Return true if the SYSSEQUENCES table exists.
*/
private static boolean sysSequencesExists( Connection conn ) throws SQLException
{
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement
(
"select count(*) from sys.systables t, sys.sysschemas s\n" +
"where t.schemaid = s.schemaid\n" +
"and ( cast(s.schemaname as varchar(128)))= 'SYS'\n" +
"and ( cast(t.tablename as varchar(128))) = 'SYSSEQUENCES'" );
rs = ps.executeQuery();
rs.next();
return ( rs.getInt( 1 ) > 0 );
}
finally
{
if ( rs != null ) { rs.close(); }
if ( ps != null ) { ps.close(); }
}
}
/**
* DROP a set of objects based upon a ResultSet from a
* DatabaseMetaData call.
*
* TODO: Handle errors to ensure all objects are dropped,
* probably requires interaction with its caller.
*
* @param s Statement object used to execute the DROP commands.
* @param rs DatabaseMetaData ResultSet
* @param schema Schema the objects are contained in
* @param mdColumn The column name used to extract the object's
* name from rs
* @param dropType The keyword to use after DROP in the SQL statement
* @throws SQLException database errors.
*/
private static void dropUsingDMD(
Statement s, ResultSet rs, String schema,
String mdColumn,
String dropType) throws SQLException
{
String dropLeadIn = "DROP " + dropType + " ";
// First collect the set of DROP SQL statements.
ArrayList<String> ddl = new ArrayList<String>();
while (rs.next())
{
String objectName = rs.getString(mdColumn);
String raw = dropLeadIn + JDBC.escape(schema, objectName);
if (
"TYPE".equals( dropType ) ||
"SEQUENCE".equals( dropType ) ||
"DERBY AGGREGATE".equals( dropType )
)
{ raw = raw + " restrict "; }
ddl.add( raw );
}
rs.close();
if (ddl.isEmpty())
return;
// Execute them as a complete batch, hoping they will all succeed.
s.clearBatch();
int batchCount = 0;
for (Iterator i = ddl.iterator(); i.hasNext(); )
{
Object sql = i.next();
if (sql != null) {
s.addBatch(sql.toString());
batchCount++;
}
}
int[] results;
boolean hadError;
try {
results = s.executeBatch();
Assert.assertNotNull(results);
Assert.assertEquals("Incorrect result length from executeBatch",
batchCount, results.length);
hadError = false;
} catch (BatchUpdateException batchException) {
results = batchException.getUpdateCounts();
Assert.assertNotNull(results);
Assert.assertTrue("Too many results in BatchUpdateException",
results.length <= batchCount);
hadError = true;
}
// Remove any statements from the list that succeeded.
boolean didDrop = false;
for (int i = 0; i < results.length; i++)
{
int result = results[i];
if (result == Statement.EXECUTE_FAILED)
hadError = true;
else if (result == Statement.SUCCESS_NO_INFO || result >= 0) {
didDrop = true;
ddl.set(i, null);
}
else
Assert.fail("Negative executeBatch status");
}
s.clearBatch();
if (didDrop) {
// Commit any work we did do.
s.getConnection().commit();
}
// If we had failures drop them as individual statements
// until there are none left or none succeed. We need to
// do this because the batch processing stops at the first
// error. This copes with the simple case where there
// are objects of the same type that depend on each other
// and a different drop order will allow all or most
// to be dropped.
if (hadError) {
do {
hadError = false;
didDrop = false;
for (ListIterator<String> i = ddl.listIterator(); i.hasNext();) {
String sql = i.next();
if (sql != null) {
try {
s.executeUpdate(sql);
i.set(null);
didDrop = true;
} catch (SQLException e) {
hadError = true;
}
}
}
if (didDrop)
s.getConnection().commit();
} while (hadError && didDrop);
}
}
/**
* Assert all columns in the ResultSetMetaData match the
* table's defintion through DatabaseMetadDta. Only works
* if the complete select list correspond to columns from
* base tables.
* <BR>
* Does not require that the complete set of any table's columns are
* returned.
* @throws SQLException
*
*/
public static void assertMetaDataMatch(DatabaseMetaData dmd,
ResultSetMetaData rsmd) throws SQLException
{
for (int col = 1; col <= rsmd.getColumnCount(); col++)
{
// Only expect a single column back
ResultSet column = dmd.getColumns(
rsmd.getCatalogName(col),
rsmd.getSchemaName(col),
rsmd.getTableName(col),
rsmd.getColumnName(col));
Assert.assertTrue("Column missing " + rsmd.getColumnName(col),
column.next());
Assert.assertEquals(column.getInt("DATA_TYPE"),
rsmd.getColumnType(col));
Assert.assertEquals(column.getInt("NULLABLE"),
rsmd.isNullable(col));
Assert.assertEquals(column.getString("TYPE_NAME"),
rsmd.getColumnTypeName(col));
column.close();
}
}
/**
* Assert a result set is empty.
* If the result set is not empty it will
* be drained before the check to see if
* it is empty.
* The ResultSet is closed by this method.
*/
public static void assertEmpty(ResultSet rs)
throws SQLException
{
assertDrainResults(rs, 0);
}
/**
*
* @param rs
*/
public static void assertClosed(ResultSet rs)
{
try {
rs.next();
Assert.fail("ResultSet not closed");
}catch (SQLException sqle){
Assert.assertEquals("XCL16", sqle.getSQLState());
}
}
/**
* Assert that no warnings were returned from a JDBC getWarnings()
* method such as Connection.getWarnings. Reports the contents
* of the warning if it is not null.
* @param warning Warning that should be null.
*/
public static void assertNoWarnings(SQLWarning warning)
{
if (warning == null)
return;
Assert.fail("Expected no SQLWarnings - got: " + warning.getSQLState()
+ " " + warning.getMessage());
}
/**
* Assert that the statement has no more results(getMoreResults) and it
* indeed does not return any resultsets(by checking getResultSet).
* Also, ensure that update count is -1.
* @param s Statement holding no results.
* @throws SQLException Exception checking results.
*/
public static void assertNoMoreResults(Statement s) throws SQLException
{
Assert.assertFalse(s.getMoreResults());
Assert.assertTrue(s.getUpdateCount() == -1);
Assert.assertNull(s.getResultSet());
}
/**
* Assert that a ResultSet representing generated keys is non-null
* and of the correct type. This method leaves the ResultSet
* open and does not fetch any date from it.
*
* @param description For assert messages
* @param keys ResultSet returned from getGeneratedKeys().
* @throws SQLException
*/
public static void assertGeneratedKeyResultSet(
String description, ResultSet keys) throws SQLException
{
Assert.assertNotNull(description, keys);
// Requirements from section 13.6 JDBC 4 specification
Assert.assertEquals(
description +
" - Required CONCUR_READ_ONLY for generated key result sets",
ResultSet.CONCUR_READ_ONLY, keys.getConcurrency());
int type = keys.getType();
if ( (type != ResultSet.TYPE_FORWARD_ONLY) &&
(type != ResultSet.TYPE_SCROLL_INSENSITIVE))
{
Assert.fail(description +
" - Invalid type for generated key result set" + type);
}
}
/**
* Drain a ResultSet and assert it has at least one row.
*
* The ResultSet is closed by this method.
*/
public static void assertDrainResultsHasData(ResultSet rs)
throws SQLException
{
int rowCount = assertDrainResults(rs, -1);
Assert.assertTrue("ResultSet expected to have data", rowCount > 0);
}
/**
* Drain a single ResultSet by reading all of its
* rows and columns. Each column is accessed using
* getString() and asserted that the returned value
* matches the state of ResultSet.wasNull().
*
* Provides simple testing of the ResultSet when the
* contents are not important.
*
* The ResultSet is closed by this method.
*
* @param rs Result set to drain.
* @return the number of rows seen.
* @throws SQLException
*/
public static int assertDrainResults(ResultSet rs)
throws SQLException
{
return assertDrainResults(rs, -1);
}
/**
* Does the work of assertDrainResults() as described
* above. If the received row count is non-negative,
* this method also asserts that the number of rows
* in the result set matches the received row count.
*
* The ResultSet is closed by this method.
*
* @param rs Result set to drain.
* @param expectedRows If non-negative, indicates how
* many rows we expected to see in the result set.
* @return the number of rows seen.
* @throws SQLException
*/
public static int assertDrainResults(ResultSet rs,
int expectedRows) throws SQLException
{
ResultSetMetaData rsmd = rs.getMetaData();
List<List<String>> seen = new ArrayList<List<String>>();
List<String> seenRow = new ArrayList<String>();
int rows = 0;
while (rs.next()) {
for (int col = 1; col <= rsmd.getColumnCount(); col++)
{
String s = rs.getString(col);
seenRow.add(s);
Assert.assertEquals(s == null, rs.wasNull());
if (rs.wasNull())
assertResultColumnNullable(rs, seen, seenRow, col);
}
rows++;
seen.add(new ArrayList<String>(seenRow));
seenRow.clear();
}
rs.close();
if (expectedRows >= 0) {
try {
Assert.assertEquals("Unexpected row count:", expectedRows, rows);
} catch (AssertionFailedError e) {
throw addRsToReport(e, rsmd, seen, seenRow, rs);
}
}
return rows;
}
/**
* Assert that a column is nullable in its ResultSetMetaData.
* Used when a utility method checking the contents of a
* ResultSet sees a NULL value. If the value is NULL then
* the column's definition in ResultSetMetaData must allow NULLs
* (or not disallow NULLS).
* @param rs the resultSet
* @param seen The set of entirely read rows so far
* @param seenRow The set of read columns in the current row so far
* @param col Position of column just fetched that was NULL.
* @throws SQLException Error accessing meta data
*/
private static void assertResultColumnNullable(
ResultSet rs,
List<List<String>> seen,
List<String> seenRow,
int col)
throws SQLException
{
final ResultSetMetaData rsmd = rs.getMetaData();
try {
Assert.assertFalse(rsmd.isNullable(col) == ResultSetMetaData.columnNoNulls);
} catch (AssertionFailedError e) {
throw addRsToReport(e, rsmd, seen, seenRow, rs);
}
}
/**
* Takes a result set and an array of expected colum names (as
* Strings) and asserts that the column names in the result
* set metadata match the number, order, and names of those
* in the array.
*
* @param rs ResultSet for which we're checking column names.
* @param expectedColNames Array of expected column names.
*/
public static void assertColumnNames(ResultSet rs,
String... expectedColNames) throws SQLException
{
ResultSetMetaData rsmd = rs.getMetaData();
int actualCols = rsmd.getColumnCount();
Assert.assertEquals("Unexpected column count:",
expectedColNames.length, rsmd.getColumnCount());
for (int i = 0; i < actualCols; i++)
{
Assert.assertEquals("Column names do not match:",
expectedColNames[i], rsmd.getColumnName(i+1));
}
}
/**
* Takes a result set and an array of expected colum names (as
* Strings) and asserts that the column names in the result
* set metadata match the number, order, and names of those
* in the array.
* Does the same for column types
* *
* @param rs ResultSet for which we're checking column names.
* @param expectedTypes Array of expected types for the columns.
* @param expectedColNames Array of expected column names.
*/
public static void assertDatabaseMetaDataColumns(ResultSet rs,
int[] expectedTypes,
String... expectedColNames) throws SQLException
{
assertDatabaseMetaDataColumns(rs, null, expectedTypes, expectedColNames);
}
/**
* Takes a result set and an array of expected colum names (as
* Strings) and asserts that the column names in the result
* set metadata match the number, order, and names of those
* in the array.
* Does the same for column types and nullability.
*
* This is a variation of JDBC.assertColumnNames, here we only compare
* the expected columns and ignore additional ones, because columns
* returned from DatabaseMetaData can be added to with newer JDBC versions.
* If the ResultSet can not change over time, JDBC.assertColumnNames
* should be used. See DERBY-6180.
*
* @param rs ResultSet for which we're checking column names.
* @param expectedTypes Array of expected types for the columns.
* @param nullability Array of expected nullability values for the columns.
* @param expectedColNames Array of expected column names.
*/
public static void assertDatabaseMetaDataColumns(ResultSet rs,
boolean[] nullability, int[] expectedTypes,
String... expectedColNames) throws SQLException
{
ResultSetMetaData rsmd = rs.getMetaData();
int actualCols = rsmd.getColumnCount();
String actualColNames="[";
for (int c = 0; c < rsmd.getColumnCount(); c++) {
if (c==0)
actualColNames=actualColNames + rsmd.getColumnName(c+1);
else
actualColNames=actualColNames + ", " + rsmd.getColumnName(c+1);
}
actualColNames=actualColNames + "]";
if (nullability != null)
Assert.assertEquals("Number of items in expected ColumnNames and " +
"expected nullability arrays don't match; fix up the test",
expectedColNames.length, nullability.length);
if (expectedTypes != null)
Assert.assertEquals("Number of items in expected ColumnNames and " +
"expected ColumnTypes arrays don't match; fix up the test",
expectedColNames.length, expectedTypes.length);
if (expectedColNames.length == rsmd.getColumnCount()) {
// the count is the same, expect the names to be the same too
for (int i = 0; i < actualCols; i++)
{
Assert.assertEquals("Column names do not match:",
expectedColNames[i], rsmd.getColumnName(i+1));
if (expectedTypes != null)
Assert.assertEquals("Column types do not match for column "
+ (i+1),
expectedTypes[i], rsmd.getColumnType(i+1));
if (nullability != null) {
int expected = nullability[i] ?
ResultSetMetaData.columnNullable :
ResultSetMetaData.columnNoNulls;
Assert.assertEquals(
"Column nullability do not match for column " +
(i+1), expected, rsmd.isNullable(i+1));
}
}
}
else
{
for (int i = 0; i < expectedColNames.length; i++)
{
String expectedColName = expectedColNames[i];
boolean found=false;
for (int j = 0 ; j < rsmd.getColumnCount(); j++)
{
if (expectedColNames[i].equalsIgnoreCase(
rsmd.getColumnName(j+1)))
{
found = true;
if (expectedTypes != null)
Assert.assertEquals("Column Type does not match for column " +
expectedColNames[i] + "(" + (i) + ")",
expectedTypes[i], rsmd.getColumnType(j+1));
if (nullability != null) {
int expected = nullability[i] ?
ResultSetMetaData.columnNullable :
ResultSetMetaData.columnNoNulls;
Assert.assertEquals(
"Column nullability does not match for " +
"column " + (i), expected, rsmd.isNullable(j+1));
}
break;
}
else {
continue;
}
}
Assert.assertTrue("Missing an expected column: " + expectedColName +
"\n Expected: " + Arrays.toString(expectedColNames) +
"\n Actual : " + actualColNames,
found);
}
}
}
/**
* Takes a result set and an array of expected column types
* from java.sql.Types
* and asserts that the column types in the result
* set metadata match the number, order, and names of those
* in the array.
*
* No length information for variable length types
* can be passed. For ResultSets from JDBC DatabaseMetaData
* the specification only indicates the types of the
* columns, not the length.
*
* @param rs ResultSet for which we're checking column names.
* @param expectedTypes Array of expected column types.
*/
public static void assertColumnTypes(ResultSet rs,
int[] expectedTypes) throws SQLException
{
ResultSetMetaData rsmd = rs.getMetaData();
int actualCols = rsmd.getColumnCount();
Assert.assertEquals("Unexpected column count:",
expectedTypes.length, rsmd.getColumnCount());
for (int i = 0; i < actualCols; i++)
{
Assert.assertEquals("Column types do not match for column " + (i+1),
expectedTypes[i], rsmd.getColumnType(i+1));
}
}
/**
* Takes a Prepared Statement and an array of expected parameter types
* from java.sql.Types
* and asserts that the parameter types in the ParamterMetaData
* match the number and order of those
* in the array.
* @param ps PreparedStatement for which we're checking parameter names.
* @param expectedTypes Array of expected parameter types.
*/
public static void assertParameterTypes (PreparedStatement ps,
int[] expectedTypes) throws Exception
{
if ( vmSupportsJSR169() )
{
Assert.fail( "The assertParameterTypes() method only works on platforms which support ParameterMetaData." );
}
Object pmd = ps.getClass().getMethod( "getParameterMetaData", null ).invoke( ps, null );
int actualParams = ((Integer) pmd.getClass().getMethod( "getParameterCount", null ).invoke( pmd, null )).intValue();
Assert.assertEquals("Unexpected parameter count:",
expectedTypes.length, actualParams );
Method getParameterType = pmd.getClass().getMethod( "getParameterType", new Class[] { Integer.TYPE } );
for (int i = 0; i < actualParams; i++)
{
Assert.assertEquals
("Types do not match for parameter " + (i+1),
expectedTypes[i],
((Integer) getParameterType.invoke( pmd, new Object[] { i + 1 } )).intValue()
);
}
}
/**
* Check the nullability of the column definitions for
* the ResultSet matches the expected values.
* @param rs
* @param nullability
* @throws SQLException
*/
public static void assertNullability(ResultSet rs,
boolean[] nullability) throws SQLException
{
ResultSetMetaData rsmd = rs.getMetaData();
int actualCols = rsmd.getColumnCount();
Assert.assertEquals("Unexpected column count:",
nullability.length, rsmd.getColumnCount());
for (int i = 0; i < actualCols; i++)
{
int expected = nullability[i] ?
ResultSetMetaData.columnNullable : ResultSetMetaData.columnNoNulls;
Assert.assertEquals("Column nullability do not match for column " + (i+1),
expected, rsmd.isNullable(i+1));
}
}
/**
* Asserts a ResultSet returns a single row with a single
* column equal to the passed in String value. The value can
* be null to indicate SQL NULL. The comparision is made
* using assertFullResultSet in trimmed string mode.
* As a side effect, this method closes the ResultSet.
*/
public static void assertSingleValueResultSet(ResultSet rs,
String value) throws SQLException
{
String[] row = new String[] {value};
String[][] set = new String[][] {row};
assertFullResultSet(rs, set);
}
/**
* assertFullResultSet() using trimmed string comparisions.
* Equal to
* <code>
* assertFullResultSet(rs, expectedRows, true)
* </code>
* As a side effect, this method closes the ResultSet.
*/
public static void assertFullResultSet(ResultSet rs,
String [][] expectedRows)
throws SQLException
{
assertFullResultSet(rs, expectedRows, true);
}
/**
* Takes a result set and a two-dimensional array and asserts
* that the rows and columns in the result set match the number,
* order, and values of those in the array. Each row in
* the array is compared with the corresponding row in the
* result set. As a side effect, this method closes the ResultSet.
*
* Will throw an assertion failure if any of the following
* is true:
*
* 1. Expected vs actual number of columns doesn't match
* 2. Expected vs actual number of rows doesn't match
* 3. Any column in any row of the result set does not "equal"
* the corresponding column in the expected 2-d array. If
* "allAsTrimmedStrings" is true then the result set value
* will be retrieved as a String and compared, via the ".equals()"
* method, to the corresponding object in the array (with the
* assumption being that the objects in the array are all
* Strings). Otherwise the result set value will be retrieved
* and compared as an Object, which is useful when asserting
* the JDBC types of the columns in addition to their values.
*
* NOTE: It follows from #3 that the order of the rows in the
* in received result set must match the order of the rows in
* the received 2-d array. Otherwise the result will be an
* assertion failure.
*
* @param rs The actual result set.
* @param expectedRows 2-Dimensional array of objects representing
* the expected result set.
* @param allAsTrimmedStrings Whether or not to fetch (and compare)
* all values from the actual result set as trimmed Strings; if
* false the values will be fetched and compared as Objects. For
* more on how this parameter is used, see assertRowInResultSet().
*/
public static void assertFullResultSet(ResultSet rs,
Object [][] expectedRows, boolean allAsTrimmedStrings)
throws SQLException
{
assertFullResultSet( rs, expectedRows, allAsTrimmedStrings, true );
}
/**
* Takes a result set and a two-dimensional array and asserts
* that the rows and columns in the result set match the number,
* order, and values of those in the array. Each row in
* the array is compared with the corresponding row in the
* result set.
*
* Will throw an assertion failure if any of the following
* is true:
*
* 1. Expected vs actual number of columns doesn't match
* 2. Expected vs actual number of rows doesn't match
* 3. Any column in any row of the result set does not "equal"
* the corresponding column in the expected 2-d array. If
* "allAsTrimmedStrings" is true then the result set value
* will be retrieved as a String and compared, via the ".equals()"
* method, to the corresponding object in the array (with the
* assumption being that the objects in the array are all
* Strings). Otherwise the result set value will be retrieved
* and compared as an Object, which is useful when asserting
* the JDBC types of the columns in addition to their values.
*
* NOTE: It follows from #3 that the order of the rows in the
* in received result set must match the order of the rows in
* the received 2-d array. Otherwise the result will be an
* assertion failure.
*
* @param rs The actual result set.
* @param expectedRows 2-Dimensional array of objects representing
* the expected result set.
* @param allAsTrimmedStrings Whether or not to fetch (and compare)
* all values from the actual result set as trimmed Strings; if
* false the values will be fetched and compared as Objects. For
* more on how this parameter is used, see assertRowInResultSet().
* @param closeResultSet If true, the ResultSet is closed on the way out.
*/
public static void assertFullResultSet(ResultSet rs,
Object [][] expectedRows, boolean allAsTrimmedStrings, boolean closeResultSet)
throws SQLException
{
assertFullResultSetMinion(rs, expectedRows, allAsTrimmedStrings,
closeResultSet, null);
}
/**
* assertFullResultSet() using trimmed string comparisons.
* Equal to
* <code>
* assertFullResultSet(rs, expectedRows, true)
* </code>
*
* As a side effect, this method closes the result set.
* <p/>
* Additionally, also assert that the given warnings are seen. The array
* {@code warnings} should contain null or a warning (SQLState string). The
* array entry is asserted against the result set after having read the
* corresponding row in the result set. <b>NOTE: only asserted for embedded
* result sets, cf DERBY-159</b>
* <p/>
* For now, we only look at the first warning if there is a chain
* of warnings.
*/
public static void assertFullResultSet(ResultSet rs,
Object [][] expectedRows, String[] warnings)
throws SQLException
{
assertFullResultSetMinion(rs, expectedRows, true, true, warnings);
}
private static void assertFullResultSetMinion(
ResultSet rs,
Object [][] expectedRows,
boolean allAsTrimmedStrings,
boolean closeResultSet,
String[] warnings)
throws SQLException
{
int rows;
ResultSetMetaData rsmd = rs.getMetaData();
List<List<String>> seen = new ArrayList<List<String>>();
List<String> seenRow = new ArrayList<String>();
// Assert that we have the right number of columns. If we expect an
// empty result set, the expected column count is unknown, so don't
// check.
if (expectedRows.length > 0) {
try {
Assert.assertEquals("Unexpected column count:",
expectedRows[0].length, rsmd.getColumnCount());
} catch (AssertionFailedError e) {
throw addRsToReport(e, rsmd, seen, seenRow, rs);
}
}
for (rows = 0; rs.next(); rows++)
{
// Assert warnings on result set, but only for embedded, cf
// DERBY-159.
if (TestConfiguration.getCurrent().getJDBCClient().isEmbedded() &&
warnings != null) {
SQLWarning w = rs.getWarnings();
String wstr = null;
if (w != null) {
wstr = w.getSQLState();
}
try {
Assert.assertEquals(
"Warning assertion error on row " + (rows+1),
warnings[rows],
wstr);
} catch (AssertionFailedError e) {
throw addRsToReport(e, rsmd, seen, seenRow, rs);
}
}
/* If we have more actual rows than expected rows, don't
* try to assert the row. Instead just keep iterating
* to see exactly how many rows the actual result set has.
*/
if (rows < expectedRows.length)
{
// dumps itself in any assertion seem
assertRowInResultSet(rs, seen, seenRow, rows + 1,
expectedRows[rows], allAsTrimmedStrings);
}
seen.add(new ArrayList<String>(seenRow));
seenRow.clear();
}
if ( closeResultSet ) { rs.close(); }
// And finally, assert the row count.
try {
Assert.assertEquals("Unexpected row count:", expectedRows.length, rows);
} catch (AssertionFailedError e) {
throw addRsToReport(e, rsmd, seen, seenRow, rs);
}
}
/**
* Similar to assertFullResultSet(...) above, except that this
* method takes a BitSet and checks the received expectedRows
* against the columns referenced by the BitSet. So the assumption
* here is that expectedRows will only have as many columns as
* there are "true" bits in the received BitSet.
*
* This method is useful when we expect there to be a specific
* ordering on some column OC in the result set, but do not care
* about the ordering of the non-OC columns when OC is the
* same across rows. Ex. If we have the following results with
* an expected ordering on column J:
*
* I J
* - -
* a 1
* b 1
* c 2
* c 2
*
* Then this method allows us to verify that J is sorted as
* "1, 1, 2, 2" without having to worry about whether or not
* (a,1) comes before (b,1). The caller would simply pass in
* a BitSet whose content was {1} and an expectedRows array
* of {{"1"},{"1"},{"2"},{"2"}}.
*
* For now this method always does comparisons with
* "asTrimmedStrings" set to true, and always closes
* the result set.
*/
public static void assertPartialResultSet(ResultSet rs,
Object [][] expectedRows, BitSet colsToCheck)
throws SQLException
{
int rows;
final List<List<String>> seen = new ArrayList<List<String>>();
final List<String> seenRow = new ArrayList<String>();
final ResultSetMetaData rsmd = rs.getMetaData();
// Assert that we have the right number of columns. If we expect an
// empty result set, the expected column count is unknown, so don't
// check.
if (expectedRows.length > 0) {
Assert.assertEquals("Unexpected column count:",
expectedRows[0].length, colsToCheck.cardinality());
}
for (rows = 0; rs.next(); rows++)
{
/* If we have more actual rows than expected rows, don't
* try to assert the row. Instead just keep iterating
* to see exactly how many rows the actual result set has.
*/
if (rows < expectedRows.length)
{
assertRowInResultSet(rs, seen, seenRow, rows + 1,
expectedRows[rows], true, colsToCheck);
}
seen.add(new ArrayList<String>(seenRow));
seenRow.clear();
}
rs.close();
// And finally, assert the row count.
try {
Assert.assertEquals("Unexpected row count:", expectedRows.length, rows);
} catch (AssertionFailedError e) {
throw addRsToReport(e, rsmd, seen, seenRow, rs);
}
}
/**
* Assert that every column in the current row of the received
* result set matches the corresponding column in the received
* array. This means that the order of the columns in the result
* set must match the order of the values in expectedRow.
*
* <p>
* If the expected value for a given row/column is a SQL NULL,
* then the corresponding value in the array should be a Java
* null.
*
* <p>
* If a given row/column could have different values (for instance,
* because it contains a timestamp), the expected value of that
* row/column could be an object whose <code>equals()</code> method
* returns <code>true</code> for all acceptable values. (This does
* not work if one of the acceptable values is <code>null</code>.)
*
* @param rs Result set whose current row we'll check.
* @param seen The set of entirely read rows so far (IN semantics)
* @param seenRow The set of read columns in the current row so far
* (OUT semantics)
* @param rowNum Row number (w.r.t expected rows) that we're
* checking.
* @param expectedRow Array of objects representing the expected
* values for the current row.
* @param asTrimmedStrings Whether or not to fetch and compare
* all values from "rs" as trimmed Strings. If true then the
* value from rs.getString() AND the expected value will both
* be trimmed before comparison. If such trimming is not
* desired (ex. if we were testing the padding of CHAR columns)
* then this param should be FALSE and the expected values in
* the received array should include the expected whitespace.
* If for example the caller wants to check the padding of a
* CHAR(8) column, asTrimmedStrings should be FALSE and the
* expected row should contain the expected padding, such as
* "FRED ".
* @throws java.sql.SQLException
*/
private static void assertRowInResultSet(
ResultSet rs,
List<List<String>> seen,
List<String> seenRow,
int rowNum,
Object [] expectedRow,
boolean asTrimmedStrings) throws SQLException
{
assertRowInResultSet(
rs, seen, seenRow, rowNum, expectedRow, asTrimmedStrings, (BitSet)null);
}
/**
* See assertRowInResultSet(...) above.
*
* @param colsToCheck If non-null then for every bit b
* that is set in colsToCheck, we'll compare the (b+1)-th column
* of the received result set's current row to the i-th column
* of expectedRow, where 0 &lt;= i &lt; # bits set in colsToCheck.
* So if colsToCheck is { 0, 3 } then expectedRow should have
* two objects and we'll check that:
*
* expectedRow[0].equals(rs.getXXX(1));
* expectedRow[1].equals(rs.getXXX(4));
*
* If colsToCheck is null then the (i+1)-th column in the
* result set is compared to the i-th column in expectedRow,
* where 0 &lt;= i &lt; expectedRow.length.
*/
private static void assertRowInResultSet(
ResultSet rs,
List<List<String>> seen,
List<String> seenRow,
int rowNum,
Object [] expectedRow,
boolean asTrimmedStrings,
BitSet colsToCheck) throws SQLException
{
int cPos = 0;
ResultSetMetaData rsmd = rs.getMetaData();
for (int i = 0; i < expectedRow.length; i++)
{
cPos = (colsToCheck == null)
? (i+1)
: colsToCheck.nextSetBit(cPos) + 1;
Object obj;
if (asTrimmedStrings)
{
// Trim the expected value, if non-null.
if (expectedRow[i] != null)
expectedRow[i] = ((String)expectedRow[i]).trim();
/* Different clients can return different values for
* boolean columns--namely, 0/1 vs false/true. So in
* order to keep things uniform, take boolean columns
* and get the JDBC string version. Note: since
* Derby doesn't have a BOOLEAN type, we assume that
* if the column's type is SMALLINT and the expected
* value's string form is "true" or "false", then the
* column is intended to be a mock boolean column.
*/
if ((expectedRow[i] != null)
&& (rsmd.getColumnType(cPos) == Types.SMALLINT))
{
String s = expectedRow[i].toString();
if (s.equals("true") || s.equals("false"))
obj = (rs.getShort(cPos) == 0) ? "false" : "true";
else
obj = rs.getString(cPos);
}
else
{
obj = rs.getString(cPos);
}
// Trim the rs string.
if (obj != null)
obj = ((String)obj).trim();
}
else {
obj = rs.getObject(cPos);
}
seenRow.add(obj == null ? "null" : obj.toString());
boolean ok = (rs.wasNull() && (expectedRow[i] == null))
|| (!rs.wasNull()
&& (expectedRow[i] != null)
&& (expectedRow[i].equals(obj)
|| (obj instanceof byte[] // Assumes byte arrays
&& Arrays.equals((byte[] )obj,
(byte[] )expectedRow[i]))));
if (!ok)
{
Object expected = expectedRow[i];
Object found = obj;
if (obj instanceof byte[]) {
expected = bytesToString((byte[] )expectedRow[i]);
found = bytesToString((byte[] )obj);
}
try {
Assert.fail("Column value mismatch @ column '" +
rsmd.getColumnName(cPos) + "', row " + rowNum +
":\n Expected: >" + expected +
"<\n Found: >" + found + "<");
} catch (AssertionFailedError e) {
throw addRsToReport(e, rsmd, seen, seenRow, rs);
}
}
if (rs.wasNull()) {
assertResultColumnNullable(rs, seen, seenRow, cPos);
}
}
}
/**
* Assert two result sets have the same contents.
* MetaData is determined from rs1, thus if rs2 has extra
* columns they will be ignored. The metadata for the
* two ResultSets are not compared.
* <BR>
* The compete ResultSet is walked for both ResultSets,
* and they are both closed.
* <BR>
* Columns are compared as primitive ints or longs, Blob,
* Clobs or as Strings.
* @throws IOException
*/
public static void assertSameContents(ResultSet rs1, ResultSet rs2)
throws SQLException, IOException {
ResultSetMetaData rsmd = rs1.getMetaData();
int columnCount = rsmd.getColumnCount();
while (rs1.next()) {
Assert.assertTrue(rs2.next());
for (int col = 1; col <= columnCount; col++) {
switch (rsmd.getColumnType(col)) {
case Types.SMALLINT:
case Types.INTEGER:
Assert.assertEquals(rs1.getInt(col), rs2.getInt(col));
break;
case Types.BIGINT:
Assert.assertEquals(rs1.getLong(col), rs2.getLong(col));
break;
case Types.BLOB:
BaseJDBCTestCase.assertEquals(rs1.getBlob(col),
rs2.getBlob(col));
break;
case Types.CLOB:
BaseJDBCTestCase.assertEquals(rs1.getClob(col),
rs2.getClob(col));
break;
default:
Assert.assertEquals(rs1.getString(col), rs2.getString(col));
break;
}
Assert.assertEquals(rs1.wasNull(), rs2.wasNull());
}
}
Assert.assertFalse(rs2.next());
rs1.close();
rs2.close();
}
/**
* Assert that the ResultSet contains the same rows as the specified
* two-dimensional array. The order of the results is ignored. Convert the
* results to trimmed strings before comparing. The ResultSet object will
* be closed.
*
* @param rs the ResultSet to check
* @param expectedRows the expected rows
*/
public static void assertUnorderedResultSet(
ResultSet rs, String[][] expectedRows) throws SQLException {
assertUnorderedResultSet(rs, expectedRows, true);
}
/**
* Assert that the ResultSet contains the same rows as the specified
* two-dimensional array. The order of the results is ignored. Objects are
* read out of the ResultSet with the <code>getObject()</code> method and
* compared with <code>equals()</code>. If the
* <code>asTrimmedStrings</code> is <code>true</code>, the objects are read
* with <code>getString()</code> and trimmed before they are compared. The
* ResultSet object will be closed when this method returns.
*
* @param rs the ResultSet to check
* @param expectedRows the expected rows
* @param asTrimmedStrings whether the object should be compared as trimmed
* strings
*/
public static void assertUnorderedResultSet(
ResultSet rs, Object[][] expectedRows, boolean asTrimmedStrings)
throws SQLException {
assertRSContains(rs, expectedRows, asTrimmedStrings, true);
}
/**
* Asserts that the {@code ResultSet} contains the rows specified by the
* two-dimensional array.
* <p>
* The order of the rows are ignored, and there may be more rows in the
* result set than in the array. All values are compared as trimmed strings.
*
* @param rs the result set to check
* @param expectedRows the rows that must exist in the result set
* @throws SQLException if accessing the result set fails
*/
public static void assertResultSetContains(
ResultSet rs, Object[][] expectedRows)
throws SQLException {
assertRSContains(rs, expectedRows, true, false);
}
/**
* Asserts that the {@code ResultSet} contains the rows specified by the
* two-dimensional array.
*
* @param rs the result set to check
* @param expectedRows the rows that must exist in the result set
* @param asTrimmedStrings whether the objects should be compared as
* trimmed strings
* @param rowCountsMustMatch whether the number of rows must be the same in
* the result set and the array of expected rows
* @throws SQLException if accessing the result set fails
*/
private static void assertRSContains(
ResultSet rs, Object[][] expectedRows, boolean asTrimmedStrings,
boolean rowCountsMustMatch)
throws SQLException {
if (expectedRows.length == 0) {
if (rowCountsMustMatch) {
assertEmpty(rs);
}
return;
}
ResultSetMetaData rsmd = rs.getMetaData();
List<List<String>> seen = new ArrayList<List<String>>();
List<String> seenRow = new ArrayList<String>();
try {
Assert.assertEquals("Unexpected column count",
expectedRows[0].length, rsmd.getColumnCount());
} catch (AssertionFailedError e) {
throw addRsToReport(e, rsmd, seen, seenRow, rs);
}
List<List<String>> expected =
new ArrayList<List<String>>(expectedRows.length);
for (int i = 0; i < expectedRows.length; i++) {
Assert.assertEquals("Different column count in expectedRows",
expectedRows[0].length, expectedRows[i].length);
List<String> row = new ArrayList<String>(expectedRows[i].length);
for (int j = 0; j < expectedRows[i].length; j++) {
String val = (String) expectedRows[i][j];
row.add(asTrimmedStrings ?
(val == null ? null : val.trim()) :
val);
}
expected.add(row);
}
List<List<String>> actual =
new ArrayList<List<String>>(expectedRows.length);
while (rs.next()) {
List<String> row = new ArrayList<String>(expectedRows[0].length);
for (int i = 1; i <= expectedRows[0].length; i++) {
String s = rs.getString(i);
seenRow.add(s);
row.add(asTrimmedStrings ?
(s == null ? null : s.trim()) :
s);
if (rs.wasNull()) {
assertResultColumnNullable(rs, seen, seenRow, i);
}
}
actual.add(row);
seen.add(new ArrayList<String>(seenRow));
seenRow.clear();
}
rs.close();
try {
if (rowCountsMustMatch) {
String message = "Unexpected row count, expected: " +
expectedRows.length + ", actual: " + actual.size() + "\n" +
"\t expected rows: \n\t\t" + expected +
"\n\t actual result: \n\t\t" + actual + "\n";
Assert.assertEquals(message,
expectedRows.length, actual.size());
}
if ( !actual.containsAll(expected) )
{
expected.removeAll( actual );
BaseTestCase.println
( "These expected rows don't appear in the actual result: " + expected );
String message = "Missing rows in ResultSet; \n\t expected rows: \n\t\t"
+ expected + "\n\t actual result: \n\t\t" + actual;
Assert.fail( message );
}
} catch (AssertionFailedError e) {
throw addRsToReport(e, rsmd, seen, seenRow, rs);
}
}
/**
* Asserts that the current schema is the same as the one specified.
*
* @param con connection to check schema in
* @param schema expected schema name
* @throws SQLException if something goes wrong
*/
public static void assertCurrentSchema(Connection con, String schema)
throws SQLException {
Statement stmt = con.createStatement();
try {
JDBC.assertSingleValueResultSet(
stmt.executeQuery("VALUES CURRENT SCHEMA"), schema);
} finally {
stmt.close();
}
}
/**
* Asserts that the current user is the same as the one specified.
*
* @param con connection to check schema in
* @param user expected user name
* @throws SQLException if something goes wrong
*/
public static void assertCurrentUser(Connection con, String user)
throws SQLException {
Statement stmt = con.createStatement();
try {
JDBC.assertSingleValueResultSet(
stmt.executeQuery("VALUES CURRENT_USER"), user);
} finally {
stmt.close();
}
}
/**
* Convert byte array to String.
* Each byte is converted to a hexadecimal string representation.
*
* @param ba Byte array to be converted.
* @return Hexadecimal string representation. Returns null on null input.
*/
private static String bytesToString(byte[] ba)
{
if (ba == null) return null;
StringBuffer s = new StringBuffer();
for (int i = 0; i < ba.length; ++i) {
s.append(Integer.toHexString(ba[i] & 0x00ff));
}
return s.toString();
}
/**
* Escape a non-qualified name so that it is suitable
* for use in a SQL query executed by JDBC.
*/
public static String escape(String name)
{
StringBuffer buffer = new StringBuffer(name.length() + 2);
buffer.append('"');
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
// escape double quote characters with an extra double quote
if (c == '"') buffer.append('"');
buffer.append(c);
}
buffer.append('"');
return buffer.toString();
}
/**
* Compress 2 adjacent (single or double) quotes into a single (s or d)
* quote when found in the middle of a String.
*
* NOTE: """" or '''' will be compressed into "" or ''.
* This function assumes that the leading and trailing quote from a
* string or delimited identifier have already been removed.
* @param source string to be compressed
* @param quotes string containing two single or double quotes.
* @return String where quotes have been compressed
*/
private static String compressQuotes(String source, String quotes)
{
String result = source;
int index;
/* Find the first occurrence of adjacent quotes. */
index = result.indexOf(quotes);
/* Replace each occurrence with a single quote and begin the
* search for the next occurrence from where we left off.
*/
while (index != -1) {
result = result.substring(0, index + 1) +
result.substring(index + 2);
index = result.indexOf(quotes, index + 1);
}
return result;
}
/**
* Convert a SQL identifier to case normal form.
*
* Normalize a SQL identifer, up-casing if <regular identifer>,
* and handling of <delimited identifer> (SQL 2003, section 5.2).
* The normal form is used internally in Derby.
*/
public static String identifierToCNF(String id)
{
if (id == null || id.length() == 0) {
return id;
}
if (id.charAt(0) == '"' &&
id.length() >= 3 &&
id.charAt(id.length() - 1) == '"') {
// assume syntax is OK, thats is, any quotes inside are doubled:
return compressQuotes(
id.substring(1, id.length() - 1), "\"\"");
} else {
return id.toUpperCase(Locale.ENGLISH);
}
}
/**
* Escape a schama-qualified name so that it is suitable
* for use in a SQL query executed by JDBC.
*/
public static String escape(String schema, String name)
{
return escape(schema) + "." + escape(name);
}
/**
* Return Type name from jdbc type
*
* @param jdbcType jdbc type to translate
*/
public static String sqlNameFromJdbc(int jdbcType) {
switch (jdbcType) {
case Types.BIT : return "Types.BIT";
case Types.BOOLEAN : return "Types.BOOLEAN";
case Types.TINYINT : return "Types.TINYINT";
case Types.SMALLINT : return "SMALLINT";
case Types.INTEGER : return "INTEGER";
case Types.BIGINT : return "BIGINT";
case Types.FLOAT : return "Types.FLOAT";
case Types.REAL : return "REAL";
case Types.DOUBLE : return "DOUBLE";
case Types.NUMERIC : return "Types.NUMERIC";
case Types.DECIMAL : return "DECIMAL";
case Types.CHAR : return "CHAR";
case Types.VARCHAR : return "VARCHAR";
case Types.LONGVARCHAR : return "LONG VARCHAR";
case Types.CLOB : return "CLOB";
case Types.DATE : return "DATE";
case Types.TIME : return "TIME";
case Types.TIMESTAMP : return "TIMESTAMP";
case Types.BINARY : return "CHAR () FOR BIT DATA";
case Types.VARBINARY : return "VARCHAR () FOR BIT DATA";
case Types.LONGVARBINARY: return "LONG VARCHAR FOR BIT DATA";
case Types.BLOB : return "BLOB";
case Types.OTHER : return "Types.OTHER";
case Types.NULL : return "Types.NULL";
default : return String.valueOf(jdbcType);
}
}
/**
* Get run-time statistics and check that a sequence of string exist in the
* statistics, using the given statement.
* <p/>
* For the format of the strings, see RuntimeStatisticsParser#assertSequence
*
* @see RuntimeStatisticsParser#assertSequence
*
* @param s the statement presumed to just have been executed, and for
* which we want to check the run-time statistics
* @param sequence the sequnce of strings we expect to see in the run-time
* statistics
* @throws SQLException standard
*/
public static void checkPlan(Statement s, String[] sequence)
throws SQLException {
ResultSet rs = s.executeQuery(
"values SYSCS_UTIL.SYSCS_GET_RUNTIMESTATISTICS()");
rs.next();
String rts = rs.getString(1);
rs.close();
RuntimeStatisticsParser rtsp = new RuntimeStatisticsParser(rts);
rtsp.assertSequence(sequence);
}
private static AssertionFailedError addRsToReport(
AssertionFailedError afe,
ResultSetMetaData rsmd,
List<List<String>> seen,
List<String> seenRow,
ResultSet rs) throws SQLException {
try {
if (rs == null) {
return BaseTestCase.newAssertionFailedError(
afe.getMessage() + "\n<NULL>", afe);
}
final int c = rsmd.getColumnCount();
StringBuilder heading = new StringBuilder(" ");
StringBuilder underline = new StringBuilder(" ");
// Display column headings
for (int i=1; i<= c; i++) {
if (i > 1) {
heading.append(",");
underline.append(" ");
}
int len = heading.length();
heading.append(rsmd.getColumnLabel(i));
len = heading.length() - len;
for (int j = len; j > 0; j--) {
underline.append("-");
}
}
heading.append("\n");
underline.append("\n");
StringBuilder rowImg = new StringBuilder();
rowImg.append(afe.getMessage()).
append("\n\n").
append(heading.toString()).
append(underline.toString());
if (!rs.isClosed()) {
final int s = seenRow.size();
// Get any rest of columns of current row
for (int i=0; i < c - s; i++) {
String column = null;
try {
column = rs.getString(s + i + 1);
} catch (SQLException e) {
// We may not yet have called next?
if (e.getSQLState().equals("24000")) {
if (rs.next()) {
column = rs.getString(s + i + 1);
} else {
break;
}
}
}
seenRow.add(column);
}
if (seenRow.size() > 0) {
seen.add(new ArrayList<String>(seenRow));
seenRow.clear();
}
// Get any remaining rows
while (rs.next()) {
for (int i = 0; i < c; i++) {
seenRow.add(rs.getString(i + 1));
}
seen.add(new ArrayList<String>(seenRow));
seenRow.clear();
}
}
// Display data
for (List<String> row : seen) {
rowImg.append(" ").
append(row.toString()).
append("\n");
}
return BaseTestCase.newAssertionFailedError(rowImg.toString(), afe);
} catch (Throwable t) {
// Something went wrong when adding the ResultSet to the error
// message. Don't return this error, as it would shadow the
// original error. Print it to help debugging.
BaseTestCase.printStackTrace(t);
// Return the original error.
return afe;
}
}
}