Author: ppoddar
Date: Mon Feb 2 15:57:27 2009
New Revision: 740016
URL: http://svn.apache.org/viewvc?rev=740016&view=rev
Log:
OPENJPA-703: Support Collection-valued parameters. Handle re-parameterization
when collection-valued parameter has different size across invocations.
Modified:
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PreparedQueryImpl.java
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/InExpression.java
openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/kernel/localizer.properties
openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java
openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java
Modified:
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PreparedQueryImpl.java
URL:
http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PreparedQueryImpl.java?rev=740016&r1=740015&r2=740016&view=diff
==============================================================================
---
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PreparedQueryImpl.java
(original)
+++
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PreparedQueryImpl.java
Mon Feb 2 15:57:27 2009
@@ -19,7 +19,9 @@
package org.apache.openjpa.jdbc.kernel;
-import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -35,8 +37,9 @@
import org.apache.openjpa.kernel.QueryImpl;
import org.apache.openjpa.kernel.QueryLanguages;
import org.apache.openjpa.lib.rop.ResultList;
+import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.util.ImplHelper;
-import org.apache.openjpa.util.InternalException;
+import org.apache.openjpa.util.UserException;
/**
* Implements {...@link PreparedQuery} for SQL queries.
@@ -45,6 +48,9 @@
*
*/
public class PreparedQueryImpl implements PreparedQuery {
+ private static Localizer _loc =
+ Localizer.forPackage(PreparedQueryImpl.class);
+
private final String _id;
private String _sql;
@@ -53,11 +59,9 @@
private boolean _subclasses;
private boolean _isProjection;
- // Parameters of the query
- private List _params;
// Position of the user defined parameters in the _params list
private Map<Object, int[]> _userParamPositions;
-
+ private Map<Integer, Object> _template;
/**
* Construct.
@@ -163,31 +167,43 @@
* {...@link #initialize(Object) initialization}.
*
* @return 0-based parameter index mapped to corresponding values.
+ *
*/
public Map<Integer, Object> reparametrize(Map user, Broker broker) {
- Map<Integer, Object> result = new HashMap<Integer, Object>();
- for (int i = 0; i < _params.size(); i++) {
- result.put(i, _params.get(i));
+ if (user == null || user.isEmpty()) {
+ if (!_userParamPositions.isEmpty()) {
+ throw new UserException(_loc.get("uparam-null",
+ _userParamPositions.keySet(), this));
+ } else {
+ return _template;
+ }
}
- if (user == null || user.isEmpty())
- return result;
+ if (!_userParamPositions.keySet().equals(user.keySet())) {
+ throw new UserException(_loc.get("uparam-mismatch",
+ _userParamPositions.keySet(), user.keySet(), this));
+ }
+ Map<Integer, Object> result = new HashMap<Integer, Object>(_template);
+
for (Object key : user.keySet()) {
int[] indices = _userParamPositions.get(key);
- if (indices == null)
- continue;
- Object value = user.get(key);
- if (ImplHelper.isManageable(value)) {
- setPersistenceCapableParameter(result, value, indices, broker);
+ if (indices == null || indices.length == 0)
+ throw new UserException(_loc.get("uparam-no-pos", key, this));
+ Object val = user.get(key);
+ if (ImplHelper.isManageable(val)) {
+ setPersistenceCapableParameter(result, val, indices, broker);
+ } else if (val instanceof Collection) {
+ setCollectionValuedParameter(result, (Collection)val, indices,
+ key);
} else {
for (int j : indices)
- result.put(j, value);
+ result.put(j, val);
}
}
return result;
}
/**
- * Calculate primary key identity value(s) of the given managable instance
+ * Calculate primary key identity value(s) of the given manageable instance
* and fill in the given map.
*
* @param values a map of integer parameter index to parameter value
@@ -209,7 +225,8 @@
Object[] array = (Object[])cols;
int n = array.length;
if (n > indices.length || indices.length%n != 0)
- throw new InternalException();
+ throw new UserException(_loc.get("uparam-pc-key",
+ pc.getClass(), n, Arrays.toString(indices)));
int k = 0;
for (int j : indices) {
result.put(j, array[k%n]);
@@ -222,8 +239,23 @@
}
}
+ private void setCollectionValuedParameter(Map<Integer,Object> result,
+ Collection values, int[] indices, Object param) {
+ int n = values.size();
+ Object[] array = values.toArray();
+ if (n > indices.length || indices.length%n != 0) {
+ throw new UserException(_loc.get("uparam-coll-size", param,
values,
+ Arrays.toString(indices)));
+ }
+ int k = 0;
+ for (int j : indices) {
+ result.put(j, array[k%n]);
+ k++;
+ }
+
+ }
/**
- * Marks the positions of user parameters.
+ * Marks the positions and keys of user parameters.
*
* @param list even elements are numbers representing the position of a
* user parameter in the _param list. Odd elements are the user parameter
@@ -248,8 +280,11 @@
}
void setParameters(List list) {
- _params = new ArrayList();
- _params.addAll(list);
+ Map<Integer, Object> tmp = new HashMap<Integer, Object>();
+ for (int i = 0; list != null && i < list.size(); i++) {
+ tmp.put(i, list.get(i));
+ }
+ _template = Collections.unmodifiableMap(tmp);
}
public String toString() {
Modified:
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/InExpression.java
URL:
http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/InExpression.java?rev=740016&r1=740015&r2=740016&view=diff
==============================================================================
---
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/InExpression.java
(original)
+++
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/InExpression.java
Mon Feb 2 15:57:27 2009
@@ -30,6 +30,7 @@
import org.apache.openjpa.jdbc.sql.SQLBuffer;
import org.apache.openjpa.jdbc.sql.Select;
import org.apache.openjpa.kernel.exps.ExpressionVisitor;
+import org.apache.openjpa.kernel.exps.Parameter;
/**
* Tests whether a value is IN a collection.
@@ -154,7 +155,8 @@
Column col = (cols != null && cols.length == 1) ? cols[0] : null;
for (Iterator itr = coll.iterator(); itr.hasNext();) {
- buf.appendValue(itr.next(), col);
+ buf.appendValue(itr.next(), col, _const instanceof Parameter
+ ? (Parameter)_const : null);
if (itr.hasNext())
buf.append(", ");
}
Modified:
openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/kernel/localizer.properties
URL:
http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/kernel/localizer.properties?rev=740016&r1=740015&r2=740016&view=diff
==============================================================================
---
openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/kernel/localizer.properties
(original)
+++
openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/kernel/localizer.properties
Mon Feb 2 15:57:27 2009
@@ -124,4 +124,13 @@
following {1} cached queries to be removed from the cache: "{2}".
prepared-query-remove-pattern: Removing a Query exclusion pattern "{0}" caused
\
following {1} queries to be re-inserted in the cache: "{2}".
-
\ No newline at end of file
+uparam-mismatch: Supplied user parameters "{1}" do not match expected \
+ parameters "{0}" for the prepared query "{2}".
+uparam-null: No user parameter was given. Expected parameters "{0}" for the \
+ prepared query "{1}".
+uparam-coll-size: Parameter "{0}" has a value "{1}" which is not compatible \
+ with the available positions {2} in the parameter list of the prepared
query
+uparam-no-pos: User parameter "{0}" does not appear in any position in the \
+ prepared query "{1}".
+uparam-pc-key: Class "{0}" uses {1} primary key columns but corresponding \
+ positions {2} in the parameter list of the prepared query is not
compatible.
\ No newline at end of file
Modified:
openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java
URL:
http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java?rev=740016&r1=740015&r2=740016&view=diff
==============================================================================
---
openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java
(original)
+++
openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java
Mon Feb 2 15:57:27 2009
@@ -821,13 +821,15 @@
return eval(firstChild(node));
case JJTNAMEDINPUTPARAMETER:
- return getParameter(node.text, false);
+ return getParameter(node.text, false, false);
case JJTPOSITIONALINPUTPARAMETER:
- return getParameter(node.text, true);
+ return getParameter(node.text, true, false);
case JJTCOLLECTIONPARAMETER:
- return getCollectionValuedParameter(node);
+ JPQLNode child = onlyChild(node);
+ return getParameter(child.text,
+ child.id == JJTPOSITIONALINPUTPARAMETER, true);
case JJTOR: // x OR y
return factory.or(getExpression(left(node)),
@@ -1288,19 +1290,25 @@
}
/**
- * Record the names and order of implicit parameters.
+ * Creates and records the names and order of parameters. The parameters
are
+ * identified by a key with its type preserved. The second argument
+ * determines whether the first argument is used as-is or converted to
+ * an Integer as parameter key.
+ *
+ * @param the text as it appears in the parsed node
+ * @param positional if true the first argument is converted to an integer
+ * @param isCollectionValued true for collection-valued parameters
*/
- private Parameter getParameter(String id, boolean positional) {
+ private Parameter getParameter(String id, boolean positional,
+ boolean isCollectionValued) {
if (parameterTypes == null)
parameterTypes = new LinkedMap(6);
Object paramKey = positional ? Integer.parseInt(id) : id;
if (!parameterTypes.containsKey(paramKey))
parameterTypes.put(paramKey, TYPE_OBJECT);
- Class type = Object.class;
ClassMetaData meta = null;
int index;
-
if (positional) {
try {
// indexes in JPQL are 1-based, as opposed to 0-based in
@@ -1318,52 +1326,12 @@
// otherwise the index is just the current size of the params
index = parameterTypes.indexOf(id);
}
- Parameter param = factory.newParameter(paramKey, type);
+ Parameter param = isCollectionValued
+ ? factory.newCollectionValuedParameter(paramKey, TYPE_OBJECT)
+ : factory.newParameter(paramKey, TYPE_OBJECT);
param.setMetaData(meta);
param.setIndex(index);
-
- return param;
- }
-
- /**
- * Record the names and order of collection valued input parameters.
- */
- private Parameter getCollectionValuedParameter(JPQLNode node) {
- JPQLNode child = onlyChild(node);
- String id = child.text;
- boolean positional = child.id == JJTPOSITIONALINPUTPARAMETER;
-
- if (parameterTypes == null)
- parameterTypes = new LinkedMap(6);
- Object paramKey = positional ? Integer.parseInt(id) : id;
- if (!parameterTypes.containsKey(id))
- parameterTypes.put(paramKey, TYPE_OBJECT);
-
- Class type = Object.class;
- ClassMetaData meta = null;
- int index;
- if (positional) {
- try {
- // indexes in JPQL are 1-based, as opposed to 0-based in
- // the core ExpressionFactory
- index = Integer.parseInt(id) - 1;
- } catch (NumberFormatException e) {
- throw parseException(EX_USER, "bad-positional-parameter",
- new Object[]{ id }, e);
- }
-
- if (index < 0)
- throw parseException(EX_USER, "bad-positional-parameter",
- new Object[]{ id }, null);
- } else {
- // otherwise the index is just the current size of the params
- index = parameterTypes.indexOf(id);
- }
-
- Parameter param = factory.newCollectionValuedParameter(id, type);
- param.setMetaData(meta);
- param.setIndex(index);
-
+
return param;
}
@@ -1502,10 +1470,10 @@
return factory.type(getValue(node));
case JJTNAMEDINPUTPARAMETER:
- return factory.type(getParameter(node.text, false));
+ return factory.type(getParameter(node.text, false, false));
case JJTPOSITIONALINPUTPARAMETER:
- return factory.type(getParameter(node.text, true));
+ return factory.type(getParameter(node.text, true, false));
default:
// TODO: enforce jpa2.0 spec rules.
Modified:
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java
URL:
http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java?rev=740016&r1=740015&r2=740016&view=diff
==============================================================================
---
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java
(original)
+++
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java
Mon Feb 2 15:57:27 2009
@@ -19,6 +19,7 @@
package org.apache.openjpa.persistence.jdbc.sqlcache;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -424,6 +425,25 @@
compare(!IS_NAMED_QUERY, jpql,
COMPANY_NAMES.length*DEPARTMENT_NAMES.length, params);
}
+ public void testCollectionValuedParameters() {
+ String jpql = "select e from Employee e where e.name in :names";
+ Object[] params1 = {"names", Arrays.asList(new
String[]{EMPLOYEE_NAMES[0], EMPLOYEE_NAMES[1]})};
+ Object[] params2 = {"names", Arrays.asList(new
String[]{EMPLOYEE_NAMES[2]})};
+ Object[] params3 = {"names", Arrays.asList(EMPLOYEE_NAMES)};
+
+ boolean checkHits = false;
+
+ int expectedCount = 2*COMPANY_NAMES.length*DEPARTMENT_NAMES.length;
+ run(jpql, params1, USE_CACHE, 2, !IS_NAMED_QUERY, expectedCount,
checkHits);
+ assertCached(jpql);
+
+ expectedCount = 1*COMPANY_NAMES.length*DEPARTMENT_NAMES.length;
+ run(jpql, params2, USE_CACHE, 2, !IS_NAMED_QUERY, expectedCount,
checkHits);
+
+ expectedCount =
EMPLOYEE_NAMES.length*COMPANY_NAMES.length*DEPARTMENT_NAMES.length;
+ run(jpql, params3, USE_CACHE, 2, !IS_NAMED_QUERY, expectedCount,
checkHits);
+ }
+
/**
* Compare the result of execution of the same query with and without
* Prepared Query Cache.
@@ -462,6 +482,10 @@
}
}
+ long run(String jpql, Object[] params, boolean useCache, int N,
+ boolean isNamedQuery, int expectedCount) {
+ return run(jpql, params, useCache, N, isNamedQuery, expectedCount,
true);
+ }
/**
* Create and run a query N times with the given parameters. The time
for
* each query execution is measured in nanosecond precision and
@@ -470,7 +494,7 @@
* returns median time taken for single execution.
*/
long run(String jpql, Object[] params, boolean useCache, int N,
- boolean isNamedQuery, int expectedCount) {
+ boolean isNamedQuery, int expectedCount, boolean
checkHits) {
trace("Executing " + N + " times " + (useCache ? " with " :
"without") + " cache");
List<Long> stats = new ArrayList<Long>();
sql.clear();
@@ -500,7 +524,7 @@
stats.add(end - start);
em.close();
}
- if (useCache) {
+ if (useCache && checkHits) {
String cacheKey = isNamedQuery ? getJPQL(jpql) : jpql;
long total =
getCache().getStatistics().getExecutionCount(cacheKey);
long hits = getCache().getStatistics().getHitCount(cacheKey);
Modified:
openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java
URL:
http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java?rev=740016&r1=740015&r2=740016&view=diff
==============================================================================
---
openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java
(original)
+++
openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java
Mon Feb 2 15:57:27 2009
@@ -40,6 +40,7 @@
import javax.persistence.Query;
import javax.persistence.TemporalType;
+import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.enhance.Reflection;
import org.apache.openjpa.kernel.Broker;
import org.apache.openjpa.kernel.DelegatingQuery;
@@ -55,10 +56,13 @@
import org.apache.openjpa.kernel.exps.AggregateListener;
import org.apache.openjpa.kernel.exps.FilterListener;
import org.apache.openjpa.kernel.jpql.JPQLParser;
+import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.rop.ResultList;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.RuntimeExceptionTranslator;
+import org.apache.openjpa.util.UserException;
+
import static org.apache.openjpa.kernel.QueryLanguages.LANG_PREPARED_SQL;
/**
@@ -86,7 +90,7 @@
* Constructor; supply factory exception translator and delegate.
*
* @param em The EntityManager which created this query
- * @param ret Exception translater for this query
+ * @param ret Exception translator for this query
* @param query The underlying "kernel" query.
*/
public QueryImpl(EntityManagerImpl em, RuntimeExceptionTranslator ret,
@@ -248,33 +252,18 @@
_query.compile();
return this;
}
-
+
private Object execute() {
if (_query.getOperation() != QueryOperations.OP_SELECT)
throw new
InvalidStateException(_loc.get("not-select-query", _query
.getQueryString()), null, null, false);
- Map params = _positional != null ? _positional : _named;
- Boolean registered = null;
- PreparedQueryCache cache = _em.getPreparedQueryCache();
- if (cache != null) {
- FetchConfiguration fetch = _query.getFetchConfiguration();
- registered = cache.register(_id, _query, fetch);
- boolean alreadyCached = (registered == null);
- String lang = _query.getLanguage();
- QueryStatistics stats = cache.getStatistics();
- if (alreadyCached && LANG_PREPARED_SQL.equals(lang)) {
- PreparedQuery pq = _em.getPreparedQuery(_id);
- params = pq.reparametrize(params, _em.getBroker());
- stats.recordExecution(pq.getOriginalQuery(),
alreadyCached);
- } else {
- stats.recordExecution(_query.getQueryString(), alreadyCached);
- }
- }
+ Map params = _positional != null ? _positional
+ : _named != null ? _named : new HashMap();
+ boolean registered = preExecute(params);
Object result = _query.execute(params);
-
- if (registered == Boolean.TRUE) {
- cache.initialize(_id, result);
+ if (registered) {
+ postExecute(result);
}
return result;
}
@@ -636,6 +625,68 @@
"JPA 2.0 - Method not yet implemented");
}
+ //
+ // Prepared Query Cache related methods
+ //
+
+ /**
+ * Invoked before a query is executed.
+ * If this receiver is cached as a {...@linkplain PreparedQuery prepared
query}
+ * then re-parameterizes the given user parameters. The given map is
cleared
+ * and re-parameterized values are filled in.
+ *
+ * @param params user supplied parameter key-values. Always supply a
+ * non-null map even if the user has not specified any parameter, because
+ * the same map will to be populated by re-parameterization.
+ *
+ * @return true if this invocation caused the query being registered in the
+ * cache.
+ */
+ private boolean preExecute(Map params) {
+ PreparedQueryCache cache = _em.getPreparedQueryCache();
+ if (cache == null) {
+ return false;
+ }
+ FetchConfiguration fetch = _query.getFetchConfiguration();
+ Boolean registered = cache.register(_id, _query, fetch);
+ boolean alreadyCached = (registered == null);
+ String lang = _query.getLanguage();
+ QueryStatistics stats = cache.getStatistics();
+ if (alreadyCached && LANG_PREPARED_SQL.equals(lang)) {
+ PreparedQuery pq = _em.getPreparedQuery(_id);
+ try {
+ Map rep = pq.reparametrize(params, _em.getBroker());
+ params.clear();
+ params.putAll(rep);
+ } catch (UserException ue) {
+ invalidatePreparedQuery();
+ Log log = _em.getConfiguration().getLog(
+ OpenJPAConfiguration.LOG_RUNTIME);
+ if (log.isWarnEnabled())
+ log.warn(ue.getMessage());
+ return false;
+ }
+ stats.recordExecution(pq.getOriginalQuery(), alreadyCached);
+ } else {
+ stats.recordExecution(_query.getQueryString(), alreadyCached);
+ }
+ return registered == Boolean.TRUE;
+ }
+
+ /**
+ * Initialize the registered Prepared Query from the given opaque object.
+ *
+ * @param result an opaque object representing execution result of a query
+ *
+ * @return true if the prepared query can be initialized.
+ */
+ boolean postExecute(Object result) {
+ PreparedQueryCache cache = _em.getPreparedQueryCache();
+ if (cache == null) {
+ return false;
+ }
+ return cache.initialize(_id, result) != null;
+ }
/**
* Remove this query from PreparedQueryCache.
@@ -680,5 +731,4 @@
_id = id;
return this;
}
-
}