diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java index 0388ff2f9..f64347ec8 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java @@ -4184,7 +4184,7 @@ public class DBDictionary * implementation of setting query and lock timeouts. * * @param stmnt - * @param fetch + * @param fetch - optional lock and query timeouts in milliseconds * @param forUpdate - true if we should also try setting a lock timeout * @throws SQLException */ @@ -4210,7 +4210,7 @@ public class DBDictionary * implementation of setting query and lock timeouts. * * @param stmnt - * @param fetch + * @param conf - optional lock and query timeouts in milliseconds * @param forUpdate - true if we should also try setting a lock timeout * @throws SQLException */ @@ -4229,22 +4229,44 @@ public class DBDictionary } /** - * This method is to provide override for non-JDBC or JDBC-like - * implementation of setting query timeout. + * Provides the default validation handling of setting a query timeout. + * @param stmnt + * @param timeout in milliseconds + * @throws SQLException */ public void setQueryTimeout(PreparedStatement stmnt, int timeout) throws SQLException { - if (supportsQueryTimeout && timeout >= 0) { - if (timeout > 0 && timeout < 1000) { + if (supportsQueryTimeout) { + if (timeout == -1) { + // special OpenJPA allowed case denoting no timeout + timeout = 0; + } else if (timeout < 0) { + if (log.isWarnEnabled()) + log.warn(_loc.get("invalid-timeout", new Integer(timeout))); + return; + } else if (timeout > 0 && timeout < 1000) { + // round up to 1 sec timeout = 1000; - Log log = conf.getLog(JDBCConfiguration.LOG_JDBC); if (log.isWarnEnabled()) log.warn(_loc.get("millis-query-timeout")); } - stmnt.setQueryTimeout(timeout / 1000); + setStatementQueryTimeout(stmnt, timeout); } } + /** + * Allow subclasses to provide DB unique override implementations of + * setting query timeouts, while preserving the default timeout logic + * in the public setQueryTimeout method. + * @param stmnt + * @param timeout in milliseconds + * @throws SQLException + */ + protected void setStatementQueryTimeout(PreparedStatement stmnt, + int timeout) throws SQLException { + // JDBC uses seconds, so we'll do a simple round-down conversion here + stmnt.setQueryTimeout(timeout / 1000); + } ////////////////////////////////////// // ConnectionDecorator implementation diff --git a/openjpa-kernel/src/main/resources/org/apache/openjpa/util/localizer.properties b/openjpa-kernel/src/main/resources/org/apache/openjpa/util/localizer.properties index e15544ac2..681418fa4 100644 --- a/openjpa-kernel/src/main/resources/org/apache/openjpa/util/localizer.properties +++ b/openjpa-kernel/src/main/resources/org/apache/openjpa/util/localizer.properties @@ -75,3 +75,5 @@ objectid-abstract: Cannot create new application identity instance for \ abstract class "{0}". query-failed: A query statement timeout has occurred. query-timeout: A query statement timeout (set to {0} milliseconds) has occurred. +invalid-timeout: An invalid timeout of {0} milliseconds was ignored. \ + Expected a value that is greater than or equal to zero. diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/TestQueryTimeout.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/TestQueryTimeout.java index 956f0fd9f..4687f6e7c 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/TestQueryTimeout.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/TestQueryTimeout.java @@ -16,18 +16,24 @@ 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.DBDictionary; import org.apache.openjpa.jdbc.sql.DerbyDictionary; import org.apache.openjpa.kernel.Broker; import org.apache.openjpa.lib.log.Log; 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.PersistenceException; import org.apache.openjpa.persistence.QueryTimeoutException; import org.apache.openjpa.persistence.query.common.apps.QTimeout; @@ -36,14 +42,38 @@ 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() + * Other behaviors to test for: + * 4) Setting timeout to < 0 should be treated as no timeout supplied + * 5) Updates after EM.find()/findAll() are not affected by query timeout + * Exception generation to test for: + * If the DB query timeout does not cause a transaction rollback, then a + * QueryTimeoutException should be thrown. + * Applicable to: unknown + * Else if the DB query timeout causes a transaction rollback, then a + * PersistenceException should be thrown instead of a QTE. + * Applicable to: Derby * * @version $Rev$ $Date$ */ public class TestQueryTimeout extends SQLListenerTestCase { private boolean skipTests = false; - // does the DB platform allow retry instead of forcing transaction rollback - private boolean supportsQueryTimeoutException = false; @Override public void setUp() { @@ -57,20 +87,24 @@ public class TestQueryTimeout extends SQLListenerTestCase { // exception type to catch DBDictionary dict = ((JDBCConfiguration) emf.getConfiguration()) .getDBDictionaryInstance(); - if ((dict.supportsQueryTimeout) && (dict instanceof DerbyDictionary)) { - // set whether we expect to see QueryTimeoutException or - // PersistenceException - supportsQueryTimeoutException = false; + if (dict.supportsQueryTimeout) { + if (!(dict instanceof DerbyDictionary)) { + // FIXME drwoods - OPENJPA-964 - haven't determined what the + // other DBs support + // setQueryTimeout is not working with DB2 v9.5.3a on Windows + getLog().info("FIXME - TestQueryTimeout tests are being " + + "skipped, as tests are not being run against a Derby DB."); + skipTests = true; + } } else { - // FIXME drwoods - OPENJPA-964 - haven't determined what the other - // DBs support - // setQueryTimeout is not working with DB2 v9.5.3a on Windows... - getLog().info("TestQueryTimeout tests are being skipped, due to " + - "DB not supporting Query Timeouts."); + getLog().info("TestQueryTimeout tests are being skipped, " + + "due to DB not supporting Query Timeouts."); skipTests = true; - return; } + if (skipTests) + return; + // create some initial entities try { em = emf.createEntityManager(); assertNotNull(em); @@ -89,54 +123,51 @@ public class TestQueryTimeout extends SQLListenerTestCase { } } - // execute some native SQL with no timeouts + // create delay function only on Derby, other DBs require manual setup if (dict instanceof DerbyDictionary) { - getLog().trace("setUp() - creating DELAY function only for Derby." + - " Other DBs require manual setup."); - // remove existing function if it exists and ignore any errors - 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'"); + getLog().trace("setUp() - creating DELAY function only for " + + "Derby. Other DBs require manual setup."); + // remove existing function if it exists and recreate + try { + 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()); + } + } + + // create triggers on all DBs + try { + getLog().trace("setUp() - creating BEFORE UPDATE/INSERT " + + "TRIGGERs for all DBs"); + exec(false, 0, "CREATE TRIGGER t1 NO CASCADE BEFORE UPDATE ON " + + "qtimeout FOR EACH ROW MODE DB2SQL values DELAY(2,-1)"); + 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, as it slows down the DROP_TABLES + // cleanup between tests + // exec(0, "CREATE TRIGGER t3 NO CASCADE BEFORE DELETE ON " + + // "qtimeout FOR EACH ROW MODE DB2SQL values DELAY(2,-3)"); + } 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. DBs other than " + + "Derby require manual setup steps for these tests."); + skipTests = true; + return; + } } - getLog().trace("setUp() - creating BEFORE UPDATE/INSERT TRIGGERs for " + - "all DBs"); - exec(false, 0, "CREATE TRIGGER t1 NO CASCADE BEFORE UPDATE ON " + - "qtimeout FOR EACH ROW MODE DB2SQL values DELAY(2,-1)"); - 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, as it slows down the DROP_TABLES - // cleanup between tests - // exec(0, "CREATE TRIGGER t3 NO CASCADE BEFORE DELETE ON qtimeout " + - // "FOR EACH ROW MODE DB2SQL values DELAY(2,-3)"); } - /* - * Query timeout scenarios to test for: - * 1) By default, there is no timeout - * 2) Setting timeout to 0 is same as no timeout (JDBC defined) - * 2.1) using the QueryHint annotation - * 2.2) calling setHint() - * 3) Setting timeout to msecs < DELAY value causes new - * javax.persistence.QueryTimeoutException when set by: - * 3.1) using the QueryHint annotation - * 3.2) calling setHint() - * Operations to validate through cross coverage of items #1-#3: - * a) getResultList() - * b) getSingleResult() - * c) executeUpdate() - * Other behaviors to test for: - * 4) Setting timeout to < 0 should be treated as no timeout supplied - * Exception generation to test for: - * If the DB query timeout does not cause a transaction rollback, then a - * QueryTimeoutException should be thrown. - * Applicable to: unknown - * Else if the DB query timeout causes a transaction rollback, then a - * PersistenceException should be thrown instead of a QTE. - * Applicable to: Derby - */ - /** * Scenario being tested: 1a) By default, there is no timeout for queries. * Expected Results: The DELAY function is being called and the query takes @@ -186,29 +217,29 @@ public class TestQueryTimeout extends SQLListenerTestCase { if (skipTests) { return; } - getLog().trace("testQueryTimeout1c() - No Update timeout"); + 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 hints = q.getHints(); + assertFalse(hints.containsKey("javax.persistence.query.timeout")); try { long startTime = System.currentTimeMillis(); - QTimeout qt = em.find(QTimeout.class, new Integer(1)); em.getTransaction().begin(); - qt.setStringField("updated"); - em.flush(); + int count = q.executeUpdate(); em.getTransaction().commit(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; - getLog().trace("testQueryTimeout1c() - EM find/update runTime" + - " msecs=" + runTime); + 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); - em.clear(); - qt = em.find(QTimeout.class, new Integer(1)); - assertEquals("Verify the entity was updated.", - qt.getStringField(), "updated"); } catch (Exception e) { fail("Unexpected testQueryTimeout1c() exception = " + e); } @@ -220,16 +251,76 @@ public class TestQueryTimeout extends SQLListenerTestCase { } /** - * Scenario being tested: 2.1.a) Explicit annotated QueryHint of timeout=0 + * 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) { + return; + } + getLog().trace("testQueryTimeout21b() - Map(timeout=0)"); + OpenJPAEntityManagerFactory emf = null; + OpenJPAEntityManager em = null; + Integer setTime = new Integer(0); + // create the Map to test overrides + Mapprops = 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 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 testQueryTimeout2a() { + public void testQueryTimeout22a() { if (skipTests) { return; } - getLog().trace("testQueryTimeout2a() - QueryHint=0"); + getLog().trace("testQueryTimeout22a() - QueryHint=0"); EntityManager em = null; try { em = emf.createEntityManager(); @@ -240,7 +331,7 @@ public class TestQueryTimeout extends SQLListenerTestCase { assertTrue(hints.containsKey("javax.persistence.query.timeout")); Integer timeout = new Integer( (String) hints.get("javax.persistence.query.timeout")); - getLog().trace("testQueryTimeout2a() - Retrieved hint " + + getLog().trace("testQueryTimeout22a() - Retrieved hint " + "javax.persistence.query.timeout=" + timeout); assertEquals(timeout, new Integer(0)); @@ -250,14 +341,14 @@ public class TestQueryTimeout extends SQLListenerTestCase { List results = q.getResultList(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; - getLog().trace("testQueryTimeout2a() - Hint0msec runTime msecs=" + 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 testQueryTimeout2a() exception = " + e); + fail("Unexpected testQueryTimeout22a() exception = " + e); } } finally { if ((em != null) && em.isOpen()) { @@ -267,17 +358,17 @@ public class TestQueryTimeout extends SQLListenerTestCase { } /** - * Scenario being tested: 2.1.b) Explicit setHint of timeout=0 is treated + * 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 testQueryTimeout2b() { + public void testQueryTimeout23b() { if (skipTests) { return; } Integer setTime = new Integer(0); - getLog().trace("testQueryTimeout2b() - setHint(" + setTime + ")"); + getLog().trace("testQueryTimeout23b() - setHint(" + setTime + ")"); EntityManager em = null; try { em = emf.createEntityManager(); @@ -289,14 +380,14 @@ public class TestQueryTimeout extends SQLListenerTestCase { assertFalse(hints.containsKey("javax.persistence.query.timeout")); // update the timeout value to 0 and verify it was set - getLog().trace("testQueryTimeout2b() - Setting hint " + + 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("testQueryTimeout2b() - Retrieved hint " + + getLog().trace("testQueryTimeout23b() - Retrieved hint " + "javax.persistence.query.timeout=" + timeout); assertEquals(timeout, setTime); @@ -305,14 +396,14 @@ public class TestQueryTimeout extends SQLListenerTestCase { Object result = q.getSingleResult(); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; - getLog().trace("testQueryTimeout2b() - NoHintSingle runTime " + + 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 testQueryTimeout2b() exception = " + e); + fail("Unexpected testQueryTimeout23b() exception = " + e); } } finally { if ((em != null) && em.isOpen()) { @@ -322,32 +413,114 @@ public class TestQueryTimeout extends SQLListenerTestCase { } /** - * Scenario being tested: 3.1.a) Explicit annotated QueryHint of - * timeout=1000 msecs will cause the query to timeout. - * Expected Results: QueryTimeoutException or PersistenceException + * 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 testQueryTimeout3a() { + public void testQueryTimeout31c() { if (skipTests) { return; } + getLog().trace("testQueryTimeout31c() - PU(timeout=1000), executeUpdate timeout"); + OpenJPAEntityManagerFactory emf = null; + OpenJPAEntityManager em = null; Integer setTime = new Integer(1000); - getLog().trace("testQueryTimeout3a() - QueryHint(" + setTime + ")"); - EntityManager em = null; + 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); - Query q = em.createNamedQuery("Hint1000msec"); + OpenJPAQuery q = em.createNativeQuery("UPDATE QTimeout SET stringField = ? WHERE mod(DELAY(2,id),2)=0"); + q.setParameter(1, new String("updated")); + // verify no default javax.persistence.query.timeout is supplied + Map 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 + 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("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 + checkException("testQueryTimeout31c()", e); + } + } 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 (skipTests) { + return; + } + getLog().trace("testQueryTimeout32a() - PU(1000), Map(0), QueryHint(1000)"); + OpenJPAEntityManagerFactory emf = null; + OpenJPAEntityManager em = null; + Integer setTime = new Integer(0); + // create the Map to test overrides + Map 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 hints = q.getHints(); assertTrue(hints.containsKey("javax.persistence.query.timeout")); Integer timeout = new Integer((String) hints.get( "javax.persistence.query.timeout")); getLog().trace( - "testQueryTimeout3a() - Found javax.persistence.query.timeout=" + "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(); @@ -356,16 +529,16 @@ public class TestQueryTimeout extends SQLListenerTestCase { long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; getLog().trace( - "testQueryTimeout3a() - Hint1000msec runTime msecs=" + "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 testQueryTimeout3a(" + setTime + + "in testQueryTimeout32a(" + setTime + " msecs), runTime msecs=" + runTime); } catch (Exception e) { // expected - checkException("testQueryTimeout3a()", e); + checkException("testQueryTimeout32a()", e); } } finally { if ((em != null) && em.isOpen()) { @@ -375,16 +548,16 @@ public class TestQueryTimeout extends SQLListenerTestCase { } /** - * Scenario being tested: 3.2.b) Explicit setHint of timeout to 1000 msecs + * 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 testQueryTimeout3b() { + public void testQueryTimeout33b() { if (skipTests) { return; } Integer setTime = new Integer(1000); - getLog().trace("testQueryTimeout3b() - setHint(" + setTime + ")"); + getLog().trace("testQueryTimeout33b() - setHint(" + setTime + ")"); EntityManager em = null; try { em = emf.createEntityManager(); @@ -396,7 +569,7 @@ public class TestQueryTimeout extends SQLListenerTestCase { assertFalse(hints.containsKey("javax.persistence.query.timeout")); // update the timeout value and verify it was set - getLog().trace("testQueryTimeout3b() - Setting hint " + + getLog().trace("testQueryTimeout33b() - Setting hint " + "javax.persistence.query.timeout=" + setTime); q.setHint("javax.persistence.query.timeout", setTime); hints = q.getHints(); @@ -412,15 +585,15 @@ public class TestQueryTimeout extends SQLListenerTestCase { long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; getLog().trace( - "testQueryTimeout3b() - NoHintSingle runTime msecs=" + "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 testQueryTimeout3b(" + setTime + + "in testQueryTimeout33b(" + setTime + " mscs), runTime msecs=" + runTime); } catch (Exception e) { // expected - checkException("testQueryTimeout3b()", e); + checkException("testQueryTimeout33b()", e); } } finally { if ((em != null) && em.isOpen()) { @@ -429,6 +602,85 @@ public class TestQueryTimeout extends SQLListenerTestCase { } } + /** + * 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 (skipTests) { + return; + } + getLog().trace("testQueryTimeout33c() - PU(timeout=0), setHint(1000), executeUpdate timeout"); + OpenJPAEntityManagerFactory emf = null; + OpenJPAEntityManager em = null; + Integer setTime = new Integer(0); + + 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("UPDATE QTimeout SET stringField = ? WHERE mod(DELAY(2,id),2)=0"); + q.setParameter(1, new String("updated")); + // verify no default javax.persistence.query.timeout is supplied + Map 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()); + + 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("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 + checkException("testQueryTimeout33c()", e); + } + } finally { + if ((em != null) && em.isOpen()) { + if (em.getTransaction().isActive()) + em.getTransaction().rollback(); + em.close(); + } + } + } + /** * Scenario being tested: 4) Timeouts < 0 are ignored and treated as the * default no timeout scenario. @@ -487,6 +739,65 @@ public class TestQueryTimeout extends SQLListenerTestCase { } } + /** + * Scenario being tested: 5) 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 testQueryTimeout5() { + if (skipTests) { + return; + } + getLog().trace("testQueryTimeout5() - No EM.find() update timeout"); + OpenJPAEntityManagerFactory emf = null; + OpenJPAEntityManager em = null; + Integer setTime = new Integer(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, new Integer(1)); + em.getTransaction().begin(); + qt.setStringField("updated"); + em.flush(); + em.getTransaction().commit(); + long endTime = System.currentTimeMillis(); + long runTime = endTime - startTime; + getLog().trace("testQueryTimeout1d() - 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, new Integer(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 testQueryTimeout5() exception = " + e); + } + } finally { + if ((em != null) && em.isOpen()) { + em.close(); + } + } + } + /** * Internal convenience method to execute SQL statements * @@ -495,7 +806,8 @@ public class TestQueryTimeout extends SQLListenerTestCase { * @param timeoutSecs * @param fail */ - private void exec(boolean ignoreExceptions, int timeoutSecs, String sql) { + private void exec(boolean ignoreExceptions, int timeoutSecs, String sql) + throws SQLException { EntityManager em = null; Statement s = null; try { @@ -511,7 +823,8 @@ public class TestQueryTimeout extends SQLListenerTestCase { s.execute(sql); } catch (SQLException sqe) { if (!ignoreExceptions) { - fail(sqe.toString()); + // fail(sqe.toString()); + throw sqe; } } finally { if (s != null) { @@ -544,13 +857,10 @@ public class TestQueryTimeout extends SQLListenerTestCase { * @param e */ private void checkException(String test, Exception e) { - if (supportsQueryTimeoutException) { - assertTrue("Expected QueryTimeoutException instead of " + e, - matchesExpectedException(QueryTimeoutException.class, e)); - } else { - assertTrue("Expected PersistenceException instead of " + e, - matchesExpectedException(PersistenceException.class, e)); - } + // no easy way to determine exact Exception type for all DBs + assertTrue(test + " - UNEXPECTED Exception = " + e, + matchesExpectedException(QueryTimeoutException.class, e) || + matchesExpectedException(PersistenceException.class, e)); getLog().trace(test + " - Caught expected Exception = " + e); } @@ -569,6 +879,11 @@ public class TestQueryTimeout extends SQLListenerTestCase { if (tested != null) { Class testExClass = tested.getClass(); exMatched = expected.isAssignableFrom(testExClass); + if (exMatched) { + // make sure it is our expected exception text from + // localizer.properties + exMatched = (tested.getMessage().indexOf("query statement timeout") != -1); + } } return exMatched; } diff --git a/openjpa-persistence-jdbc/src/test/resources/META-INF/persistence3.xml b/openjpa-persistence-jdbc/src/test/resources/META-INF/persistence3.xml index 713900455..81a569182 100644 --- a/openjpa-persistence-jdbc/src/test/resources/META-INF/persistence3.xml +++ b/openjpa-persistence-jdbc/src/test/resources/META-INF/persistence3.xml @@ -21,11 +21,11 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"> - + org.apache.openjpa.persistence.query.common.apps.QTimeout - + org.apache.openjpa.persistence.query.common.apps.QTimeout @@ -34,7 +34,13 @@ - + + org.apache.openjpa.persistence.query.common.apps.QTimeout + + + + + org.apache.openjpa.persistence.query.common.apps.QTimeout