OPENJPA-1935 Updated Informix dictionary to examine nested SQL exceptions when determining whether a generic exception is a lock exception. Added Informix JDBC profile to jdbc and locking poms.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@1068472 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jeremy Bauer 2011-02-08 16:42:22 +00:00
parent 85a408aca7
commit d1ec54c691
5 changed files with 215 additions and 6 deletions

View File

@ -27,6 +27,8 @@ import java.sql.Statement;
import java.sql.Types;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.identifier.DBIdentifier.DBIdentifierType;
@ -393,12 +395,61 @@ public class InformixDictionary
// SQL State of IX000 is a general purpose Informix error code
// category, so only return Boolean.TRUE if we match SQL Codes
// recoverable = Boolean.FALSE;
if ((subtype == StoreException.LOCK && ex.getErrorCode() == -154)
if ((subtype == StoreException.LOCK && checkNestedErrorCodes(ex, "IX000", -154))
||(subtype == StoreException.QUERY && ex.getErrorCode() == -213)) {
return false;
}
return super.isFatalException(subtype, ex);
}
/**
* Specialized matchErrorState method for Informix. Informix exceptions are
* typically nested multiple levels deep. Correct determination of the exception type requires
* inspection of nested exceptions to determine the root cause. A list of Informix (IDS v10) error codes
* can be found here:
*
* http://publib.boulder.ibm.com/infocenter/idshelp/v10/index.jsp?topic=/com.ibm.em.doc/errors_ids100.html
*
* @param errorStates classification of SQL error states by their specific nature. The keys of the
* map represent one of the constants defined in {@link StoreException}. The value corresponding to
* a key represent the set of SQL Error States representing specific category of database error.
* This supplied map is sourced from <code>sql-error-state-codes.xml</xml> and filtered the
* error states for the current database.
*
* @param ex original SQL Exception as raised by the database driver.
*
* @return A constant indicating the category of error as defined in {@link StoreException}.
*/
protected int matchErrorState(Map<Integer,Set<String>> errorStates, SQLException ex) {
// Informix SQLState IX000 is a general SQLState that applies to many possible conditions
// If the underlying cause is also an IX000 with error code:
// -107 ISAM error: record is locked. || -154 ISAM error: Lock Timeout Expired.
// the exception type is LOCK.
if (checkNestedErrorCodes(ex, "IX000", -107, -154)) {
return StoreException.LOCK;
}
return super.matchErrorState(errorStates, ex);
}
private boolean checkNestedErrorCodes(SQLException ex, String sqlState, int...errorCodes) {
SQLException cause = ex;
int level = 0;
// Query at most 5 exceptions deep to prevent infinite iteration exception loops
// Typically, the root exception is at level 3.
while (cause != null && level < 5) {
String errorState = cause.getSQLState();
if (sqlState == null || sqlState.equals(errorState)) {
for (int ec : errorCodes) {
if (cause.getErrorCode() == ec) {
return true;
}
}
}
cause = cause.getNextException();
level++;
}
return false;
}
}

View File

@ -627,6 +627,74 @@
</repositories>
</profile>
<!-- Profile for testing Informix with the Informix JDBC Driver -->
<profile>
<!--
Example Informix profile. You can use this profile if you:
1) have the Informix JDBC artifacts installed in a local repo and
supply the URL:
-Dids.maven.repo=http://my.local.repo
2) have a copy of the Informix driver and run the following
commands :
mvn install:install-file -Dfile=${path to ifxjdbc.jar} \
-DgroupId=com.informix \
-DartifactId=informix-driver \
-Dversion=3.70 \
-Dpackaging=jar
You must also set the following properties:
-Dopenjpa.ids.url=jdbc:informix-sqli://<HOST>:<PORT>:informixserver=<INFORMIXSERVER>;database=<DBNAME>
-Dopenjpa.ids.username=<ids_uid>
-Dopenjpa.ids.password=<ids_pwd>
Optionally, you can override the default Informix groupId,
artifactIds and version by also supplying the following
properties:
-Dids.groupid=com.informix
-Dids.driver.artifactid=informix-driver
-Dids.version=3.70
-->
<id>test-ids-informix</id>
<activation>
<property>
<name>test-ids-informix</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>${ids.groupid}</groupId>
<artifactId>${ids.driver.artifactid}</artifactId>
<version>${ids.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<ids.maven.repo>http://not.a.real.repository</ids.maven.repo>
<ids.groupid>com.informix</ids.groupid>
<ids.driver.artifactid>informix-driver</ids.driver.artifactid>
<ids.version>3.70</ids.version>
<connection.driver.name>com.informix.jdbc.IfxDriver</connection.driver.name>
<connection.url>${openjpa.ids.url}</connection.url>
<connection.username>${openjpa.ids.username}</connection.username>
<connection.password>${openjpa.ids.password}</connection.password>
</properties>
<repositories>
<repository>
<id>ids.repository</id>
<name>Informix Repository</name>
<url>${ids.maven.repo}</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<enabled>true</enabled>
<checksumPolicy>ignore</checksumPolicy>
</releases>
</repository>
</repositories>
</profile>
<!-- Profile for testing with Oracle DB -->
<profile>
<!--

View File

@ -222,6 +222,16 @@ public abstract class SQLListenerTestCase
return buf.toString();
}
/**
* Returns the last SQL executed or the empty string if the list is
* empty.
*/
public String getLastSQL(List<String> list) {
if (list != null && list.size() > 0)
return list.get(list.size() -1);
return "";
}
public enum SQLAssertType {
SQL, NotSQL, ContainsSQL, AllSQLInOrder, AllExactSQLInOrder,
AllSQLAnyOrder, NoneSQLAnyOrder, AnySQLAnyOrder

View File

@ -594,6 +594,74 @@
</repositories>
</profile>
<!-- Profile for testing Informix with the Informix JDBC Driver -->
<profile>
<!--
Example Informix profile. You can use this profile if you:
1) have the Informix JDBC artifacts installed in a local repo and
supply the URL:
-Dids.maven.repo=http://my.local.repo
2) have a copy of the Informix driver and run the following
commands :
mvn install:install-file -Dfile=${path to ifxjdbc.jar} \
-DgroupId=com.informix \
-DartifactId=informix-driver \
-Dversion=3.70 \
-Dpackaging=jar
You must also set the following properties:
-Dopenjpa.ids.url=jdbc:informix-sqli://<HOST>:<PORT>:informixserver=<INFORMIXSERVER>;database=<DBNAME>
-Dopenjpa.ids.username=<ids_uid>
-Dopenjpa.ids.password=<ids_pwd>
Optionally, you can override the default Informix groupId,
artifactIds and version by also supplying the following
properties:
-Dids.groupid=com.informix
-Dids.driver.artifactid=informix-driver
-Dids.version=3.70
-->
<id>test-ids-informix</id>
<activation>
<property>
<name>test-ids-informix</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>${ids.groupid}</groupId>
<artifactId>${ids.driver.artifactid}</artifactId>
<version>${ids.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<ids.maven.repo>http://not.a.real.repository</ids.maven.repo>
<ids.groupid>com.informix</ids.groupid>
<ids.driver.artifactid>informix-driver</ids.driver.artifactid>
<ids.version>3.70</ids.version>
<connection.driver.name>com.informix.jdbc.IfxDriver</connection.driver.name>
<connection.url>${openjpa.ids.url}</connection.url>
<connection.username>${openjpa.ids.username}</connection.username>
<connection.password>${openjpa.ids.password}</connection.password>
</properties>
<repositories>
<repository>
<id>ids.repository</id>
<name>Informix Repository</name>
<url>${ids.maven.repo}</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<enabled>true</enabled>
<checksumPolicy>ignore</checksumPolicy>
</releases>
</repository>
</repositories>
</profile>
<!-- Profile for testing with Oracle DB -->
<profile>
<!--

View File

@ -40,6 +40,7 @@ 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.lib.log.Log;
import org.apache.openjpa.persistence.LockTimeoutException;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
@ -396,14 +397,14 @@ public class TestPessimisticLocks extends SQLListenerTestCase {
String firstName1 = (String) q1.getSingleResult();
//Expected sql for Derby is:
//SELECT t0.firstName FROM Employee t0 WHERE (t0.id = CAST(? AS BIGINT)) FOR UPDATE WITH RR
String SQL1 = toString(sql);
String SQL1 = getLastSQL(sql);
// run the second time
resetSQL();
Query q2 = em.createQuery(jpql);
q2.setLockMode(LockModeType.PESSIMISTIC_WRITE);
String firstName2 = (String) q2.getSingleResult();
String SQL2 = toString(sql);
String SQL2 = getLastSQL(sql);
assertEquals(SQL1, SQL2);
em.getTransaction().commit();
}
@ -422,10 +423,18 @@ public class TestPessimisticLocks extends SQLListenerTestCase {
// Only run this test on DB2 and Derby for now. It could cause
// the test to hang on other platforms.
if (!(dict instanceof DerbyDictionary ||
dict instanceof DB2Dictionary)) {
dict instanceof DB2Dictionary ||
dict instanceof InformixDictionary)) {
return;
}
// Informix currently requires the lock timeout to be set directly on the dictionary
if (dict instanceof InformixDictionary) {
InformixDictionary ifxDict = (InformixDictionary)((JDBCConfiguration)emf.getConfiguration()).getDBDictionaryInstance();
ifxDict.lockModeEnabled = true;
ifxDict.lockWaitSeconds = 5;
}
EntityManager em = emf.createEntityManager();
resetSQL();
@ -459,8 +468,11 @@ public class TestPessimisticLocks extends SQLListenerTestCase {
Map<String,Object> props = new HashMap<String,Object>();
// This property does not have any effect on Derby for the locking
// condition produced by this test. Instead, Derby uses the
// lock timeout value specified in the config (pom.xml)
// lock timeout value specified in the config (pom.xml). On Informix,
// the dictionary level timeout (set above) will be used.
if (!(dict instanceof InformixDictionary)) {
props.put("javax.persistence.lock.timeout", 5000);
}
em.getTransaction().begin();
getLog().trace("Main: refresh with force increment");
em.refresh(ve, LockModeType.PESSIMISTIC_FORCE_INCREMENT, props);