blob: ea0deb079c9e531c66a02fa3fe1f6c42b58deba4 [file] [log] [blame]
/*
* 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.openjpa.persistence.query;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.sql.DB2Dictionary;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.jdbc.sql.DerbyDictionary;
import org.apache.openjpa.jdbc.sql.InformixDictionary;
import org.apache.openjpa.jdbc.sql.OracleDictionary;
import org.apache.openjpa.jdbc.sql.SQLServerDictionary;
import org.apache.openjpa.kernel.Broker;
import org.apache.openjpa.persistence.JPAFacadeHelper;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory;
import org.apache.openjpa.persistence.OpenJPAPersistence;
import org.apache.openjpa.persistence.OpenJPAQuery;
import org.apache.openjpa.persistence.OptimisticLockException;
import org.apache.openjpa.persistence.PersistenceException;
import org.apache.openjpa.persistence.QueryTimeoutException;
import org.apache.openjpa.persistence.query.common.apps.QTimeout;
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
/**
* Tests the new query timeout hint support in the JPA 2.0 spec.
* Query timeout scenarios being tested:
* 1) By default, there is no timeout
* 2) Setting timeout to 0 is same as no timeout (JDBC defined)
* 2.1) using Map properties on createEMF (or PU properties)
* 2.2) using the QueryHint annotation
* 2.3) using setHint()
* 3) Setting timeout to msecs < DELAY value causes new
* javax.persistence.QueryTimeoutException for databases that do not
* cause a rollback or a PersistenceException if they do, when set by:
* 3.1) using persistence.xml PU properties (or createEMF Map properties)
* 3.2) using the QueryHint annotation
* 3.3) calling setHint()
* Query operations to validate through cross coverage of items #1-#3:
* a) getResultList()
* b) getSingleResult()
* c) executeUpdate() - Requires the db UPDATE trigger
* Other behaviors to test for:
* 4) Setting timeout to -1 should be treated as no timeout supplied
* 5) Setting timeout to < -1 should throw an IllegalArgumentExpection
* 6) Updates after EM.find()/findAll() are not affected by query timeout
* Exception generation to test, covered by 31c and 33c:
* If the DB query timeout does not cause a transaction rollback, then a
* QueryTimeoutException should be thrown.
* Else if the DB query timeout causes a transaction rollback, then a
* PersistenceException should be thrown instead of a QTE.
*
* @version $Rev$ $Date$
*/
public class TestQueryTimeout extends SQLListenerTestCase {
private DBDictionary dict = null;
// skip test due to unsupported/untested db
private boolean skipTests = false;
// skip some tests due to no DB triggers to cause delay timeout exceptions
private boolean skipExceptionTests = false;
// skip some tests due to no SELECT query timeouts being generated by the DB
private boolean noSelectTimeouts = false;
// default native query string to use for update testing
private String nativeUpdateStr = new String("UPDATE QTimeout SET " +
"stringField = ? WHERE mod(DELAY(2,id),2)=0");
@Override
public void setUp() {
super.setUp(CLEAR_TABLES, QTimeout.class);
getLog().trace("setUp()");
dict = ((JDBCConfiguration) emf.getConfiguration())
.getDBDictionaryInstance();
assertNotNull(dict);
// determine if we should run our tests on this DB platform and what
// exception type to catch
if (dict.supportsQueryTimeout) {
if (dict instanceof DerbyDictionary) {
// create some initial entities
setupCreateEntities();
// create delay function only on Derby, other DBs manual setup
setupCreateDBFunction();
// create triggers on all DBs
// If this fails, skip running the timeout tests on this DB
skipExceptionTests = !setupCreateDBTriggers();
} else if (dict instanceof DB2Dictionary) {
setupCreateEntities();
// basic timeout tests fail to pop w/o multiple connections
skipTests = true;
skipExceptionTests = true;
} else if ((dict instanceof SQLServerDictionary) ||
(dict instanceof OracleDictionary)) {
// create some initial entities
setupCreateEntities();
// skip running the SELECT timeout tests due to:
// C# Delay.dll code on SQLServer
// Embedded Java code on Oracle
skipTests = true;
noSelectTimeouts = true;
// create triggers on all DBs
skipExceptionTests = !setupCreateDBTriggers();
// special case for MS SQL Server and Oracle, as our
// Function code doesn't support returning the id for now
nativeUpdateStr = "UPDATE QTimeout SET stringField = ? " +
"WHERE id = 1";
} else if (dict instanceof InformixDictionary) {
// create some initial entities
setupCreateEntities();
// create triggers on all DBs
skipExceptionTests = !setupCreateDBTriggers();
} else {
// unknown db, so skip all timeout tests
skipTests = skipExceptionTests = noSelectTimeouts = true;
setTestsDisabled(true);
getLog().info("TestQueryTimeout tests are being skipped, due " +
"to " + dict.platform + " not supporting Query Timeouts.");
}
} else {
getLog().info("TestQueryTimeout tests are being skipped, " +
"due to " + dict.platform + " not supporting Query Timeouts.");
skipTests = skipExceptionTests = true;
setTestsDisabled(true);
}
}
/**
* Scenario being tested: 1a) By default, there is no timeout for queries.
* Expected Results: The DELAY function is being called and the query takes
* 6000+ msecs to complete.
*/
public void testQueryTimeout1a() {
if (skipTests) {
getLog().trace("testQueryTimeout1a - test is " +
"being skipped for " + dict.platform);
return;
}
getLog().trace("testQueryTimeout1a() - No Query timeout");
EntityManager em = null;
try {
em = emf.createEntityManager();
assertNotNull(em);
Query q = em.createNamedQuery("NoHintList");
// verify no default javax.persistence.query.timeout is supplied
Map<String, Object> hints = q.getHints();
assertFalse(hints.containsKey("javax.persistence.query.timeout"));
try {
long startTime = System.currentTimeMillis();
@SuppressWarnings("unchecked")
List results = q.getResultList();
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
getLog().trace("testQueryTimeout1a() - NoHintList runTime" +
" msecs=" + runTime);
// Hack - Windows sometimes returns 5999 instead of 6000+
assertTrue("Should have taken 6+ secs, but was msecs=" +
runTime, runTime > 5900);
assertEquals("Verify we found 2 results.", 2, results.size());
} catch (Exception e) {
fail("Unexpected testQueryTimeout1a() exception = " + e);
}
} finally {
if ((em != null) && em.isOpen()) {
em.close();
}
}
}
/**
* Scenario being tested: 1c) By default, there is no timeout for updates.
* Expected Results: The DELAY function is being called and the query takes
* 2000+ msecs to complete.
*/
public void testQueryTimeout1c() {
if (skipTests) {
getLog().trace("testQueryTimeout1c - test is " +
"being skipped for " + dict.platform);
return;
}
getLog().trace("testQueryTimeout1c() - No executeUpdate timeout");
EntityManager em = null;
try {
em = emf.createEntityManager();
assertNotNull(em);
Query q = em.createQuery("UPDATE QTimeout q SET q.stringField = " +
":strVal WHERE q.id = 1");
q.setParameter("strVal", new String("updated"));
// verify no default javax.persistence.query.timeout is supplied
Map<String, Object> hints = q.getHints();
assertFalse(hints.containsKey("javax.persistence.query.timeout"));
try {
long startTime = System.currentTimeMillis();
em.getTransaction().begin();
int count = q.executeUpdate();
em.getTransaction().commit();
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
getLog().trace("testQueryTimeout1c() - executeUpdate runTime " +
"msecs=" + runTime);
assertTrue("Verify we received one result.", (count == 1));
// Hack - Windows sometimes returns 1999 instead of 2000+
assertTrue("Should have taken 2+ secs, but was msecs=" +
runTime, runTime > 1900);
} catch (Exception e) {
fail("Unexpected testQueryTimeout1c() exception = " + e);
}
} finally {
if ((em != null) && em.isOpen()) {
em.close();
}
}
}
/**
* Scenario being tested: 2.1.b) Explicit Map of properties to createEMF
* with timeout=0 is treated the same as the default no query timeout.
* Expected Results: The DELAY function is being called and the query
* takes 2000+ msecs to complete.
*/
public void testQueryTimeout21b() {
if (skipTests) {
getLog().trace("testQueryTimeout21b - test is " +
"being skipped for " + dict.platform);
return;
}
getLog().trace("testQueryTimeout21b() - Map(timeout=0)");
OpenJPAEntityManagerFactory emf = null;
OpenJPAEntityManager em = null;
Integer setTime = 0;
// create the Map to test overrides
Map<String,String >props = new HashMap<>();
props.put("javax.persistence.query.timeout", "0");
try {
// create our EMF with our timeout property
emf = OpenJPAPersistence.createEntityManagerFactory(
"qtimeout-no-properties", "persistence3.xml", props);
assertNotNull(emf);
// verify Map properties updated the config
OpenJPAConfiguration conf = emf.getConfiguration();
assertNotNull(conf);
assertEquals("Map provided query timeout", setTime.intValue(),
conf.getQueryTimeout());
// verify no default javax.persistence.query.timeout is supplied
// as the Map properties are not passed through as hints
em = emf.createEntityManager();
assertNotNull(em);
OpenJPAQuery q = em.createNamedQuery("NoHintSingle");
Map<String, Object> hints = q.getHints();
assertFalse(hints.containsKey("javax.persistence.query.timeout"));
// verify internal config values were updated
assertEquals("Map provided query timeout", setTime.intValue(),
q.getFetchPlan().getQueryTimeout());
try {
long startTime = System.currentTimeMillis();
Object result = q.getSingleResult();
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
getLog().trace("testQueryTimeout21b() - NoHintSingle runTime " +
"msecs=" + runTime);
// Hack - Windows sometimes returns 1999 instead of 2000+
assertTrue("Should have taken 2+ secs, but was msecs=" +
runTime, runTime > 1900);
assertNotNull("Verify we received a result.", result);
} catch (Exception e) {
fail("Unexpected testQueryTimeout21b() exception = " + e);
}
} finally {
if ((em != null) && em.isOpen()) {
em.close();
}
}
}
/**
* Scenario being tested: 2.2.a) Explicit annotated QueryHint of timeout=0
* is treated the same as the default no timeout for queries.
* Expected Results: The DELAY function is being called and the query
* takes 6000+ msecs to complete.
*/
public void testQueryTimeout22a() {
if (skipTests) {
getLog().trace("testQueryTimeout22a - test is " +
"being skipped for " + dict.platform);
return;
}
getLog().trace("testQueryTimeout22a() - QueryHint=0");
EntityManager em = null;
try {
em = emf.createEntityManager();
assertNotNull(em);
Query q = em.createNamedQuery("Hint0msec");
// verify javax.persistence.query.timeout is supplied
Map<String, Object> hints = q.getHints();
assertTrue(hints.containsKey("javax.persistence.query.timeout"));
Integer timeout = new Integer(
(String) hints.get("javax.persistence.query.timeout"));
getLog().trace("testQueryTimeout22a() - Retrieved hint " +
"javax.persistence.query.timeout=" + timeout);
assertEquals(timeout, new Integer(0));
try {
long startTime = System.currentTimeMillis();
@SuppressWarnings("unchecked")
List results = q.getResultList();
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
getLog().trace("testQueryTimeout22a() - Hint0msec runTime " +
"msecs=" + runTime);
// Hack - Windows sometimes returns 5999 instead of 6000+
assertTrue("Should have taken 6+ secs, but was msecs=" +
runTime, runTime > 5900);
assertEquals("Verify we found 2 results.", 2, results.size());
} catch (Exception e) {
fail("Unexpected testQueryTimeout22a() exception = " + e);
}
} finally {
if ((em != null) && em.isOpen()) {
em.close();
}
}
}
/**
* Scenario being tested: 2.3.b) Explicit setHint of timeout=0 is treated
* the same as the default no timeout for queries.
* Expected Results: The DELAY function is being called and the query
* takes 2000+ msecs to complete.
*/
public void testQueryTimeout23b() {
if (skipTests) {
getLog().trace("testQueryTimeout23b - test is " +
"being skipped for " + dict.platform);
return;
}
Integer setTime = 0;
getLog().trace("testQueryTimeout23b() - setHint(" + setTime + ")");
EntityManager em = null;
try {
em = emf.createEntityManager();
assertNotNull(em);
Query q = em.createNamedQuery("NoHintSingle");
// verify no default javax.persistence.query.timeout is supplied
Map<String, Object> hints = q.getHints();
assertFalse(hints.containsKey("javax.persistence.query.timeout"));
// update the timeout value to 0 and verify it was set
getLog().trace("testQueryTimeout23b() - Setting hint " +
"javax.persistence.query.timeout=" + setTime);
q.setHint("javax.persistence.query.timeout", setTime);
hints = q.getHints();
assertTrue(hints.containsKey("javax.persistence.query.timeout"));
Integer timeout = (Integer) hints.get(
"javax.persistence.query.timeout");
getLog().trace("testQueryTimeout23b() - Retrieved hint " +
"javax.persistence.query.timeout=" + timeout);
assertEquals(timeout, setTime);
try {
long startTime = System.currentTimeMillis();
Object result = q.getSingleResult();
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
getLog().trace("testQueryTimeout23b() - NoHintSingle runTime " +
"msecs=" + runTime);
// Hack - Windows sometimes returns 1999 instead of 2000+
assertTrue("Should have taken 2+ secs, but was msecs=" +
runTime, runTime > 1900);
assertNotNull("Verify we received a result.", result);
} catch (Exception e) {
fail("Unexpected testQueryTimeout23b() exception = " + e);
}
} finally {
if ((em != null) && em.isOpen()) {
em.close();
}
}
}
/**
* Scenario being tested: 3.1.c) Explicit persistence.xml provided PU
* property of timeout=1000 msecs will cause the query to timeout.
* Expected Results: The DELAY function is being called and the query
* takes 2000+ msecs to complete.
*/
public void testQueryTimeout31c() {
if (skipExceptionTests) {
getLog().trace("testQueryTimeout31c - test is " +
"being skipped for " + dict.platform);
return;
}
getLog().trace("testQueryTimeout31c() - PU(timeout=1000), " +
"executeUpdate timeout");
OpenJPAEntityManagerFactory emf = null;
OpenJPAEntityManager em = null;
Integer setTime = 1000;
boolean bRetry = true;
try {
// create our EMF with our PU set timeout property
emf = OpenJPAPersistence.createEntityManagerFactory(
"qtimeout-1000msecs", "persistence3.xml");
assertNotNull(emf);
// verify PU properties updated the config
OpenJPAConfiguration conf = emf.getConfiguration();
assertNotNull(conf);
assertEquals("PU provided query timeout", setTime.intValue(),
conf.getQueryTimeout());
// create EM and Query
em = emf.createEntityManager();
assertNotNull(em);
OpenJPAQuery q = em.createNativeQuery(nativeUpdateStr);
q.setParameter(1, new String("updated"));
// verify no default javax.persistence.query.timeout is supplied
Map<String, Object> hints = q.getHints();
assertFalse(hints.containsKey("javax.persistence.query.timeout"));
// verify internal config values were updated
assertEquals("PU provided query timeout", setTime.intValue(),
q.getFetchPlan().getQueryTimeout());
// verify queryTimeout on EM find operations
em.getTransaction().begin();
// if we get a QTE, then retry this once to prove no db rollback
for (int i=0; i<=1 && bRetry; i++)
{
try {
long startTime = System.currentTimeMillis();
@SuppressWarnings("unused")
int count = q.executeUpdate();
// exception should occur before commit
em.getTransaction().commit();
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
getLog().trace("testQueryTimeout31c() - executeUpdate " +
"runTime msecs=" + runTime);
fail("QueryTimeout for executeUpdate failed to cause an " +
"Exception in testQueryTimeout31c(" + setTime +
" mscs), runTime msecs=" + runTime);
} catch (Exception e) {
// expected - Should cause a QueryTimeoutException for:
// Derby, MS SQL
// should only retry the statement if db did not rollback
// which should be a QueryTimeoutException
bRetry = checkException("testQueryTimeout31c()", e);
// we're only retrying to prove the right exception type
// was thrown and the transaction wasn't rolled back
if (bRetry)
getLog().trace("testQueryTimeout31c() - retrying... ");
}
}
} finally {
if ((em != null) && em.isOpen()) {
if (em.getTransaction().isActive())
em.getTransaction().rollback();
em.close();
}
}
}
/**
* Scenario being tested: 3.2.a) Explicit annotated QueryHint of
* timeout=1000 msecs will override the PU and Map provided timeouts
* and cause the query to timeout.
* Expected Results: QueryTimeoutException or PersistenceException
*/
public void testQueryTimeout32a() {
if (skipExceptionTests || noSelectTimeouts) {
getLog().trace("testQueryTimeout32a - test is " +
"being skipped for " + dict.platform);
return;
}
getLog().trace("testQueryTimeout32a() - PU(1000), Map(0), " +
"QueryHint(1000)");
OpenJPAEntityManagerFactory emf = null;
OpenJPAEntityManager em = null;
Integer setTime = 0;
// create the Map to test overrides
Map<String,String> props = new HashMap<>();
props.put("javax.persistence.query.timeout", "0");
try {
// create our EMF with our PU set timeout property
emf = OpenJPAPersistence.createEntityManagerFactory(
"qtimeout-1000msecs", "persistence3.xml", props);
assertNotNull(emf);
// verify Map properties overrode the PU properties in config
OpenJPAConfiguration conf = emf.getConfiguration();
assertNotNull(conf);
assertEquals("Map provided query timeout", setTime.intValue(),
conf.getQueryTimeout());
// create EM and named query
em = emf.createEntityManager();
assertNotNull(em);
OpenJPAQuery q = em.createNamedQuery("Hint1000msec");
setTime = 1000;
// verify javax.persistence.query.timeout hint via annotation set
Map<String, Object> hints = q.getHints();
assertTrue(hints.containsKey("javax.persistence.query.timeout"));
Integer timeout = new Integer((String) hints.get(
"javax.persistence.query.timeout"));
getLog().trace(
"testQueryTimeout32a() - Found javax.persistence.query.timeout="
+ timeout);
assertTrue("Expected to find a javax.persistence.query.timeout="
+ setTime, (timeout.intValue() == setTime.intValue()));
// verify internal config values were updated
assertEquals("QueryHint provided query timeout", setTime.intValue(),
q.getFetchPlan().getQueryTimeout());
try {
long startTime = System.currentTimeMillis();
@SuppressWarnings( { "unchecked", "unused" })
List results = q.getResultList();
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
getLog().trace(
"testQueryTimeout32a() - Hint1000msec runTime msecs="
+ runTime);
//assertEquals("Should never get valid results due to the " +
// "timeout.", 2, results.size());
fail("QueryTimeout annotation failed to cause an Exception " +
"in testQueryTimeout32a(" + setTime +
" msecs), runTime msecs=" + runTime);
} catch (Exception e) {
// expected
checkException("testQueryTimeout32a()", e);
}
} finally {
if ((em != null) && em.isOpen()) {
em.close();
}
}
}
/**
* Scenario being tested: 3.3.b) Explicit setHint of timeout to 1000 msecs
* will cause the query to timeout.
* Expected Results: QueryTimeoutException or PersistenceException
*/
public void testQueryTimeout33b() {
if (skipExceptionTests || noSelectTimeouts) {
getLog().trace("testQueryTimeout33b - test is " +
"being skipped for " + dict.platform);
return;
}
Integer setTime = 1000;
getLog().trace("testQueryTimeout33b() - setHint(" + setTime + ")");
EntityManager em = null;
try {
em = emf.createEntityManager();
assertNotNull(em);
Query q = em.createNamedQuery("NoHintSingle");
// verify no default javax.persistence.query.timeout is supplied
Map<String, Object> hints = q.getHints();
assertFalse(hints.containsKey("javax.persistence.query.timeout"));
// update the timeout value and verify it was set
getLog().trace("testQueryTimeout33b() - Setting hint " +
"javax.persistence.query.timeout=" + setTime);
q.setHint("javax.persistence.query.timeout", setTime);
hints = q.getHints();
assertTrue(hints.containsKey("javax.persistence.query.timeout"));
Integer timeout = (Integer) hints.get(
"javax.persistence.query.timeout");
assertEquals(timeout, setTime);
try {
long startTime = System.currentTimeMillis();
@SuppressWarnings("unused")
Object result = q.getSingleResult();
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
getLog().trace(
"testQueryTimeout33b() - NoHintSingle runTime msecs="
+ runTime);
//assertNull("Should never get valid result due to the timeout."
// , result);
fail("QueryTimeout annotation failed to cause an Exception " +
"in testQueryTimeout33b(" + setTime +
" mscs), runTime msecs=" + runTime);
} catch (Exception e) {
// expected
checkException("testQueryTimeout33b()", e);
}
} finally {
if ((em != null) && em.isOpen()) {
em.close();
}
}
}
/**
* Scenario being tested: 3.3.c) Explicit setHint of timeout to 1000 msecs
* will cause the PU provided timeout=0 value to be overridden and the
* executeUpdate to timeout.
* Expected Results: QueryTimeoutException (Derby) or PersistenceException
*/
public void testQueryTimeout33c() {
if (skipExceptionTests) {
getLog().trace("testQueryTimeout33c - test is " +
"being skipped for " + dict.platform);
return;
}
getLog().trace("testQueryTimeout33c() - PU(timeout=0), setHint(1000)," +
" executeUpdate timeout");
OpenJPAEntityManagerFactory emf = null;
OpenJPAEntityManager em = null;
Integer setTime = 0;
boolean bRetry = true;
try {
// create our EMF with our PU set timeout property
emf = OpenJPAPersistence.createEntityManagerFactory(
"qtimeout-0msecs", "persistence3.xml");
assertNotNull(emf);
// verify PU properties updated the config
OpenJPAConfiguration conf = emf.getConfiguration();
assertNotNull(conf);
assertEquals("PU provided no query timeout", setTime.intValue(),
conf.getQueryTimeout());
// create EM and Query
em = emf.createEntityManager();
assertNotNull(em);
// Following fails to cause a SQLException, but takes 2+ secs
// Query q = em.createQuery("UPDATE QTimeout q SET q.stringField =
// + ":strVal WHERE q.id > 0");
// q.setParameter("strVal", new String("updated"));
// Following fails to cause a SQLException, but takes 2+ secs
// Query q = em.createNativeQuery("INSERT INTO QTimeout (id, " +
// "stringField) VALUES (?,?)");
// q.setParameter(1, 99);
// q.setParameter(2, new String("inserted"));
OpenJPAQuery q = em.createNativeQuery(nativeUpdateStr);
q.setParameter(1, new String("updated"));
// verify no default javax.persistence.query.timeout is supplied
Map<String, Object> hints = q.getHints();
assertFalse(hints.containsKey("javax.persistence.query.timeout"));
// update the query timeout value and verify it was set
setTime = 1000;
getLog().trace("testQueryTimeout33c() - Setting hint " +
"javax.persistence.query.timeout=" + setTime);
q.setHint("javax.persistence.query.timeout", setTime);
hints = q.getHints();
assertTrue(hints.containsKey("javax.persistence.query.timeout"));
Integer timeout = (Integer) hints.get(
"javax.persistence.query.timeout");
assertEquals(timeout, setTime);
// verify internal config values were updated
assertEquals("PU provided query timeout", setTime.intValue(),
q.getFetchPlan().getQueryTimeout());
em.getTransaction().begin();
for (int i=0; i<=1 && bRetry; i++)
{
try {
long startTime = System.currentTimeMillis();
@SuppressWarnings("unused")
int count = q.executeUpdate();
em.getTransaction().commit();
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
getLog().trace("testQueryTimeout33c() - executeUpdate " +
"runTime msecs=" + runTime);
fail("QueryTimeout for executeUpdate failed to cause an " +
"Exception in testQueryTimeout33c(" + setTime +
" mscs), runTime msecs=" + runTime);
} catch (Exception e) {
// expected - Should cause a QueryTimeoutException for:
// Derby, MS SQL
// should only retry the statement if db did not rollback
// which should be a QueryTimeoutException
bRetry = checkException("testQueryTimeout33c()", e);
// we're only retrying to prove the right exception type
// was thrown and the transaction wasn't rolled back
if (bRetry)
getLog().trace("testQueryTimeout33c() - retrying... ");
}
}
} finally {
if ((em != null) && em.isOpen()) {
if (em.getTransaction().isActive())
em.getTransaction().rollback();
em.close();
}
}
}
/**
* Scenario being tested: 4) Timeout of -1 should be treated the same
* as the default no timeout scenario.
* Expected Results: The DELAY function is being called and the query
* takes 2000+ msecs to complete.
*/
public void testQueryTimeout4() {
if (skipTests) {
getLog().trace("testQueryTimeout4 - test is " +
"being skipped for " + dict.platform);
return;
}
Integer setTime = -1;
getLog().trace("testQueryTimeout4() - setHint(" + setTime + ")");
EntityManager em = null;
try {
em = emf.createEntityManager();
assertNotNull(em);
Query q = em.createNamedQuery("NoHintSingle");
// verify no default javax.persistence.query.timeout is supplied
Map<String, Object> hints = q.getHints();
assertFalse(hints.containsKey("javax.persistence.query.timeout"));
// update the timeout value to -1 and verify it was set
getLog().trace("testQueryTimeout4() - Setting hint " +
"javax.persistence.query.timeout="
+ setTime);
q.setHint("javax.persistence.query.timeout", setTime);
hints = q.getHints();
assertTrue(hints.containsKey("javax.persistence.query.timeout"));
Integer timeout = (Integer) hints.get(
"javax.persistence.query.timeout");
getLog().trace("testQueryTimeout4() - Retrieved hint " +
"javax.persistence.query.timeout="
+ timeout);
assertEquals(timeout, setTime);
try {
long startTime = System.currentTimeMillis();
Object result = q.getSingleResult();
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
getLog().trace(
"testQueryTimeout4() - NoHintSingle runTime msecs="
+ runTime);
// Hack - Windows sometimes returns 1999 instead of 2000+
assertTrue("Should have taken 2+ secs, but was msecs=" +
runTime, runTime > 1900);
assertNotNull("Verify we received a result.", result);
} catch (Exception e) {
fail("Unexpected testQueryTimeout4() exception = " + e);
}
} finally {
if ((em != null) && em.isOpen()) {
em.close();
}
}
}
/**
* Scenario being tested: 5) Setting timeout to < -1 should throw an
* IllegalArgumentExpection
* Expected Results: IllegalArgumentException
*/
public void testQueryTimeout5() {
Integer setTime = -2000;
getLog().trace("testQueryTimeout5() - setHint(" + setTime + ")");
EntityManager em = null;
try {
em = emf.createEntityManager();
assertNotNull(em);
Query q = em.createNamedQuery("NoHintSingle");
// verify no default javax.persistence.query.timeout is supplied
Map<String, Object> hints = q.getHints();
assertFalse(hints.containsKey("javax.persistence.query.timeout"));
// update the timeout value to -2000 and verify it was set
getLog().trace("testQueryTimeout5() - Setting hint " +
"javax.persistence.query.timeout="
+ setTime);
q.setHint("javax.persistence.query.timeout", setTime);
fail("Expected testQueryTimeout5() to throw a " +
"IllegalArgumentException");
} catch (Exception e) {
// expected - setHint(-2000) should cause IllegalArgumentException
checkException("testQueryTimeout5()", e,
IllegalArgumentException.class, null);
} finally {
if ((em != null) && em.isOpen()) {
em.close();
}
}
}
/**
* Scenario being tested: 6) PU Query timeout hints do not affect EM
* operations like updating Entities returned by EM.find()/findAll()
* Expected Results: The DELAY function is being called and the update
* takes 2000+ msecs to complete.
*/
public void testQueryTimeout6() {
if (skipTests) {
getLog().trace("testQueryTimeout6 - test is " +
"being skipped for " + dict.platform);
return;
}
getLog().trace("testQueryTimeout6() - No EM.find() update timeout");
OpenJPAEntityManagerFactory emf = null;
OpenJPAEntityManager em = null;
Integer setTime = 1000;
try {
// create our EMF with our PU set timeout property
emf = OpenJPAPersistence.createEntityManagerFactory(
"qtimeout-1000msecs", "persistence3.xml");
assertNotNull(emf);
// verify PU properties updated the config
OpenJPAConfiguration conf = emf.getConfiguration();
assertNotNull(conf);
assertEquals("PU provided timeout", setTime.intValue(),
conf.getQueryTimeout());
// create EM
em = emf.createEntityManager();
assertNotNull(em);
try {
long startTime = System.currentTimeMillis();
QTimeout qt = em.find(QTimeout.class, 1);
em.getTransaction().begin();
qt.setStringField("updated");
em.flush();
em.getTransaction().commit();
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
getLog().trace("testQueryTimeout6() - EM find/update runTime" +
" msecs=" + runTime);
// Hack - Windows sometimes returns 1999 instead of 2000+
assertTrue("Should have taken 2+ secs, but was msecs=" +
runTime, runTime > 1900);
em.clear();
qt = em.find(QTimeout.class, 1);
assertEquals("Verify the entity was updated.",
qt.getStringField(), "updated");
} catch (Exception e) {
// setting a timeout property via PU or Map shouldn't cause a
// timeout exception
fail("Unexpected testQueryTimeout6() exception = " + e);
}
} finally {
if ((em != null) && em.isOpen()) {
em.close();
}
}
}
/**
* Internal setup convenience method to create some entities for testing
*/
private void setupCreateEntities() {
getLog().trace("setupCreateEntities()");
String[] _strings = new String[] { "a", "b", "c" };
QTimeout qt = null;
EntityManager em = null;
// create some initial entities
try {
em = emf.createEntityManager();
assertNotNull(em);
getLog().trace("setupCreateEntities() - creating 3 Qtimeout");
em.getTransaction().begin();
for (int i = 0; i < _strings.length; i++) {
qt = new QTimeout(i, _strings[i]);
em.persist(qt);
}
em.getTransaction().commit();
} catch (Exception e) {
fail("setupCreateEntities() - Unexpected Exception - " + e);
} finally {
if ((em != null) && em.isOpen()) {
em.close();
}
}
}
/**
* Create delay function only on Derby, other DBs require manual setup
*/
private void setupCreateDBFunction() {
if (dict instanceof DerbyDictionary) {
getLog().trace("setupCreateDBFunction()");
// remove existing function if it exists and recreate
try {
exec(true, 0, "DROP TRIGGER t1");
exec(true, 0, "DROP FUNCTION DELAY");
exec(false, 0, "CREATE FUNCTION DELAY(SECONDS INTEGER, " +
"VALUE INTEGER) RETURNS INTEGER PARAMETER STYLE JAVA " +
"NO SQL LANGUAGE JAVA EXTERNAL NAME " +
"'org.apache.openjpa.persistence." +
"query.TestQueryTimeout.delay'");
} catch (SQLException sqe) {
fail(sqe.toString());
}
} else {
getLog().trace("setupCreateDBFunction() - skipping as DB != Derby");
}
}
/**
* Create triggers on all DBs
* @param dict
* @return boolean - true if successful, otherwise false
*/
private boolean setupCreateDBTriggers() {
boolean brc = false;
getLog().trace("setupCreateDBTriggers()");
// create triggers on all DBs
try {
if ((dict instanceof DerbyDictionary) ||
(dict instanceof DB2Dictionary)) {
getLog().trace("setupCreateDBTriggers() - creating BEFORE " +
"TRIGGER for Derby and DB2");
exec(true, 0, "DROP TRIGGER t1");
// DB2 needs multiple connections and longer delays to timeout
exec(false, 0, "CREATE TRIGGER t1 NO CASCADE BEFORE UPDATE " +
"ON qtimeout FOR EACH ROW MODE DB2SQL values DELAY(2,-1)");
// exec(true, 0, "DROP TRIGGER t2");
// exec(false, 0, "CREATE TRIGGER t2 NO CASCADE BEFORE " +
// "INSERT ON qtimeout FOR EACH ROW MODE DB2SQL " +
// "values DELAY(2,-2)");
// Don't include a DELETE trigger - slows down the DROP_TABLES
// cleanup between tests
// exec(false, 0, "CREATE TRIGGER t3 NO CASCADE BEFORE DELETE "+
// "ON qtimeout FOR EACH ROW MODE DB2SQL values DELAY(2,-3)");
brc = true;
} else if (dict instanceof SQLServerDictionary) {
// These may have already been created by the DDL
getLog().trace("setupCreateDBTriggers() - creating BEFORE " +
"UPDATE TRIGGER for MS SQL Server");
exec(true, 0, "DROP TRIGGER t1");
exec(false, 0, "CREATE TRIGGER t1 ON QTimeout FOR UPDATE " +
"AS EXTERNAL NAME Delay.Delay.delay");
// exec(true, 0, "DROP TRIGGER t2");
// exec(false, 0, "CREATE OR REPLACE TRIGGER t2 ON QTimeout " +
// "FOR INSERT AS EXTERNAL NAME Delay.Delay.delay");
brc = true;
} else if (dict instanceof OracleDictionary) {
// These may have already been created by the DDL
getLog().trace("setupCreateDBTriggers() - creating BEFORE " +
"UPDATE TRIGGER for Oracle");
exec(false, 0, "CREATE OR REPLACE TRIGGER \"OPENJPA\".T1 " +
"BEFORE UPDATE OF \"ID\", \"STRINGFIELD\", " +
"\"VERSIONFIELD\" ON \"OPENJPA\".\"QTIMEOUT\" CALL DELAY");
// exec(false, 0, "CREATE OR REPLACE TRIGGER \"OPENJPA\".T2 " +
// "BEFORE INSERT ON \"OPENJPA\".\"QTIMEOUT\" CALL DELAY");
brc = true;
} else if (dict instanceof InformixDictionary) {
// These may have already been created by the DDL
getLog().trace("setupCreateDBTriggers() - creating BEFORE " +
"UPDATE TRIGGER for Informix");
exec(true, 0, "DROP TRIGGER t1");
// exec(false, 0, "CREATE TRIGGER t1 UPDATE " +
// "of Id, stringField, versionField ON QTimeout " +
// "BEFORE(EXECUTE FUNCTION delay(5,-1))");
exec(false, 0, "CREATE TRIGGER t1 UPDATE " +
"of Id, stringField, versionField ON QTimeout " +
"BEFORE(EXECUTE PROCEDURE delay5())");
brc = true;
} else {
getLog().info("TestQueryTimeout tests are being skipped as " +
"triggers were not created for " + dict.platform);
}
} catch (SQLException sqe) {
if (dict instanceof DerbyDictionary) {
// Always fail if we couldn't create triggers in Derby
fail(sqe.toString());
} else {
// just disable tests for other DBs
getLog().info("TestQueryTimeout tests are being skipped, " +
"due to DB delay() function missing and/or problems " +
"creating the required triggers. " + dict.platform +
" requires manual setup steps for these tests.");
getLog().trace("setupCreateDBTriggers() failed with " +
"SQLException = ", sqe);
}
}
return brc;
}
/**
* Internal convenience method to execute SQL statements
*
* @param em
* @param sql
* @param timeoutSecs
* @param fail
*/
private void exec(boolean ignoreExceptions, int timeoutSecs, String sql)
throws SQLException {
EntityManager em = null;
Statement s = null;
try {
em = emf.createEntityManager();
assertNotNull(em);
Broker broker = JPAFacadeHelper.toBroker(em);
Connection conn = (Connection) broker.getConnection();
s = conn.createStatement();
if (timeoutSecs > 0) {
s.setQueryTimeout(timeoutSecs);
}
getLog().trace("execute(" + sql + ")");
s.execute(sql);
} catch (SQLException sqe) {
if (!ignoreExceptions) {
// fail(sqe.toString());
throw sqe;
}
} finally {
if (s != null) {
try {
s.close();
} catch (Exception e) {
// ignore
}
}
if ((em != null) && em.isOpen()) {
em.close();
}
}
}
/**
* Internal convenience method for checking that the given Exception matches
* the expected type for a given DB platform.
*
* @param test
* @param e
* @return boolean indicating if operation can be retried
*/
private boolean checkException(String test, Exception e) {
String eStr = new String("query statement timeout");
boolean bRetry = matchesExpectedException(QueryTimeoutException.class,
e, eStr);
boolean bRollback = matchesExpectedException(PersistenceException.class,
e, eStr);
// 31c and 33c fail on MSSQL2005, so this is a special case to handle it
boolean bLockFailed = matchesExpectedException(OptimisticLockException.class,
e, "Unable to obtain an object lock");
// no easy way to determine exact Exception type for all DBs
assertTrue(test + " - UNEXPECTED Exception = " + e,
(bRetry || bRollback || bLockFailed));
getLog().trace(test + " - Caught expected Exception = ", e);
return bRetry;
}
/**
* Internal convenience method for checking that the given Exception matches
* the expected type.
*
* @param test case name
* @param tested exception type
* @param expected exception type
* @param eStr an optional substring to match in the exception text
*/
private void checkException(String test, Exception tested,
Class<?> expected, String eStr) {
assertTrue(test + " - UNEXPECTED Exception = " + tested,
matchesExpectedException(expected, tested, eStr));
getLog().trace(test + " - Caught expected Exception = ", tested);
}
/**
* Internal convenience method for checking that the given Exception matches
* the expected type.
*
* @param expected
* @param tested
* @param eStr an optional substring to match in the exception text
* @return true if the exception matched, false otherwise
*/
private boolean matchesExpectedException(Class<?> expected,
Exception tested, String eStr) {
assertNotNull(expected);
boolean exMatched = false;
if (tested != null) {
Class<?> testExClass = tested.getClass();
exMatched = expected.isAssignableFrom(testExClass);
if (exMatched && eStr != null) {
// make sure it is our expected exception text from
// localizer.properties
exMatched = (tested.getMessage().indexOf(eStr) != -1);
}
}
return exMatched;
}
/**
* This is the user-defined DB FUNCTION which is called from our queries to
* sleep and cause timeouts, based on seconds.
*
* @param secs
* @param value
* @return value
* @throws SQLException
*/
public static int delay(int secs, int value) throws SQLException {
try {
/*
if (value >= 0) {
System.out.println(" Native SQL called delay(secs=" + secs +
",value=" + value + ")");
} else {
System.out.println(" Trigger called delay(secs=" + secs +
",value=" + value + ")");
}
*/
Thread.sleep(secs * 1000);
} catch (InterruptedException e) {
// Ignore
}
return value;
}
}