QueryTimeoutTestpublic class QueryTimeoutTest extends TestCase Functional test for the Statement.setQueryTimeout() method. Adopted from
Apache Derby project (Apache License 2.0).
TODO Test requires transaction isolation to be supported. => Ticket 69
This test consists of four parts: 1. Executes a SELECT
query in 4 different threads concurrently. The query calls a user-defined,
server-side function which delays the execution, so that it takes several
seconds even though the data volume is really low. The fetch operations take
longer time than the timeout value set. Hence, this part tests getting
timeouts from calls to ResultSet.next(). Two connections are used, two
threads execute their statement in the context of one connection, the other
two threads in the context of the other connection. Of the 4 threads, only
one executes its statement with a timeout value. This way, the test ensures
that the correct statement is affected by setQueryTimeout(), regardless of
what connection/transaction it and other statements are executed in the
context of.
2. Executes an INSERT query in multiple threads. This part tests
getting timeouts from calls to Statement.execute(). Each thread executes the
query in the context of a separate connection. There is no point in executing
multiple statements on the same connection; since only one statement per
connection executes at a time, there will be no interleaving of execution
between them (contrary to the first part of this test, where calls to
ResultSet.next() may be interleaved between the different threads). Half of
the threads execute their statement with a timeout value set, this is to
verify that the correct statements are affected by the timeout, while the
other statements execute to completion.
3. Sets an invalid (negative)
timeout. Verifies that the correct exception is thrown.
4. Tests that the query timeout value is not forgotten after the execution of a statement. |
Fields Summary |
---|
private static Statement | statement | private static final int | TIMEOUT | private static final int | CONNECTIONS | private static Connection[] | connections |
Methods Summary |
---|
private static void | dropTables(java.sql.Connection conn, java.lang.String tablePrefix)
Collection ignore = new HashSet();
//ignore.add("42Y55");
exec(conn, "drop table if exists " + tablePrefix + "_orig;", ignore);
exec(conn, "drop table if exists " + tablePrefix + "_copy;", ignore);
| private static void | exec(java.sql.Connection connection, java.lang.String queryString, java.util.Collection ignoreExceptions)Used for executing the SQL statements for setting up this test (the
preparation phase). The queries testing setQueryTimeout() are run by the
StatementExecutor class.
Statement statement = null;
try {
statement = connection.createStatement();
System.out.println(" Executing "+queryString);
statement.execute(queryString);
} catch (SQLException e) {
String sqlState = e.getSQLState();
if (!ignoreExceptions.contains(sqlState)) {
throw new TestFailedException(e); // See finally block below
}
} finally {
if (statement != null) {
try {
statement.close();
} catch (SQLException ee) {
// This will discard an exception possibly thrown above :-(
// But we don't worry too much about this, since:
// 1. This is just a test
// 2. We don't expect close() to throw
// 3. If it does, this will be inspected by a developer
throw new TestFailedException(ee);
}
}
}
| private static void | exec(java.sql.Connection connection, java.lang.String queryString)
exec(connection, queryString, Collections.EMPTY_SET);
| private static void | expectException(java.lang.String expectSqlState, java.sql.SQLException sqlException, java.lang.String failMsg)This method compares a thrown SQLException's SQLState value to an
expected SQLState. If they do not match, a TestFailedException is thrown
with the given message string.
if (sqlException == null) {
throw new TestFailedException(failMsg);
} else {
String sqlState = sqlException.getSQLState();
if (!expectSqlState.equals(sqlState)) {
throw new TestFailedException(sqlException);
}
}
| private static java.lang.String | getExecQuery(java.lang.String tablePrefix)
return "insert into " + tablePrefix + "_copy select a from "
+ tablePrefix + "_orig where DELAY(1,1)=1";
| private static java.lang.String | getFetchQuery(java.lang.String tablePrefix)
/**
* The reason for using the mod function here is to force at least one
* invocation of ResultSet.next() to read more than one row from the
* table before returning. This is necessary since timeout is checked
* only when reading rows from base tables, and when the first row is
* read, the query still has not exceeded the timeout.
*/
return "select a from " + tablePrefix
+ "_orig where mod(DELAY(1,a),3)=0";
| private static java.sql.PreparedStatement | prepare(java.sql.Connection conn, java.lang.String query)
try {
return conn.prepareStatement(query);
} catch (SQLException e) {
throw new TestFailedException(e);
}
| private static void | prepareTables(java.sql.Connection conn, java.lang.String tablePrefix)
System.out.println("Initializing tables with prefix " + tablePrefix);
dropTables(conn, tablePrefix);
exec(conn, "create table " + tablePrefix + "_orig (a int)");
exec(conn, "create table " + tablePrefix + "_copy (a int)");
for (int i = 0; i < 7; i++) {
exec(conn, "insert into " + tablePrefix + "_orig"
+ " values ("+i+");");
}
| private static void | printSQLException(java.sql.SQLException e)
while (e != null) {
e.printStackTrace();
e = e.getNextException();
}
| public static void | statementRemembersTimeout(java.sql.Statement stmt)
System.out.println("Testing that Statement remembers timeout.");
stmt.setQueryTimeout(1);
for (int i = 0; i < 3; i++) {
try {
ResultSet rs = stmt.executeQuery(getFetchQuery("t"));
while (rs.next()) {
// do nothing
}
throw new TestFailedException("Should have timed out.");
} catch (SQLException sqle) {
expectException("XCL52", sqle, "Should have timed out.");
}
}
stmt.close();
| private static void | statementRemembersTimeout(java.sql.PreparedStatement ps)
String name = (ps instanceof CallableStatement) ? "CallableStatement"
: "PreparedStatement";
System.out.println("Testing that " + name + " remembers timeout.");
ps.setQueryTimeout(1);
for (int i = 0; i < 3; i++) {
try {
ResultSet rs = ps.executeQuery();
while (rs.next()) {
// do nothing
}
throw new TestFailedException("Should have timed out.");
} catch (SQLException sqle) {
expectException("XCL52", sqle, "Should have timed out.");
}
}
ps.close();
| public static junit.framework.Test | suite()The actual main bulk of this test. Sets up the environment, prepares
tables, runs the tests, and shuts down.
TestSetup setup = new TestSetup( new TestSuite (QueryTimeoutTest.class)) {
public void setUp() {
// Establish connections
Support_SQL.loadDriver();
try {
for (int i = 0; i < connections.length; ++i) {
connections[i] = Support_SQL.getConnection();
}
for (int i = 0; i < connections.length; ++i) {
connections[i]
.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
}
// setup Delay function
prepare();
} catch (Throwable e) {
fail("Unexpected SQLException " + e.toString());
}
System.out.println("Connections set up");
}
public void tearDown() {
for (int i = connections.length - 1; i >= 0; --i) {
if (connections[i] != null) {
try {
connections[i].close();
} catch (SQLException ex) {
printSQLException(ex);
}
}
}
System.out.println("Closed connections");
}
public void prepare() throws TestFailedException {
System.out.println("Preparing for testing queries with timeout");
Database db = new Database();
Connection conn = connections[0];
try {
db.open(Support_SQL.getFilename(), 1);
conn.setAutoCommit(true);
} catch (Exception e) {
throw new TestFailedException("Unexpected Exception", e);
}
Function delayFc = new Delay();
db.create_function("DELAY", 2, delayFc);
prepareTables(conn, "t");
}
};
TestSuite ts = new TestSuite();
ts.addTestSuite(QueryTimeoutTest.class);
return setup;
| public static void | testInvalidTimeoutValue(java.sql.Connection conn)
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
throw new TestFailedException("Unexpected Exception", e);
}
// Create statement
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement("select * from sys.systables");
} catch (SQLException e) {
throw new TestFailedException("Unexpected Exception", e);
}
// Set (invalid) timeout value - expect exception
try {
stmt.setQueryTimeout(-1);
} catch (SQLException e) {
expectException("XJ074", e,
"negative timeout value should give exception");
}
System.out
.println("Negative timeout value caused exception, as expected");
// Execute the statement and fetch result
ResultSet rs = null;
try {
rs = stmt.executeQuery();
System.out.println("Execute returned a ResultSet");
rs.close();
} catch (SQLException e) {
throw new TestFailedException("Unexpected Exception", e);
} finally {
try {
stmt.close();
} catch (SQLException e) {
// This will discard an exception possibly thrown above :-(
// But we don't worry too much about this, since:
// 1. This is just a test
// 2. We don't expect close() to throw
// 3. If it does, this will be inspected by a developer
throw new TestFailedException("close should not throw", e);
}
}
| public static void | testRememberTimeoutValue()Test for DERBY-1692.
String sql = getFetchQuery("t");
try {
Statement stmt = connections[0].createStatement();
statementRemembersTimeout(stmt);
PreparedStatement ps = connections[0].prepareStatement(sql);
statementRemembersTimeout(ps);
CallableStatement cs = connections[0].prepareCall(sql);
statementRemembersTimeout(cs);
} catch (SQLException sqle) {
throw new TestFailedException("Unexpected Exception", sqle);
}
| public static void | testTimeoutWithExec()
System.out.println("Testing timeout with an execute operation");
for (int i = 0; i < connections.length; ++i) {
try {
connections[i].setAutoCommit(true);
} catch (SQLException e) {
throw new TestFailedException("Unexpected Exception", e);
}
}
PreparedStatement statements[] = new PreparedStatement[connections.length];
for (int i = 0; i < statements.length; ++i) {
statements[i] = prepare(connections[i], getExecQuery("t"));
}
StatementExecutor[] executors = new StatementExecutor[statements.length];
for (int i = 0; i < executors.length; ++i) {
int timeout = (i % 2 == 0) ? TIMEOUT : 0;
executors[i] = new StatementExecutor(statements[i], false, timeout);
}
for (int i = 0; i < executors.length; ++i) {
executors[i].start();
}
for (int i = 0; i < executors.length; ++i) {
try {
executors[i].join();
} catch (InterruptedException e) {
throw new TestFailedException("Should never happen", e);
}
}
/**
* Actually, there is no guarantee that setting a query timeout for a
* statement will actually cause a timeout, even if execution of the
* statement takes longer than the specified timeout. However, these
* queries execute significantly longer than the specified query
* timeout. Also, the cancellation mechanism implemented should be quite
* responsive. In sum, we expect the statement to always time out. If it
* does not time out, however, we print the highest execution time for
* the query, as an assistance in determining why it failed. Compare the
* number to the TIMEOUT constant in this class (note that the TIMEOUT
* constant is in seconds, while the execution time is in milliseconds).
*/
for (int i = 0; i < executors.length; ++i) {
int timeout = (i % 2 == 0) ? TIMEOUT : 0;
if (timeout > 0) {
expectException("XCL52", executors[i].getSQLException(),
"exec did not time out. Execution time: "
+ executors[i].getHighestRunTime() + " ms");
} else {
SQLException sqlException = executors[i].getSQLException();
if (sqlException != null) {
throw new TestFailedException(sqlException);
}
}
}
System.out
.println("Statements that should time out timed out, and statements that should complete completed");
for (int i = 0; i < statements.length; ++i) {
try {
statements[i].close();
} catch (SQLException e) {
throw new TestFailedException(e);
}
}
| public static void | testTimeoutWithExecuteUpdate()
System.out.println("Testing timeout with executeUpdate call.");
try {
Statement stmt = connections[0].createStatement();
stmt.setQueryTimeout(TIMEOUT);
stmt.executeUpdate(getExecQuery("t"));
} catch (SQLException sqle) {
expectException("XCL52", sqle, "Should have timed out.");
}
| public static void | testTimeoutWithFetch()Part 1 of this test.
System.out.println("Testing timeout with fetch operations");
Connection conn1 = connections[0];
Connection conn2 = connections[1];
try {
conn1.setAutoCommit(false);
conn2.setAutoCommit(false);
} catch (SQLException e) {
throw new TestFailedException("Unexpected Exception", e);
}
// The idea with these 4 statements is as follows:
// A - should time out
// B - different stmt on the same connection; should NOT time out
// C - different stmt on different connection; should NOT time out
// D - here just to create equal contention on conn1 and conn2
PreparedStatement statementA = prepare(conn1, getFetchQuery("t"));
PreparedStatement statementB = prepare(conn1, getFetchQuery("t"));
PreparedStatement statementC = prepare(conn2, getFetchQuery("t"));
PreparedStatement statementD = prepare(conn2, getFetchQuery("t"));
StatementExecutor[] statementExecutor = new StatementExecutor[4];
statementExecutor[0] = new StatementExecutor(statementA, true, TIMEOUT);
statementExecutor[1] = new StatementExecutor(statementB, true, 0);
statementExecutor[2] = new StatementExecutor(statementC, true, 0);
statementExecutor[3] = new StatementExecutor(statementD, true, 0);
for (int i = 3; i >= 0; --i) {
statementExecutor[i].start();
}
for (int i = 0; i < 4; ++i) {
try {
statementExecutor[i].join();
} catch (InterruptedException e) {
throw new TestFailedException("Should never happen", e);
}
}
/**
* Actually, there is no guarantee that setting a query timeout for a
* statement will actually cause a timeout, even if execution of the
* statement takes longer than the specified timeout. However, these
* queries execute significantly longer than the specified query
* timeout. Also, the cancellation mechanism implemented should be quite
* responsive. In sum, we expect the statement to always time out. If it
* does not time out, however, we print the highest execution time for
* the query, as an assistance in determining why it failed. Compare the
* number to the TIMEOUT constant in this class (note that the TIMEOUT
* constant is in seconds, while the execution time is in milliseconds).
*/
expectException("XCL52", statementExecutor[0].getSQLException(),
"fetch did not time out. Highest execution time: "
+ statementExecutor[0].getHighestRunTime() + " ms");
System.out.println("Statement 0 timed out");
for (int i = 1; i < 4; ++i) {
SQLException sqlException = statementExecutor[i].getSQLException();
if (sqlException != null) {
throw new TestFailedException("Unexpected exception in " + i,
sqlException);
}
System.out.println("Statement " + i + " completed");
}
try {
statementA.close();
statementB.close();
statementC.close();
statementD.close();
conn1.commit();
conn2.commit();
} catch (SQLException e) {
throw new TestFailedException(e);
}
|
|