Index: openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/AbstractDB2Dictionary.java =================================================================== --- openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/AbstractDB2Dictionary.java (revision 523434) +++ openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/AbstractDB2Dictionary.java (working copy) @@ -52,7 +52,7 @@ supportsLockingWithOrderClause = false; supportsLockingWithOuterJoin = false; supportsLockingWithInnerJoin = false; - supportsLockingWithSelectRange = false; + supportsLockingWithSelectRange = true; requiresAutoCommitForMetaData = true; requiresAliasForSubselect = true; Index: openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DB2Dictionary.java =================================================================== --- openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DB2Dictionary.java (revision 523434) +++ openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DB2Dictionary.java (working copy) @@ -15,6 +15,8 @@ */ package org.apache.openjpa.jdbc.sql; + +import java.lang.reflect.Method; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; @@ -19,9 +21,11 @@ import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.util.Arrays; +import java.util.StringTokenizer; import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration; import org.apache.openjpa.jdbc.schema.Sequence; +import org.apache.openjpa.lib.log.Log; /** * Dictionary for IBM DB2 database. @@ -31,7 +35,18 @@ public String optimizeClause = "optimize for"; public String rowClause = "row"; - + // Variables for supporting locking syntax + public boolean db2ISeriesV5R3AndEarlier = false; + public boolean db2UDBV81OrEarlier = false; + public boolean db2ZOSV8x = false; + public boolean db2UDBV82AndLater = false; + public boolean db2ISeriesV5R4AndLater = false; + public String forUpdateOfClause="FOR UPDATE OF"; + public String withRSClause="WITH RS"; + public String withRRClause="WITH RR"; + public String useKeepUpdateLockClause= "USE AND KEEP UPDATE LOCKS"; + public String useKeepExclusiveLockClause="USE AND KEEP EXCLUSIVE LOCKS"; + public String forReadOnlyClause = "FOR READ ONLY"; public DB2Dictionary() { platform = "DB2"; validationSQL = "SELECT DISTINCT(CURRENT TIMESTAMP) FROM " @@ -170,6 +185,18 @@ if (isJDBC3(metaData)) { int maj = metaData.getDatabaseMajorVersion(); int min = metaData.getDatabaseMinorVersion(); + + // Determine the type of DB2 database + if (isDB2ISeriesV5R3AndEarlier(metaData)) + db2ISeriesV5R3AndEarlier= true; + else if (isDB2UDBV81OrEarlier(metaData,maj,min)) + db2UDBV81OrEarlier = true; + else if (isDB2ZOSV8x(metaData,maj)) + db2ZOSV8x = true; + else if (isDB2UDBV82AndLater(metaData,maj,min)) + db2UDBV82AndLater=true; + else if (isDB2ISeriesV5R4AndLater(metaData)) + db2ISeriesV5R4AndLater=true; if (maj >= 9 || (maj == 8 && min >= 2)) { supportsLockingWithMultipleTables = true; @@ -198,13 +225,219 @@ } } - public SQLBuffer toSelect(Select sel, boolean forUpdate, - JDBCFetchConfiguration fetch) { - SQLBuffer buf = super.toSelect(sel, forUpdate, fetch); - if (sel.getExpectedResultCount() > 0) - buf.append(" ").append(optimizeClause).append(" "). - append(String.valueOf(sel.getExpectedResultCount())). - append(" ").append(rowClause); + /** Get the update clause for the query based on the + * updateClause and isolationLevel hints + */ + public String getForUpdateClause(JDBCFetchConfiguration fetch, boolean forUpdate) { + String isolationLevel = null; + Boolean updateClause = null; + DatabaseMetaData metaData = null; + StringBuffer forUpdateString = new StringBuffer(); + try { + // Determine the update clause/isolationLevel the hint + // overrides the persistence.xml value + if (fetch != null && fetch.getHint("openjpa.hint.updateClause") + !=null ) + updateClause = (Boolean)fetch. + getHint("openjpa.hint.updateClause"); + else + updateClause = forUpdate; + if (fetch != null &&fetch.getHint("openjpa.hint.isolationLevel") + !=null ) + isolationLevel = (String)fetch. + getHint("openjpa.hint.isolationLevel"); + else + isolationLevel = conf.getTransactionIsolation(); + if (updateClause == false) + //This sql is not for update so add FOR Read Only clause + forUpdateString.append(" ").append(forReadOnlyClause) + .append(" "); + else if (updateClause == true){ + // DB2 UDB for iseries V5R3 and DB2 UDB V8.1 earlier + if (db2ISeriesV5R3AndEarlier || db2UDBV81OrEarlier){ + if (isolationLevel.equals("read-uncommitted")) + forUpdateString.append(" ").append(withRSClause) + .append(" ").append(forUpdateOfClause).append(" "); + else + forUpdateString.append(" ").append(forUpdateOfClause) + .append(" "); + } + else if (db2ZOSV8x || db2UDBV82AndLater){ + if (isolationLevel.equals("serializable")) + forUpdateString.append(" ").append(withRRClause) + .append(" ").append(useKeepUpdateLockClause) + .append(" "); + else + forUpdateString.append(" ").append(withRSClause) + .append(" ").append(useKeepUpdateLockClause) + .append(" "); + } + else if (db2ISeriesV5R4AndLater){ + if (isolationLevel.equals("serializable")) + forUpdateString.append(" ").append(withRRClause) + .append(" ").append(useKeepExclusiveLockClause) + .append(" "); + else + forUpdateString.append(" ").append(withRSClause) + .append(" ").append(useKeepExclusiveLockClause) + .append(" "); + } + } + } + catch (Exception e) { + if (log.isTraceEnabled()) + log.error(e.toString(),e); + } + return forUpdateString.toString(); + } + + + /** Override the DBDictionary toSelect to call getOptimizeClause and append + * to the select string + */ + public SQLBuffer toSelect(SQLBuffer selects, JDBCFetchConfiguration fetch, + SQLBuffer from, SQLBuffer where, SQLBuffer group, + SQLBuffer having, SQLBuffer order, + boolean distinct, boolean forUpdate, long start, long end, + int expectedResultCount) { + String forUpdateString = getForUpdateClause(fetch,forUpdate); + SQLBuffer selString = toOperation(getSelectOperation(fetch), + selects, from, where, + group, having, order, distinct, + forUpdate, start, end,forUpdateString); + return selString; + } + + public boolean isDB2UDBV82AndLater(DatabaseMetaData metadata, int maj, + int min) throws SQLException { + boolean match = false; + if (metadata.getDatabaseProductVersion().indexOf("SQL") != -1 + && ((maj ==8 && min >=2) ||(maj >=8))) + match = true; + return match; + } + + public boolean isDB2ZOSV8x(DatabaseMetaData metadata,int maj) + throws SQLException { + boolean match = false; + if (metadata.getDatabaseProductVersion().indexOf("DSN") != -1 + && maj ==8 ) + match = true; + return match; + } + + public boolean isDB2ISeriesV5R3AndEarlier(DatabaseMetaData metadata) + throws SQLException { + boolean match = false; + if (metadata.getDatabaseProductVersion().indexOf("AS") != -1 + && generateVersionNumber(metadata.getDatabaseProductVersion()) + <= 530 ) + match = true; + return match; + } + + public boolean isDB2ISeriesV5R4AndLater(DatabaseMetaData metadata) + throws SQLException { + boolean match = false; + if (metadata.getDatabaseProductVersion().indexOf("AS") != -1 + && generateVersionNumber(metadata.getDatabaseProductVersion()) + >= 540 ) + match = true; + return match; + } + + public boolean isDB2UDBV81OrEarlier(DatabaseMetaData metadata,int maj, + int min) throws SQLException { + boolean match = false; + if (metadata.getDatabaseProductVersion().indexOf("SQL") != -1 && + ((maj ==8 && min <=1)|| maj <8 )) + match = true; + return match; + } + + /** Get the version number for the ISeries + */ + protected int generateVersionNumber(String versionString) { + String s = versionString.substring(versionString.indexOf('V')); + s = s.toUpperCase(); + int i = -1; + StringTokenizer stringtokenizer = new StringTokenizer(s, "VRM", false); + if (stringtokenizer.countTokens() == 3) + { + String s1 = stringtokenizer.nextToken(); + s1 = s1 + stringtokenizer.nextToken(); + s1 = s1 + stringtokenizer.nextToken(); + i = Integer.parseInt(s1); + } + return i; + } + + + /** + * Override the toOperationMethod of DBDictionary to pass the + * forUpdateString. + */ + protected SQLBuffer toOperation(String op, SQLBuffer selects, + SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having, + SQLBuffer order, boolean distinct, boolean forUpdate, long start, + long end,String forUpdateString) { + SQLBuffer buf = new SQLBuffer(this); + buf.append(op); + boolean range = start != 0 || end != Long.MAX_VALUE; + if (range && rangePosition == RANGE_PRE_DISTINCT) + appendSelectRange(buf, start, end); + if (distinct) + buf.append(" DISTINCT"); + if (range && rangePosition == RANGE_POST_DISTINCT) + appendSelectRange(buf, start, end); + buf.append(" ").append(selects).append(" FROM ").append(from); + + if (where != null && !where.isEmpty()) + buf.append(" WHERE ").append(where); + if (group != null && !group.isEmpty()) + buf.append(" GROUP BY ").append(group); + if (having != null && !having.isEmpty()) { + assertSupport(supportsHaving, "SupportsHaving"); + buf.append(" HAVING ").append(having); + } + if (order != null && !order.isEmpty()) + buf.append(" ORDER BY ").append(order); + if (range && rangePosition == RANGE_POST_SELECT) + appendSelectRange(buf, start, end); + + if (!simulateLocking ) { + assertSupport(supportsSelectForUpdate, "SupportsSelectForUpdate"); + buf.append(" ").append(forUpdateString); + } + if (range && rangePosition == RANGE_POST_LOCK) + appendSelectRange(buf, start, end); return buf; } + + public SQLBuffer toSelect(Select sel, boolean forUpdate, + JDBCFetchConfiguration fetch) { + sel.addJoinClassConditions(); + boolean update = forUpdate && sel.getFromSelect() == null; + SQLBuffer select = getSelects(sel, false, update); + SQLBuffer ordering = null; + if (!sel.isAggregate() || sel.getGrouping() != null) + ordering = sel.getOrdering(); + SQLBuffer from; + if (sel.getFromSelect() != null) + from = getFromSelect(sel, forUpdate); + else + from = getFrom(sel, update); + SQLBuffer where = getWhere(sel, update); + String forUpdateString = getForUpdateClause(fetch,forUpdate); + SQLBuffer buf = toOperation(getSelectOperation(fetch), select, + from, where,sel.getGrouping(), sel.getHaving(), ordering, + sel.isDistinct(), forUpdate, sel.getStartIndex(), + sel.getEndIndex(),forUpdateString); + if (sel.getExpectedResultCount() > 0) + buf.append(" ").append(optimizeClause).append(" "). + append(String.valueOf(sel.getExpectedResultCount())). + append(" ").append(rowClause); + return buf; + } + }