blob: 87341d8b54ccf983d57f04b96ed8de13a8f508b5 [file] [log] [blame]
/*
*
* Derby - Class BaseJDBCTestCase
*
* 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.ByteArrayInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.PrintStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.net.URL;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
import org.apache.derby.iapi.sql.execute.RunTimeStatistics;
import org.apache.derby.impl.jdbc.EmbedConnection;
import org.apache.derby.tools.ij;
import org.apache.derbyTesting.functionTests.util.TestNullOutputStream;
/**
* Base class for JDBC JUnit tests.
* A method for getting a default connection is provided, along with methods
* for telling if a specific JDBC client is used.
*/
public abstract class BaseJDBCTestCase
extends BaseTestCase {
private static final boolean ORDERED = true;
private static final boolean UNORDERED = false;
/**
* Maintain a single connection to the default
* database, opened at the first call to getConnection.
* Typical setup will just require a single connection.
* @see BaseJDBCTestCase#getConnection()
*/
private Connection conn;
/**
* Maintain a list of statement objects that
* were returned by utility methods and close
* them at teardown.
*/
private List<Statement> statements;
/**
* Maintain a list of connection objects that
* were returned by utility methods and close
* them at teardown.
*/
private List<Connection> connections;
/**
* Create a test case with the given name.
*
* @param name of the test case.
*/
public BaseJDBCTestCase(String name) {
super(name);
}
/**
* Obtain the connection to the default database.
* This class maintains a single connection returned
* by this class, it is opened on the first call to
* this method. Subsequent calls will return the same
* connection object unless it has been closed. In that
* case a new connection object will be returned.
* <P>
* The tearDown method will close the connection if
* it is open.
* <BR>
* The connection will be initialized by calling initializeConnection.
* A sub-class may provide an implementation of initializeConnection
* to ensure its connections are in a consistent state that is different
* to the default.
* @see #openDefaultConnection()
*/
public Connection getConnection() throws SQLException
{
if (conn != null)
{
if (!conn.isClosed())
return conn;
conn = null;
}
return conn = openDefaultConnection();
}
/**
* Allow a sub-class to initialize a connection to provide
* consistent connection state for its tests. Called once
* for each time these method calls open a connection:
* <UL>
* <LI> getConnection()
* <LI> openDefaultConnection()
* <LI> openConnection(database)
* <LI> getDefaultConnection(String connAttrs)
* </UL>
* Default action is to not modify the connection's state from
* the initialization provided by the data source.
* @param conn Connection to be intialized
* @throws SQLException Error setting the initial state.
*/
protected void initializeConnection(Connection conn) throws SQLException
{
}
/**
* Utility method to create a Statement using the connection
* returned by getConnection.
* The returned statement object will be closed automatically
* at tearDown() but may be closed earlier by the test if required.
* @return Statement object from getConnection.createStatement()
* @throws SQLException
*/
public Statement createStatement() throws SQLException
{
Statement s = getConnection().createStatement();
addStatement(s);
return s;
}
/**
* Add a statement into the list we will close
* at tearDown.
*/
private void addStatement(Statement s)
{
if (statements == null)
statements = new ArrayList<Statement>();
statements.add(s);
}
/**
* Add connection to the list. We will close at tearDown
* @param c
*/
private void addConnection(Connection c)
{
if (connections == null)
connections = new ArrayList<Connection>();
connections.add(c);
}
/**
* Close a statement and remove it from the list of statements to close
* at tearDown(). Useful for test cases that create a large number of
* statements that are only used for a short time, as the memory footprint
* may become big if all the statements are held until tearDown().
*
* @param s the statement to close and forget
* @throws SQLException if closing the statement fails
*/
public void closeStatement(Statement s) throws SQLException {
s.close();
if (statements != null) {
statements.remove(s);
}
}
/**
* Utility method to create a Statement using the connection
* returned by getConnection.
* The returned statement object will be closed automatically
* at tearDown() but may be closed earlier by the test if required.
* @return Statement object from
* getConnection.createStatement(resultSetType, resultSetConcurrency)
* @throws SQLException
*/
public Statement createStatement(int resultSetType,
int resultSetConcurrency) throws SQLException
{
Statement s =
getConnection().createStatement(resultSetType, resultSetConcurrency);
addStatement(s);
return s;
}
/**
* Utility method to create a Statement using the connection
* returned by getConnection.
* @return Statement object from
* getConnection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability)
* @throws SQLException
*/
public Statement createStatement(int resultSetType,
int resultSetConcurrency,
int resultSetHoldability) throws SQLException
{
return getConnection().createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
}
/**
* Utility method to create a PreparedStatement using the connection
* returned by getConnection.
* The returned statement object will be closed automatically
* at tearDown() but may be closed earlier by the test if required.
* @return Statement object from
* getConnection.prepareStatement(sql)
* @throws SQLException
*/
public PreparedStatement prepareStatement(String sql) throws SQLException
{
PreparedStatement ps = getConnection().prepareStatement(sql);
addStatement(ps);
return ps;
}
/**
* Utility method to create a PreparedStatement using the connection
* returned by getConnection with result set type and concurrency.
* The returned statement object will be closed automatically
* at tearDown() but may be closed earlier by the test if required.
* @return Statement object from
* getConnection.prepareStatement(sql)
* @throws SQLException
*/
public PreparedStatement prepareStatement(String sql,
int resultSetType, int resultSetConcurrency) throws SQLException
{
PreparedStatement ps = getConnection().prepareStatement(sql,
resultSetType, resultSetConcurrency);
addStatement(ps);
return ps;
}
/**
* Utility method to create a PreparedStatement using the connection
* returned by getConnection with result set type and concurrency.
* The returned statement object will be closed automatically
* at tearDown() but may be closed earlier by the test if required.
* @return Statement object from
* getConnection.prepareStatement(sql)
* @throws SQLException
*/
public PreparedStatement prepareStatement(String sql,
int resultSetType, int resultSetConcurrency,
int resultSetHoldability) throws SQLException
{
PreparedStatement ps = getConnection().prepareStatement(sql,
resultSetType, resultSetConcurrency, resultSetHoldability);
addStatement(ps);
return ps;
}
/**
* Utility method to create a PreparedStatement using the connection
* returned by getConnection and a flag that signals the driver whether
* the auto-generated keys produced by this Statement object should be
* made available for retrieval.
* The returned statement object will be closed automatically
* at tearDown() but may be closed earlier by the test if required.
* @return Statement object from
* prepareStatement(sql, autoGeneratedKeys)
* @throws SQLException
*/
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
throws SQLException
{
PreparedStatement ps =
getConnection().prepareStatement(sql, autoGeneratedKeys);
addStatement(ps);
return ps;
}
/**
* Utility method to create a PreparedStatement using the connection
* returned by getConnection and an array of column indexes that
* indicates which auto-generated keys produced by this Statement
* object should be made available for retrieval.
* The returned statement object will be closed automatically
* at tearDown() but may be closed earlier by the test if required.
*
* @return Statement object from:
* prepareStatement(sql, columnIndexes)
*
* @throws SQLException
*/
public PreparedStatement prepareStatement(String sql,
int [] columnIndexes) throws SQLException
{
PreparedStatement ps =
getConnection().prepareStatement(sql, columnIndexes);
addStatement(ps);
return ps;
}
/**
* Utility method to create a PreparedStatement using the connection
* returned by getConnection and an array of column names that
* indicates which auto-generated keys produced by this Statement
* object should be made available for retrieval.
* The returned statement object will be closed automatically
* at tearDown() but may be closed earlier by the test if required.
*
* @return Statement object from:
* prepareStatement(sql, columnNames)
*
* @throws SQLException
*/
public PreparedStatement prepareStatement(String sql,
String [] columnNames) throws SQLException
{
PreparedStatement ps =
getConnection().prepareStatement(sql, columnNames);
addStatement(ps);
return ps;
}
/**
* Utility method to create a CallableStatement using the connection
* returned by getConnection.
* The returned statement object will be closed automatically
* at tearDown() but may be closed earlier by the test if required.
* @return Statement object from
* getConnection().prepareCall(sql)
* @throws SQLException
*/
public CallableStatement prepareCall(String sql) throws SQLException
{
CallableStatement cs =
getConnection().prepareCall(sql);
addStatement(cs);
return cs;
}
/**
* Utility method to create a CallableStatement using the connection
* returned by getConnection.
* The returned statement object will be closed automatically
* at tearDown() but may be closed earlier by the test if required.
* @return Statement object from
* getConnection().prepareCall(sql, resultSetType, resultSetConcurrency)
* @throws SQLException
*/
public CallableStatement prepareCall(String sql,
int resultSetType,
int resultSetConcurrency) throws SQLException
{
CallableStatement cs = getConnection().prepareCall(sql, resultSetType,
resultSetConcurrency);
addStatement(cs);
return cs;
}
/**
* Utility method to create a CallableStatement using the connection
* returned by getConnection.
* The returned statement object will be closed automatically
* at tearDown() but may be closed earlier by the test if required.
* @return Statement object from
* getConnection().prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability)
* @throws SQLException
*/
public CallableStatement prepareCall(String sql,
int resultSetType,
int resultSetConcurrency,
int resultSetHoldability) throws SQLException
{
CallableStatement cs = getConnection().prepareCall(sql,
resultSetType, resultSetConcurrency, resultSetHoldability);
addStatement(cs);
return cs;
}
/**
* Utility method to set auto commit behaviour.
* @param commit false if autoCommit should be disabled.
*/
public void setAutoCommit(boolean commit) throws SQLException {
getConnection().setAutoCommit(commit);
}
/**
* Utility method to commit using the connection
* returned by getConnection.
* @throws SQLException
*/
public void commit() throws SQLException
{
getConnection().commit();
}
/**
* Utility method to rollback using the connection
* returned by getConnection.
* @throws SQLException
*/
public void rollback() throws SQLException
{
getConnection().rollback();
}
/**
* <p>
* Run the bare test, including {@code setUp()} and {@code tearDown()}.
* </p>
*
* <p>
* Subclasses that want to override {@code runBare()}, should override
* this method instead. Overriding this method shouldn't be necessary
* except in very special cases. Override {@code setUp()} and
* {@code tearDown()} instead if possible.
* </p>
*
* <p>
* The overridden method would typically want to call
* {@code super.runBareOverridable()} to actually run the test.
* </p>
*/
protected void runBareOverridable() throws Throwable {
super.runBare();
}
/**
* <p>
* Run the bare test, including {@code setUp()} and {@code tearDown()},
* and finally verify that the cached connection has been released.
* </p>
*
* <p>
* This method is final to prevent subclasses from accidentally bypassing
* the assert that checks if the cached connection has been released.
* Subclasses that want to override the method, should override
* {@link #runBareOverridable()} instead.
* </p>
*/
public final void runBare() throws Throwable {
runBareOverridable();
// It's quite common to forget to call super.tearDown() when
// overriding tearDown() in sub-classes.
assertNull(
"Connection should be null by now. " +
"Missing call to super.tearDown()?", conn);
}
/**
* Tear down this fixture, sub-classes should call
* super.tearDown(). This cleans up and closes the connection
* if it is open and any statement objects returned through
* the utility methods.
*/
protected void tearDown()
throws java.lang.Exception
{
if (statements != null) {
for (Statement s : statements) {
s.close();
}
// Allow gc'ing of all those statements.
statements = null;
}
if (connections != null) {
for (Connection c : connections) {
JDBC.cleanup(c);
}
// Allow gc'ing of all those connections.
connections = null;
}
conn = null;
}
/**
* Open a connection to the default database.
* If the database does not exist, it will be created.
* A default username and password will be used for the connection.
*
* The connection will be initialized by calling initializeConnection.
* A sub-class may provide an implementation of initializeConnection
* to ensure its connections are in a consistent state that is different
* to the default.
*
* @return connection to default database.
* @see TestConfiguration#openDefaultConnection()
* @see BaseJDBCTestCase#initializeConnection(Connection)
*/
public Connection openDefaultConnection()
throws SQLException {
Connection conn = getTestConfiguration().openDefaultConnection();
addConnection(conn);
initializeConnection(conn);
return conn;
}
/**
* Open a connection to the default database for the given configuration.
* If the database does not exist, it will be created. A default username
* and password will be used for the connection.
*
* The connection will be initialized by calling initializeConnection.
* A sub-class may provide an implementation of initializeConnection
* to ensure its connections are in a consistent state that is different
* to the default.
* @param tc test configuration to use
* @return connection to default database for the configuration
* @see TestConfiguration#openDefaultConnection()
* @see BaseJDBCTestCase#initializeConnection(Connection)
*/
public Connection openDefaultConnection(TestConfiguration tc)
throws SQLException {
Connection conn = tc.openDefaultConnection();
addConnection(conn);
initializeConnection(conn);
return conn;
}
/**
* Open a connection to the current default database using the
* specified user name and password.
* <BR>
* This connection is not
* automatically closed on tearDown, the test fixture must
* ensure the connection is closed.
*
* The connection will be initialized by calling initializeConnection.
* A sub-class may provide an implementation of initializeConnection
* to ensure its connections are in a consistent state that is different
* to the default.
* @see BaseJDBCTestCase#initializeConnection(Connection)
*/
public Connection openDefaultConnection(String user, String password)
throws SQLException
{
Connection conn = getTestConfiguration().openDefaultConnection(user,
password);
addConnection(conn);
initializeConnection(conn);
return conn;
}
/**
* Open a connection to the current default database using the
* specified user name. The password is a function of
* the user name and the password token setup by the
* builtin authentication decorators.
* <BR>
* If the fixture is not wrapped in one of the decorators
* that setup BUILTIN authentication then the password
* is a function of the user name and the empty string
* as the password token. This mode is not recommended.
*
* <BR>
* This connection is not
* automaticaly closed on tearDown, the test fixture must
* ensure the connection is closed.
* <BR>
* The connection will be initialized by calling initializeConnection.
* A sub-class may provide an implementation of initializeConnection
* to ensure its connections are in a consistent state that is different
* to the default.
*
* @see DatabasePropertyTestSetup#builtinAuthentication(Test, String[], String)
* @see TestConfiguration#sqlAuthorizationDecorator(Test, String[], String)
* @see BaseJDBCTestCase#initializeConnection(Connection)
*/
public Connection openUserConnection(String user) throws SQLException
{
return openDefaultConnection(user,
getTestConfiguration().getPassword(user));
}
/**
* Open a connection to the specified database.
* If the database does not exist, it will be created.
* A default username and password will be used for the connection.
* Requires that the test has been decorated with a
* additionalDatabaseDecorator with the matching name.
* <BR>
* The connection will be initialized by calling initializeConnection.
* A sub-class may provide an implementation of initializeConnection
* to ensure its connections are in a consistent state that is different
* to the default.
* @return connection to default database.
* @see TestConfiguration#additionalDatabaseDecorator(Test, String)
* @see BaseJDBCTestCase#initializeConnection(Connection)
*/
public Connection openConnection(String databaseName)
throws SQLException {
Connection conn = getTestConfiguration().openConnection(databaseName);
addConnection(conn);
initializeConnection(conn);
return conn;
}
/**
* Run a SQL script through ij discarding the output
* using this object's default connection. Intended for
* setup scripts.
* @throws UnsupportedEncodingException
* @throws SQLException
*/
public int runScript(InputStream script, String encoding)
throws UnsupportedEncodingException, SQLException
{
// Sink output.
OutputStream sink = new TestNullOutputStream();
// Use the same encoding as the input for the output.
return ij.runScript(getConnection(), script, encoding,
sink, encoding);
}
/**
* Run a SQL script through ij discarding the output
* using this object's default connection. Intended for
* setup scripts.
* @return Number of errors executing the script
* @throws UnsupportedEncodingException
* @throws PrivilegedActionException
* @throws SQLException
*/
public int runScript(String resource,String encoding)
throws UnsupportedEncodingException, SQLException,
PrivilegedActionException,IOException
{
URL sql = getTestResource(resource);
assertNotNull("SQL script missing: " + resource, sql);
InputStream sqlIn = openTestResource(sql);
Connection conn = getConnection();
int numErrors = runScript(sqlIn,encoding);
sqlIn.close();
if (!conn.isClosed() && !conn.getAutoCommit())
conn.commit();
return numErrors;
}
/**
* Run a set of SQL commands from a String discarding the output.
* Commands are separated by a semi-colon. Connection used
* is this objects default connection.
* @param sqlCommands
* @return Number of errors executing the script.
* @throws UnsupportedEncodingException
* @throws SQLException
*/
public int runSQLCommands(String sqlCommands)
throws UnsupportedEncodingException, SQLException
{
byte[] raw = sqlCommands.getBytes("UTF-8");
ByteArrayInputStream in = new ByteArrayInputStream(raw);
return runScript(in, "UTF-8");
}
/**
* Tell if the client is embedded.
*
* @return <code>true</code> if using the embedded client
* <code>false</code> otherwise.
*/
public static boolean usingEmbedded() {
return TestConfiguration.getCurrent().getJDBCClient().isEmbedded();
}
/**
* Tell if the client is DerbyNetClient.
*
* @return <code>true</code> if using the DerbyNetClient client
* <code>false</code> otherwise.
*/
public static boolean usingDerbyNetClient() {
return TestConfiguration.getCurrent().getJDBCClient().isDerbyNetClient();
}
/**
* Tell if the client is DB2Client.
*
* @return <code>true</code> if using the DB2 client driver,
* <code>false</code> otherwise.
*/
public static boolean usingDB2Client() {
return TestConfiguration.getCurrent().getJDBCClient().isDB2Client();
}
/**
* Get the value of a database property using the default connection
* @param propertyName Property key
* @return null if the property is not set at the database level,
* otherwise the value of the property.
* @throws SQLException
*/
public String getDatabaseProperty(String propertyName) throws SQLException
{
PreparedStatement ps = prepareStatement(
"VALUES SYSCS_UTIL.SYSCS_GET_DATABASE_PROPERTY(?)");
ps.setString(1, propertyName);
ResultSet rs = ps.executeQuery();
rs.next();
String val = rs.getString(1);
rs.close();
closeStatement(ps);
return val;
}
/**
* Assert equality between two <code>Blob</code> objects.
* If both input references are <code>null</code>, they are considered
* equal. The same is true if both blobs have <code>null</code>-streams.
*
* @param b1 first <code>Blob</code>.
* @param b2 second <code>Blob</code>.
* @throws AssertionFailedError if blobs are not equal.
* @throws IOException if reading or closing a stream fails
* @throws SQLException if obtaining a stream fails
*/
public static void assertEquals(Blob b1, Blob b2)
throws IOException, SQLException {
if (b1 == null || b2 == null) {
assertNull("Blob b2 is null, b1 is not", b1);
assertNull("Blob b1 is null, b2 is not", b2);
return;
}
assertEquals("Blobs have different lengths",
b1.length(), b2.length());
InputStream is1 = b1.getBinaryStream();
InputStream is2 = b2.getBinaryStream();
if (is1 == null || is2 == null) {
assertNull("Blob b2 has null-stream, blob b1 doesn't", is1);
assertNull("Blob b1 has null-stream, blob b2 doesn't", is2);
return;
}
// wrap buffered stream around the binary stream
is1 = new BufferedInputStream(is1);
is2 = new BufferedInputStream(is2);
long index = 1;
int by1 = is1.read();
int by2 = is2.read();
do {
// Avoid string concatenation for every byte in the stream.
if (by1 != by2) {
assertEquals("Blobs differ at index " + index,
by1, by2);
}
index++;
by1 = is1.read();
by2 = is2.read();
} while ( by1 != -1 || by2 != -1);
is1.close();
is2.close();
}
/**
* Assert equality between two <code>Clob</code> objects.
* If both input references are <code>null</code>, they are considered
* equal. The same is true if both clobs have <code>null</code>-streams.
*
* @param c1 first <code>Clob</code>.
* @param c2 second <code>Clob</code>.
* @throws AssertionFailedError if clobs are not equal.
* @throws IOException if reading or closing a stream fails
* @throws SQLException if obtaining a stream fails
*/
public static void assertEquals(Clob c1, Clob c2)
throws IOException, SQLException {
if (c1 == null || c2 == null) {
assertNull("Clob c2 is null, c1 is not", c1);
assertNull("Clob c1 is null, c2 is not", c2);
return;
}
assertEquals("Clobs have different lengths",
c1.length(), c2.length());
Reader r1 = c1.getCharacterStream();
assertNotNull(r1); // java.sql.Blob object cannot represent NULL
Reader r2 = c2.getCharacterStream();
assertNotNull(r2); // java.sql.Blob object cannot represent NULL
// wrap buffered reader around the character stream
r1 = new BufferedReader(r1);
r2 = new BufferedReader(r2);
long index = 1;
int ch1 = r1.read();
int ch2 = r2.read();
do {
// Avoid string concatenation for every char in the stream.
if (ch1 != ch2) {
assertEquals("Clobs differ at index " + index,
ch1, ch2);
}
index++;
ch1 = r1.read();
ch2 = r2.read();
} while (ch1 != -1 || ch2 != -1);
r1.close();
r2.close();
}
/**
* Assert equality between two <code>java.sql.Time</code> objects.
* If both input references are <code>null</code>, they are considered
* equal.
*
* @param msg String with message to supply with AssertionFailedError
* @param t1 first java.sql.Time object.
* @param t2 second java.sql.Time object.
* @throws AssertionFailedError if Time objects are not equal.
*/
public static void assertEquals(String msg, Time t1, Time t2) {
if(null == t1 && null == t2) {
return;
}
assertNotNull(msg, t1);
assertNotNull(msg, t2);
assertEquals(msg, t1.toString(), t2.toString());
}
/**
* Assert that SQLState is as expected. If the SQLState for
* the top-level exception doesn't match, look for nested
* exceptions and, if there are any, see if they have the
* desired SQLState.
*
* @param message message to print on failure.
* @param expected the expected SQLState.
* @param exception the exception to check the SQLState of.
*/
public static void assertSQLState(String message,
String expected,
SQLException exception) {
// Make sure exception is not null. We want to separate between a
// null-exception object, and a null-SQLState.
assertNotNull("Exception cannot be null when asserting on SQLState",
exception);
try {
String state = exception.getSQLState();
if ( state != null )
assertTrue("The exception's SQL state must be five characters long",
state.length() == 5);
if ( expected != null )
assertTrue("The expected SQL state must be five characters long",
expected.length() == 5);
assertEquals(message, expected, state);
} catch (AssertionFailedError e) {
// Save the SQLException
e.initCause(exception);
if (usingDB2Client())
{
/* For JCC the error message is a series of tokens representing
* different things like SQLSTATE, SQLCODE, nested SQL error
* message, and nested SQL state. Based on observation it
* appears that the last token in the message is the SQLSTATE
* of the nested exception, and it's preceded by a colon.
* So using that (hopefully consistent?) rule, try to find
* the target SQLSTATE.
*/
String msg = exception.getMessage();
if (!msg.substring(msg.lastIndexOf(":")+1)
.trim().equals(expected))
{
throw e;
}
}
else
{
// Check nested exceptions to see if any of them is
// the one we're looking for.
exception = exception.getNextException();
if (exception != null)
assertSQLState(message, expected, exception);
else
throw e;
}
}
}
/**
* Assert that a warning is chained to the connection.
*
* @param conn the connection
* @param expected the expected SQLState of the warning
*/
public static void assertWarning( Connection conn, String expected )
throws SQLException
{
SQLWarning firstWarning = conn.getWarnings();
assertNotNull( firstWarning );
for ( SQLWarning warning = firstWarning; warning != null; warning = warning.getNextWarning() )
{
if ( expected.equals( warning.getSQLState() ) ) { return; }
}
fail( "Expected to see a SQLWarning with the SQLState " + expected );
}
/**
* Assert that SQLState is as expected.
*
* @param expected the expected SQLState.
* @param exception the exception to check the SQLState of.
*/
public static void assertSQLState(String expected, SQLException exception) {
assertSQLState("Unexpected SQL state.", expected, exception);
}
/**
* Assert that the error code is as expected.
*
* @param expected the expected error code
* @param exception the exception to check
* @throws AssertionFailedError if the error code is wrong
*/
public static void assertErrorCode(int expected, SQLException exception) {
assertNotNull("Exception should not be null", exception);
int actual = exception.getErrorCode();
if (actual != expected) {
fail("Expected error code " + expected + ", got " + actual,
exception);
}
}
/**
* Assert that the SQL statement does not compile and throws
* a SQLException with the expected state.
*
* @param sqlState expected sql state.
* @param sql the SQL to compile.
*/
public void assertCompileError(String sqlState, String sql) {
try {
PreparedStatement pSt = prepareStatement(sql);
if (usingDB2Client())
{
/* For JCC the prepares are deferred until execution,
* so we have to actually execute in order to see the
* expected error. Note that we don't need to worry
* about binding the parameters (if any); the compile
* error should occur before the execution-time error
* about unbound parameters.
*/
try {
pSt.execute();
} finally {
pSt.close();
}
}
fail("expected compile error: " + sqlState);
} catch (SQLException se) {
assertSQLState(sqlState, se);
}
}
/**
* Check the table using SYSCS_UTIL.SYSCS_CHECK_TABLE.
*/
public void assertCheckTable(String table) throws SQLException
{
PreparedStatement ps = prepareStatement(
"VALUES SYSCS_UTIL.SYSCS_CHECK_TABLE(?, ?)");
ps.setString(1, getTestConfiguration().getUserName());
ps.setString(2, table);
ResultSet rs = ps.executeQuery();
JDBC.assertSingleValueResultSet(rs, "1");
ps.close();
}
/**
* Assert that the number of rows in a table is an expected value.
* Query uses a SELECT COUNT(*) FROM "table".
*
* @param table Name of table in current schema, will be quoted
* @param rowCount Number of rows expected in the table
* @throws SQLException Error accessing the database.
*/
protected void assertTableRowCount(String table, int rowCount) throws SQLException
{
assertEscapedTableRowCount(JDBC.escape(table), rowCount);
}
/**
* Assert that the number of rows in a table is an expected value.
* Query uses a SELECT COUNT(*) FROM table.
*
* @param escapedTableName Escaped name of table, will be used as-is.
* @param rowCount Number of rows expected in the table
* @throws SQLException Error accessing the database.
*/
private void assertEscapedTableRowCount(String escapedTableName, int rowCount)
throws SQLException
{
Statement s = createStatement();
ResultSet rs = s.executeQuery(
"SELECT COUNT(*) FROM " + escapedTableName);
rs.next();
assertEquals(escapedTableName + " row count:",
rowCount, rs.getInt(1));
rs.close();
s.close();
}
/**
* Clear the contents of the statement cache. Useful if a test case
* needs to make sure that a statement is actually compiled and not
* just fetched from the statement cache.
*
* @throws SQLException if a database error happens
*/
protected final void emptyStatementCache() throws SQLException {
Statement s = createStatement();
s.execute("CALL SYSCS_UTIL.SYSCS_EMPTY_STATEMENT_CACHE()");
closeStatement(s);
}
/**
* Execute a DROP TABLE command using the passed in tableName as-is
* and the default connection.
* If the DROP TABLE fails because the table does not exist then
* the exception is ignored.
* @param tableName Table to be dropped.
* @throws SQLException
*/
public final void dropTable(String tableName) throws SQLException
{
dropTable(getConnection(), tableName);
}
/**
* Execute a DROP TABLE command using the passed in tableName as-is.
* If the DROP TABLE fails because the table does not exist then
* the exception is ignored.
* @param conn Connection to execute the DROP TABLE
* @param tableName Table to be dropped.
* @throws SQLException
*/
public static void dropTable(Connection conn, String tableName) throws SQLException
{
Statement statement = conn.createStatement();
String dropSQL = "DROP TABLE " + tableName;
try {
statement.executeUpdate(dropSQL);
} catch (SQLException e) {
assertSQLState("42Y55", e);
}
finally {
statement.close();
}
}
/**
* Execute a DROP VIEW command using the passed in viewName as-is
* and the default connection.
* If the DROP VIEW fails because the view does not exist then
* the exception is ignored.
* @param viewName Table to be dropped.
* @throws SQLException
*/
public final void dropView(String viewName) throws SQLException
{
dropView(getConnection(), viewName);
}
/**
* Execute a DROP VIEW command using the passed in viewName as-is.
* If the DROP VIEW fails because the view does not exist then
* the exception is ignored.
* @param conn Connection to execute the DROP VIEW
* @param viewName Table to be dropped.
* @throws SQLException
*/
public static void dropView(Connection conn, String viewName) throws SQLException
{
Statement statement = conn.createStatement();
String dropSQL = "DROP VIEW " + viewName;
try {
statement.executeUpdate(dropSQL);
} catch (SQLException e) {
assertSQLState("42Y55", e);
}
finally {
statement.close();
}
}
/**
* Assert that the query fails (either in compilation,
* execution, or retrieval of results--doesn't matter)
* and throws a SQLException with the expected states.
*
* Assumption is that 'query' does *not* have parameters
* that need binding and thus can be executed using a
* simple Statement.execute() call.
*
* If there are extra chained SQLExceptions that are
* not in sqlStates, this method will not fail.
*
* @param sqlStates expected sql states.
* @param st Statement object on which to execute.
* @param query the query to compile and execute.
*/
public static void assertStatementError(String[] sqlStates,
Statement st, String query) {
assertStatementErrorMinion(sqlStates, ORDERED, st, query);
}
/**
* Assert that the query fails (either in compilation,
* execution, or retrieval of results--doesn't matter)
* and throws a SQLException with the expected states.
*
* Assumption is that 'query' does *not* have parameters
* that need binding and thus can be executed using a
* simple Statement.execute() call.
*
* If there are extra chained SQLExceptions that are
* not in sqlStates, this method will not fail.
*
* @param sqlStates expected sql states.
* @param st Statement object on which to execute.
* @param query the query to compile and execute.
*/
public static void assertStatementErrorUnordered(String[] sqlStates,
Statement st, String query) {
assertStatementErrorMinion(sqlStates, UNORDERED, st, query);
}
/**
* Asserts that the given statement fails (compilation, execution or
* retrieval of results) and throws an {@code SQLException} with the
* expected (chained) states.
*
* @param sqlStates the expected states
* @param orderedStates whether or not the states are expected in the
* specified order or not
* @param st the statement used to execute the query
* @param query the query to execute
*/
private static void assertStatementErrorMinion(
String[] sqlStates, boolean orderedStates,
Statement st, String query) {
ArrayList<String> statesBag = null;
if (!orderedStates) {
statesBag = new ArrayList<String>(Arrays.asList(sqlStates));
}
try {
boolean haveRS = st.execute(query);
fetchAndDiscardAllResults(st, haveRS);
String errorMsg = "Expected error(s) '" ;
for (int i = 0; i < sqlStates.length;i++)
errorMsg += " " + sqlStates[i];
errorMsg += "' but no error was thrown.";
fail(errorMsg);
} catch (SQLException se) {
int count = 0;
do {
if (orderedStates) {
assertSQLState(sqlStates[count], se);
} else {
String state = se.getSQLState();
assertTrue("Unexpected state: " + state,
statesBag.remove(state));
// Run through assertSQLStates too, to catch invalid states.
assertSQLState(state, se);
}
count++;
se = se.getNextException();
} while (se != null && count < sqlStates.length);
// We must have at least as many exceptions as
// we expected.
assertEquals("Got " +
count + " exceptions. Expected at least"+
sqlStates.length,count,sqlStates.length);
}
}
/**
* Assert that the query fails with a single error
*
* @param sqlState Expected SQLState of exception
* @param st
* @param query
* @see #assertStatementError(String[], Statement, String)
*/
public static void assertStatementError(String sqlState, Statement st, String query) {
assertStatementError(new String[] {sqlState},st,query);
}
/**
* Assert that that a commit fails with the given error
* @param sqlState state
* @param c the connection
*/
public static void assertCommitError(String sqlState, Connection c) {
try {
c.commit();
fail();
} catch (SQLException e) {
assertSQLState(sqlState, e);
}
}
/**
* Assert that the query fails (either in compilation,
* execution, or retrieval of results--doesn't matter)
* and throws a SQLException with the expected state
* and error code
*
* Assumption is that 'query' does *not* have parameters
* that need binding and thus can be executed using a
* simple Statement.execute() call.
*
* @param sqlState expected sql state.
* @param errorCode expected error code.
* @param st Statement object on which to execute.
* @param query the query to compile and execute.
*/
public static void assertStatementError(String sqlState, int errorCode, Statement st, String query) {
try {
boolean haveRS = st.execute(query);
fetchAndDiscardAllResults(st, haveRS);
fail("Expected error '" + sqlState +
"' but no error was thrown.");
} catch (SQLException se) {
assertSQLState(sqlState, se);
assertErrorCode(errorCode, se);
}
}
/**
* Assert that the query fails (either in execution, or retrieval of
* results--doesn't matter) and throws a SQLException with the expected
* state and error code
*
* Parameters must have been already bound, if any.
*
* @param sqlState expected sql state.
* @param ps PreparedStatement query object to execute.
*/
public static void assertPreparedStatementError(String sqlState,
PreparedStatement ps) {
try {
boolean haveRS = ps.execute();
fetchAndDiscardAllResults(ps, haveRS);
fail("Expected error '" + sqlState +
"' but no error was thrown.");
} catch (SQLException se) {
assertSQLState(sqlState, se);
}
}
/**
* Assert that execution of the received PreparedStatement
* object fails (either in execution or when retrieving
* results) and throws a SQLException with the expected
* state.
*
* Assumption is that "pSt" is either a PreparedStatement
* or a CallableStatement that has already been prepared
* and whose parameters (if any) have already been bound.
* Thus the only thing left to do is to call "execute()"
* and look for the expected SQLException.
*
* @param sqlState expected sql state.
* @param pSt A PreparedStatement or CallableStatement on
* which to call "execute()".
*/
public static void assertStatementError(String sqlState,
PreparedStatement pSt)
{
try {
boolean haveRS = pSt.execute();
fetchAndDiscardAllResults(pSt, haveRS);
fail("Expected error '" + sqlState +
"' but no error was thrown.");
} catch (SQLException se) {
assertSQLState(sqlState, se);
}
}
/**
* Executes the Callable statement that is expected to fail and verifies
* that it throws the expected SQL exception.
* @param expectedSE The expected SQL exception
* @param callSQL The SQL to execute
* @throws SQLException
*/
public void assertCallError(String expectedSE, String callSQL)
throws SQLException
{
try {
CallableStatement cs = prepareCall(callSQL);
cs.execute();
fail("FAIL - SQL expected to throw exception");
} catch (SQLException se) {
assertSQLState(expectedSE, se);
}
}
/**
* Perform a fetch on the ResultSet with an expected failure
*
* @param sqlState Expected SQLState
* @param rs ResultSet upon which next() will be called
*/
public static void assertNextError(String sqlState,ResultSet rs)
{
try {
rs.next();
fail("Expected error on next()");
}catch (SQLException se){
assertSQLState(sqlState,se);
}
}
/**
* Perform getInt(position) with expected error
* @param position position argument to pass to getInt
* @param sqlState sqlState of expected error
* @param rs ResultSet upon which to call getInt(position)
*/
public static void assertGetIntError(int position, String sqlState, ResultSet rs)
{
try {
rs.getInt(position);
fail("Expected exception " + sqlState);
} catch (SQLException se){
assertSQLState(sqlState,se);
}
}
/**
* Take a Statement object and a SQL statement, execute it
* via the "executeUpdate()" method, and assert that the
* resultant row count matches the received row count.
*
* Assumption is that 'sql' does *not* have parameters
* that need binding and that it can be executed using a
* simple Statement.executeUpdate() call.
*
* @param st Statement object on which to execute.
* @param expectedRC Expected row count.
* @param sql SQL to execute.
*/
public static void assertUpdateCount(Statement st,
int expectedRC, String sql) throws SQLException
{
assertEquals("Update count does not match:",
expectedRC, st.executeUpdate(sql));
}
/**
* Assert that a call to "executeUpdate()" on the received
* PreparedStatement object returns a row count that matches
* the received row count.
*
* Assumption is that "pSt" is either a PreparedStatement
* or a CallableStatement that has already been prepared
* and whose parameters (if any) have already been bound.
* Also assumes the statement's SQL is such that a call
* executeUpdate() is allowed. Thus the only thing left
* to do is to call the "executeUpdate" method.
*
* @param pSt The PreparedStatement on which to execute.
* @param expectedRC The expected row count.
*/
public static void assertUpdateCount(PreparedStatement pSt,
int expectedRC) throws SQLException
{
assertEquals("Update count does not match:",
expectedRC, pSt.executeUpdate());
}
/**
* Get the last SQLException in chain.
* @param sqle <code>SQLException</code>
* @return the last exception in the chain.
*/
public SQLException getLastSQLException(SQLException sqle) {
SQLException current = sqle;
SQLException next = sqle.getNextException();
while (next != null) {
current = next;
next = next.getNextException();
}
return current;
}
/**
* Take the received Statement--on which a query has been
* executed--and fetch all rows of all result sets (if any)
* returned from execution. The rows themselves are
* discarded. This is useful when we expect there to be
* an error when processing the results but do not know
* (or care) at what point the error occurs.
*
* @param st An already-executed statement from which
* we get the result set to process (if there is one).
* @param haveRS Whether or not the the statement's
* first result is a result set (as opposed to an
* update count).
*/
private static void fetchAndDiscardAllResults(Statement st,
boolean haveRS) throws SQLException
{
ResultSet rs = null;
while (haveRS || (st.getUpdateCount() != -1))
{
// If we have a result set, iterate through all
// of the rows.
if (haveRS)
JDBC.assertDrainResults(st.getResultSet(), -1);
haveRS = st.getMoreResults();
}
}
/**
* Assert that the two (2) passed-in SQLException's are equals and
* not just '=='.
*
* @param se1 first SQLException to compare
* @param se2 second SQLException to compare
*/
public static void assertSQLExceptionEquals(SQLException se1,
SQLException se2) {
// Ensure non-null SQLException's are being passed.
assertNotNull(
"Passed-in SQLException se1 cannot be null",
se1);
assertNotNull(
"Passed-in SQLException se2 cannot be null",
se2);
// Now verify that the passed-in SQLException's are of the same type
assertEquals("SQLException class types are different",
se1.getClass().getName(), se2.getClass().getName());
// Here we check that the detailed message of both
// SQLException's is the same
assertEquals(
"Detailed messages of the SQLException's are different",
se1.getMessage(), se2.getMessage());
// Check that getCause() returns the same value on the two exceptions.
Throwable se1Cause = se1.getCause();
Throwable se2Cause = se2.getCause();
if (se1Cause == null) {
assertNull(se2Cause);
} else {
assertThrowableEquals(se1Cause, se2Cause);
}
// Check that the two exceptions have the same next exception.
if (se1.getNextException() == null) {
assertNull(se2.getNextException());
} else {
assertSQLExceptionEquals(se1.getNextException(),
se2.getNextException());
}
}
/**
* Compares two JDBC types to see if they are equivalent.
* DECIMAL and NUMERIC and DOUBLE and FLOAT are considered
* equivalent.
* @param expectedType Expected jdbctype from java.sql.Types
* @param type Actual type from metadata
*/
public static void assertEquivalentDataType(int expectedType, int type)
{
if (expectedType == type)
return;
if (expectedType == java.sql.Types.DECIMAL &&
type == java.sql.Types.NUMERIC)
return;
if (expectedType == java.sql.Types.NUMERIC &&
type == java.sql.Types.DECIMAL)
return;
if (expectedType == java.sql.Types.DOUBLE &&
type == java.sql.Types.FLOAT)
return;
if (expectedType == java.sql.Types.FLOAT &&
type == java.sql.Types.DOUBLE)
return;
fail("types:" + expectedType + " and " + type + " are not equivalent");
}
/**
* Attempts to obtain the client-side transaction counter from the given
* connection, which is internal state information.
* <p>
* <em>NOTE:</em> Use with care, accesses internal state.
*
* @param conn the connection
* @return Internal client transaction id.
* @throws SQLException if the given connection is an embedded connection,
* or if invoking the required method fails
**/
public static int getClientTransactionID(Connection conn)
throws SQLException {
try {
Method m = conn.getClass().getMethod(
"getTransactionID", new Class[] {});
return ((Integer) m.invoke(conn, new Object[] {} )).intValue();
} catch (Exception e) {
SQLException se = new SQLException(e.getMessage());
se.initCause(e);
throw se;
}
}
/**
* Return estimated row count for runtime statistics.
* Requires caller first turned on RuntimeStatistics, executed a query and closed the ResultSet.
*
* For client calls we just return as we can't find out this information.
* @param conn
* @param expectedCount
* @throws SQLException
*/
public static void checkEstimatedRowCount(Connection conn, double expectedCount) throws SQLException {
if (! (conn instanceof EmbedConnection))
{ return; }
EmbedConnection econn = (EmbedConnection) conn;
LanguageConnectionContext lcc = (LanguageConnectionContext) getLanguageConnectionContext( econn );
RunTimeStatistics rts = lcc.getRunTimeStatisticsObject();
assertNotNull(" RuntimeStatistics is null. Did you call SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)?",rts);
assertEquals((long) expectedCount, (long) rts.getEstimatedRowCount());
}
/**
* Check consistency of all tables
*
* @param conn
* @throws SQLException
*/
protected void checkAllConsistency(
Connection conn)
throws SQLException
{
Statement s = createStatement();
ResultSet rs =
s.executeQuery(
"select schemaname, tablename, SYSCS_UTIL.SYSCS_CHECK_TABLE(schemaname, tablename) " +
"from sys.systables a, sys.sysschemas b where a.schemaid = b.schemaid");
int table_count = 0;
while (rs.next())
{
table_count++;
if (rs.getInt(3) != 1)
{
assertEquals("Bad return from consistency check of " +
rs.getString(1) + "." + rs.getString(2),1,rs.getInt(3));
}
}
assertTrue("Something wrong with consistency check query, found only " +
table_count + " tables.",table_count >= 5);
rs.close();
s.close();
conn.commit();
}
protected static void dumpRs(ResultSet s, PrintStream out)
throws SQLException
{
if (s == null) {
out.println("<NULL>");
return;
}
ResultSetMetaData rsmd = s.getMetaData();
// Get the number of columns in the result set
int numCols = rsmd.getColumnCount();
if (numCols <= 0) {
out.println("(no columns!)");
return;
}
StringBuilder heading = new StringBuilder("\t ");
StringBuilder underline = new StringBuilder("\t ");
int len;
// Display column headings
for (int i=1; i<=numCols; i++) {
if (i > 1) {
heading.append(",");
underline.append(" ");
}
len = heading.length();
heading.append(rsmd.getColumnLabel(i));
len = heading.length() - len;
for (int j = len; j > 0; j--) {
underline.append("-");
}
}
out.println(heading.toString());
out.println(underline.toString());
StringBuilder row = new StringBuilder();
// Display data, fetching until end of the result set
while (s.next()) {
row.append("\t{");
// Loop through each column, getting the
// column data and displaying
for (int i=1; i<=numCols; i++) {
if (i > 1) row.append(",");
row.append(s.getString(i));
}
row.append("}\n");
}
out.println(row.toString());
s.close();
}
public static void dumpRs(ResultSet s) throws SQLException {
dumpRs(s, System.out);
}
// helper methods moved from GeneratedColumnsHelper
/**
* Run good DDL.
* @throws SQLException
*/
protected void goodStatement( Connection conn, String command ) throws SQLException
{
PreparedStatement ps = chattyPrepare( conn, command );
ps.execute();
ps.close();
}
/**
* Run a good update statement with an expected row count.
* @throws SQLException
*/
protected void goodUpdate( Connection conn, String update, int expectedRowCount ) throws SQLException
{
PreparedStatement ps = chattyPrepare( conn, update );
int actualRowCount = ps.executeUpdate();
ps.close();
println( "Expecting to touch " + expectedRowCount + " rows." );
assertEquals( expectedRowCount, actualRowCount );
}
/**
* Assert that the statement returns the correct results.
*/
protected void assertResults( Connection conn, String query, String[][] rows, boolean trimResults )
throws SQLException
{
PreparedStatement ps = chattyPrepare( conn, query );
ResultSet rs = ps.executeQuery();
assertResults( rs, rows, trimResults );
rs.close();
ps.close();
}
/**
* Assert that the ResultSet returns the desired rows.
*/
protected void assertResults( ResultSet rs, String[][] rows, boolean trimResults )
throws SQLException
{
int rowCount = rows.length;
for ( int i = 0; i < rowCount; i++ )
{
String[] row = rows[ i ];
int columnCount = row.length;
assertTrue( rs.next() );
for ( int j = 0; j < columnCount; j++ )
{
String expectedValue = row[ j ];
//println( "(row, column ) ( " + i + ", " + j + " ) should be " + expectedValue );
String actualValue = null;
int column = j+1;
actualValue = rs.getString( column );
if ( rs.wasNull() ) { actualValue = null; }
if ( (actualValue != null) && trimResults ) { actualValue = actualValue.trim(); }
assertEquals( (expectedValue == null), rs.wasNull() );
if ( expectedValue == null ) { assertNull( actualValue ); }
else { assertEquals(expectedValue, actualValue); }
}
}
assertFalse( rs.next() );
}
protected static ResultSet executeQuery( Statement stmt, String text )
throws SQLException
{
println( "Executing '" + text + "'" );
return stmt.executeQuery( text );
}
/**
* Prepare a statement and report its sql text.
*/
protected PreparedStatement chattyPrepare( Connection conn, String text )
throws SQLException
{
println( "Preparing statement:\n\t" + text );
return conn.prepareStatement( text );
}
/**
* Prepare a callable statement and report its sql text.
*/
protected CallableStatement chattyPrepareCall( Connection conn, String text )
throws SQLException
{
println( "Preparing callable statement:\n\t" + text );
return conn.prepareCall( text );
}
/**
* Assert that the statement text, when compiled, raises an exception
*/
protected void expectCompilationError( String sqlState, String query )
{
println( "\nExpecting " + sqlState + " when preparing:\n\t" + query );
assertCompileError( sqlState, query );
}
/**
* Assert that the statement text, when compiled, raises an exception
*/
protected void expectCompilationError( Connection conn, String sqlState, String query )
{
println( "\nExpecting " + sqlState + " when preparing:\n\t" + query );
PreparedStatement ps = null;
try {
ps = conn.prepareStatement( query );
} catch (SQLException se )
{
assertSQLState( sqlState, se );
return;
}
fail( "Expected SQL state: " + sqlState );
}
/**
* Assert that the statement text, when executed, raises an error.
*/
protected void expectExecutionError( Connection conn, String sqlState, String query )
throws Exception
{
println( "\nExpecting " + sqlState + " when executing:\n\t" );
PreparedStatement ps = chattyPrepare( conn, query );
assertStatementError( sqlState, ps );
ps.close();
}
/**
* Gets the LanguageConnectionContext for this connection. You might think that
* this method could take an EmbedConnection as its argument and return a
* LanguageConnectionContext. That, however, makes the compatibility tests blow up.
* With those stronger types, the test lookup machinery in junit.framework.TestSuite
* can't resolve the signature of this private method. That is because the engine jar is
* not on the client-only classpath used by the compatibility tests. Now you know.
*/
private static Object getLanguageConnectionContext( Connection conn )
{
final EmbedConnection econn = (EmbedConnection) conn;
return AccessController.doPrivileged
(
new PrivilegedAction<LanguageConnectionContext>()
{
public LanguageConnectionContext run()
{
return econn.getLanguageConnection();
}
}
);
}
} // End class BaseJDBCTestCase