You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by dj...@apache.org on 2005/06/30 19:37:37 UTC

svn commit: r208650 [2/2] - in /incubator/derby/code/trunk/java: engine/org/apache/derby/ engine/org/apache/derby/iapi/reference/ engine/org/apache/derby/iapi/services/monitor/ engine/org/apache/derby/iapi/services/timer/ engine/org/apache/derby/iapi/s...

Added: incubator/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/SetQueryTimeoutTest.java
URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/SetQueryTimeoutTest.java?rev=208650&view=auto
==============================================================================
--- incubator/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/SetQueryTimeoutTest.java (added)
+++ incubator/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/SetQueryTimeoutTest.java Thu Jun 30 10:37:35 2005
@@ -0,0 +1,720 @@
+/*
+
+   Derby - Class org.apache.derbyTesting.functionTests.tests.lang.SetQueryTimeoutTest
+
+   Copyright 2005 The Apache Software Foundation or its licensors, as applicable.
+
+   Licensed 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.functionTests.tests.jdbcapi;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Collections;
+
+import org.apache.derby.tools.ij;
+import org.apache.derby.iapi.reference.SQLState;
+
+/**
+ * Functional test for the Statement.setQueryTimeout() method.
+ *
+ * This test consists of three parts:
+ *
+ * 1. Executes a SELECT query in 4 different threads concurrently.
+ *    The query is designed to make the execution time for each
+ *    ResultSet.next() operation unpredictable, and in the order
+ *    of seconds for many of them. The executeQuery() call finishes
+ *    quickly, but the fetch operations may 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 a long-running INSERT query in two 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).
+ *
+ *    Only one thread executes its statement with a timeout value set,
+ *    this is to verify that the correct statement is affected by the
+ *    timeout, while the other statement executes to completion.
+ *
+ * 3. Sets an invalid (negative) timeout. Verifies that the correct
+ *    exception is thrown.
+ *
+ * @author oyvind.bakksjo@sun.com
+ */
+public class SetQueryTimeoutTest
+{
+    private static final int TIMEOUT = 3; // In seconds
+
+    private static void printSQLException(SQLException e)
+    {
+        while (e != null)
+        {
+            e.printStackTrace();
+            e = e.getNextException();
+        }
+    }
+
+    /**
+     * This Exception class is used for getting fail-fast behaviour in
+     * this test. There is no point in wasting cycles running a test to
+     * the end when we know that it has failed.
+     *
+     * In order to enable chaining of exceptions in J2ME, this class defines
+     * its own "cause", duplicating existing functionality in J2SE.
+     */
+    private static class TestFailedException
+        extends
+            Exception
+    {
+        private Throwable cause;
+
+        public TestFailedException(Throwable t)
+        {
+            super();
+            cause = t;
+        }
+        
+        public TestFailedException(String message)
+        {
+            super(message);
+            cause = null;
+        }
+        
+        public TestFailedException(String message, Throwable t)
+        {
+            super(message);
+            cause = t;
+        }
+        
+        public String toString()
+        {
+            if (cause != null) {
+                return super.toString() + ": " + cause.toString();
+            } else {
+                return super.toString();
+            }
+        }
+        
+        public void printStackTrace()
+        {
+            super.printStackTrace();
+            if (cause != null) {
+                if (cause instanceof SQLException) {
+                    SetQueryTimeoutTest.printSQLException((SQLException)cause);
+                } else {
+                    cause.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Used for executing the SQL statements for setting up this test
+     * (the preparation phase). The queries testing setQueryTimeout()
+     * are run by the StatementExecutor class.
+     */
+    private static void exec(Connection connection,
+                             String queryString,
+                             Collection ignoreExceptions)
+        throws
+            TestFailedException
+    {
+        PreparedStatement statement = null;
+        try {
+            statement = connection.prepareStatement(queryString);
+            statement.execute();
+        } 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);
+                }
+            }
+        }
+    }
+
+    // Convenience method
+    private static void exec(Connection connection,
+                             String queryString)
+        throws
+            TestFailedException
+    {
+        exec(connection, queryString, Collections.EMPTY_SET);
+    }
+    
+    private static void dropTables(Connection conn, String tablePrefix)
+        throws
+            TestFailedException
+    {
+        Collection ignore = new HashSet();
+        ignore.add(SQLState.LANG_OBJECT_DOES_NOT_EXIST.substring(0,5));
+        
+        exec(conn, "drop table " + tablePrefix + "0", ignore);
+        exec(conn, "drop table " + tablePrefix + "1", ignore);
+        exec(conn, "drop table " + tablePrefix + "2", ignore);
+        exec(conn, "drop table " + tablePrefix + "3", ignore);
+        exec(conn, "drop table " + tablePrefix + "4", ignore);
+        exec(conn, "drop table " + tablePrefix + "5", ignore);
+    }
+    
+    private static void prepareTables(Connection conn, String tablePrefix)
+        throws
+            TestFailedException
+    {
+        System.out.println("Initializing tables with prefix " + tablePrefix);
+
+        dropTables(conn, tablePrefix);
+        
+        exec(conn,
+             "create table " + tablePrefix + "1 (a int, b char(1))");
+
+        exec(conn,
+             "create table " + tablePrefix + "2 (a int, b char(2))");
+        
+        exec(conn,
+             "create table " + tablePrefix + "3 (a int, b char(4))");
+
+        exec(conn,
+             "create table " + tablePrefix + "4 (a int, b char(6))");
+
+        exec(conn,
+             "create table " + tablePrefix + "5 (a int, b char(8))");
+
+        exec(conn,
+             "insert into "
+             + tablePrefix + "1"
+             + " values(3,'a')"
+             + ",(7,'b')"
+             + ",(13,'c')"
+             + ",(37,'d')"
+             + ",(141,'e')"
+             + ",(1,'f')");
+        
+
+        exec(conn,
+             "insert into "
+             + tablePrefix + "2 select "
+             + tablePrefix + "1.a+"
+             + tablePrefix + "x.a,"
+             + tablePrefix + "1.b||"
+             + tablePrefix + "x.b from "
+             + tablePrefix + "1 join "
+             + tablePrefix + "1 as "
+             + tablePrefix + "x on 1=1");
+        
+        exec(conn,
+             "insert into "
+             + tablePrefix + "3 select "
+             + tablePrefix + "2.a+"
+             + tablePrefix + "x.a,"
+             + tablePrefix + "2.b||"
+             + tablePrefix + "x.b from "
+             + tablePrefix + "2 join "
+             + tablePrefix + "2 as "
+             + tablePrefix + "x on 1=1");
+        
+        exec(conn,
+             "insert into "
+             + tablePrefix + "4 select "
+             + tablePrefix + "3.a+"
+             + tablePrefix + "2.a,"
+             + tablePrefix + "3.b||"
+             + tablePrefix + "2.b from "
+             + tablePrefix + "3 join "
+             + tablePrefix + "2 on 1=1");
+    }
+    
+    private static void prepareForTimedQueries(Connection conn)
+        throws
+            TestFailedException
+    {
+        System.out.println("Preparing for testing queries with timeout");
+
+        try {
+            conn.setAutoCommit(true);
+        } catch (SQLException e) {
+            throw new TestFailedException("Should not happen", e);
+        }
+
+        prepareTables(conn, "t");
+        prepareTables(conn, "u");
+        prepareTables(conn, "v");
+        prepareTables(conn, "x");
+    }
+    
+    private static String getFetchQuery(String tablePrefix)
+    {
+        return "select "
+            + tablePrefix + "4.a+"
+            + tablePrefix + "3.a,"
+            + tablePrefix + "4.b||"
+            + tablePrefix + "3.b from "
+            + tablePrefix + "4 left join "
+            + tablePrefix + "3 on 1=1 where mod("
+            + tablePrefix + "4.a+"
+            + tablePrefix + "3.a,1000)=0";
+    }
+    
+    private static String getExecQuery(String tablePrefix)
+    {
+        return "insert into "
+            + tablePrefix + "5 select "
+            + tablePrefix + "3.a+"
+            + tablePrefix + "x.a,"
+            + tablePrefix + "3.b from "
+            + tablePrefix + "3 left join "
+            + tablePrefix + "3 as "
+            + tablePrefix + "x on 1=1";
+    }
+    
+    private static class StatementExecutor
+        extends
+            Thread
+    {
+        private PreparedStatement statement;
+        private boolean doFetch;
+        private int timeout;
+        private SQLException sqlException;
+        private String name;
+        private long highestRunTime;
+        
+        public StatementExecutor(PreparedStatement statement,
+                                 boolean doFetch,
+                                 int timeout)
+        {
+            this.statement = statement;
+            this.doFetch = doFetch;
+            this.timeout = timeout;
+            highestRunTime = 0;
+            sqlException = null;
+            try {
+                statement.setQueryTimeout(timeout);
+            } catch (SQLException e) {
+                sqlException = e;
+            }
+        }
+        
+        private void setHighestRunTime(long runTime)
+        {
+            synchronized (this) {
+                highestRunTime = runTime;
+            }
+        }
+        
+        public long getHighestRunTime()
+        {
+            synchronized (this) {
+                return highestRunTime;
+            }
+        }
+        
+        private boolean fetchRow(ResultSet resultSet)
+            throws
+                SQLException
+        {
+            long startTime = System.currentTimeMillis();
+            boolean hasNext = resultSet.next();
+            long endTime = System.currentTimeMillis();
+            long runTime = endTime - startTime;
+            if (runTime > highestRunTime) setHighestRunTime(runTime);
+            return hasNext;
+        }
+
+        public void run()
+        {
+            if (sqlException != null)
+                return;
+
+            ResultSet resultSet = null;
+
+            try {
+                if (doFetch) {
+                    long startTime = System.currentTimeMillis();
+                    resultSet = statement.executeQuery();
+                    long endTime = System.currentTimeMillis();
+                    setHighestRunTime(endTime - startTime);
+                    while (fetchRow(resultSet)) {
+                        yield();
+                    }
+                } else {
+                    long startTime = System.currentTimeMillis();
+                    statement.execute();
+                    long endTime = System.currentTimeMillis();
+                    setHighestRunTime(endTime - startTime);
+                }
+            } catch (SQLException e) {
+                synchronized (this) {
+                    sqlException = e;
+                }
+            } finally {
+                if (resultSet != null) {
+                    try {
+                        resultSet.close();
+                    } catch (SQLException ex) {
+                        if (sqlException != null) {
+                            System.err.println("Discarding previous exception");
+                            sqlException.printStackTrace();
+                        }
+                        sqlException = ex;
+                    }
+                }
+            }
+        }
+
+        public SQLException getSQLException()
+        {
+            synchronized (this) {
+                return sqlException;
+            }
+        }
+    }
+
+    /**
+     * 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.
+     */
+    private static void expectException(String expectSqlState,
+                                        SQLException sqlException,
+                                        String failMsg)
+        throws
+            TestFailedException
+    {
+        if (sqlException == null) {
+            throw new TestFailedException(failMsg);
+        } else {
+            String sqlState = sqlException.getSQLState();
+            if (!expectSqlState.startsWith(sqlState)) {
+                throw new TestFailedException(sqlException);
+            }
+        }
+    }
+
+    // A convenience method which wraps a SQLException
+    private static PreparedStatement prepare(Connection conn, String query)
+        throws
+            TestFailedException
+    {
+        try {
+            return conn.prepareStatement(query);
+        } catch (SQLException e) {
+            throw new TestFailedException(e);
+        }
+    }
+
+    /**
+     * Part 1 of this test.
+     */
+    private static void testTimeoutWithFetch(Connection conn1,
+                                             Connection conn2)
+        throws
+            TestFailedException
+    {
+        System.out.println("Testing timeout with fetch operations");
+
+        try {
+            conn1.setAutoCommit(false);
+            conn2.setAutoCommit(false);
+        } catch (SQLException e) {
+            throw new TestFailedException("Should not happen", 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("u"));
+        PreparedStatement statementC = prepare(conn2, getFetchQuery("v"));
+        PreparedStatement statementD = prepare(conn2, getFetchQuery("x"));
+
+        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(SQLState.LANG_STATEMENT_CANCELLED_OR_TIMED_OUT,
+                        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);
+        }
+    }
+
+    /**
+     * Part two of this test.
+     */
+    private static void testTimeoutWithExec(Connection conn1,
+                                            Connection conn2)
+        throws
+            TestFailedException
+    {
+        System.out.println("Testing timeout with an execute operation");
+
+        try {
+            conn1.setAutoCommit(true);
+            conn2.setAutoCommit(true);
+        } catch (SQLException e) {
+            throw new TestFailedException("Should not happen", e);
+        }
+
+        PreparedStatement statementA = prepare(conn1, getExecQuery("t"));
+        PreparedStatement statementB = prepare(conn2, getExecQuery("u"));
+        
+        StatementExecutor exec0 = new StatementExecutor(statementA, false, TIMEOUT);
+        StatementExecutor exec1 = new StatementExecutor(statementB, false, 0);
+        
+        exec1.start();
+        exec0.start();
+
+        try {
+            exec0.join();
+            exec1.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(SQLState.LANG_STATEMENT_CANCELLED_OR_TIMED_OUT,
+                        exec0.getSQLException(),
+                        "exec did not time out. Execution time: "
+                        + exec0.getHighestRunTime() + " ms");
+
+        System.out.println("Statement 0 timed out");
+
+        SQLException sqlException = exec1.getSQLException();
+        if (sqlException != null) {
+            throw new TestFailedException(sqlException);
+        }
+
+        System.out.println("Statement 1 completed");
+        try {
+            statementA.close();
+            statementB.close();
+        } catch (SQLException e) {
+            throw new TestFailedException(e);
+        }
+    }
+    
+    private static void testInvalidTimeoutValue(Connection conn)
+        throws
+            TestFailedException
+    {
+        System.out.println("Testing setting a negative timeout value");
+
+        try {
+            conn.setAutoCommit(true);
+        } catch (SQLException e) {
+            throw new TestFailedException("Should not happen", e);
+        }
+
+        // Create statement
+        PreparedStatement stmt = null;
+        try {
+            stmt = conn.prepareStatement("select * from sys.systables");
+        } catch (SQLException e) {
+            throw new TestFailedException("Should not happen", e);
+        }
+        
+        // Set (invalid) timeout value - expect exception
+        try {
+            stmt.setQueryTimeout(-1);
+        } catch (SQLException e) {
+            expectException(SQLState.INVALID_QUERYTIMEOUT_VALUE, 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("Should not happen", 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);
+            }
+        }
+    }
+
+    /**
+     * Main program, makes this class invocable from the command line
+     */
+    public static void main(String[] args)
+    {
+        new SetQueryTimeoutTest().go(args);
+    }
+
+    /**
+     * The actual main bulk of this test.
+     * Sets up the environment, prepares tables,
+     * runs part 1 and 2, and shuts down.
+     */
+    public void go(String[] args)
+    {
+        System.out.println("Test SetQueryTimeoutTest starting");
+        
+        Connection conn1 = null;
+        Connection conn2 = null;
+
+        try {
+            // Load the JDBC Driver class
+            // use the ij utility to read the property file and
+            // create connections
+            ij.getPropertyArg(args);
+            conn1 = ij.startJBMS();
+            conn2 = ij.startJBMS();
+
+            System.out.println("Got connections");
+            
+            conn1.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
+            conn2.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
+
+            prepareForTimedQueries(conn1);
+            testTimeoutWithFetch(conn1, conn2);
+            testTimeoutWithExec(conn1, conn2);
+            testInvalidTimeoutValue(conn1);
+  
+            System.out.println("Test SetQueryTimeoutTest PASSED");
+        } catch (Throwable e) {
+            System.out.println("Test SetQueryTimeoutTest FAILED");
+            e.printStackTrace();
+        } finally {
+            if (conn2 != null) {
+                try {
+                    conn2.close();
+                } catch (SQLException ex) {
+                    printSQLException(ex);
+                }
+            }
+            if (conn1 != null) {
+                try {
+                    conn1.close();
+                } catch (SQLException ex) {
+                    printSQLException(ex);
+                }
+            }
+            System.out.println("Closed connections");
+        }
+    }
+}

Propchange: incubator/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/SetQueryTimeoutTest.java
------------------------------------------------------------------------------
    svn:eol-style = native