You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by pp...@apache.org on 2008/09/09 04:03:38 UTC

svn commit: r693341 [1/2] - in /openjpa/branches/sql-cache: openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/ openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/ openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/ openjpa-kerne...

Author: ppoddar
Date: Mon Sep  8 19:03:37 2008
New Revision: 693341

URL: http://svn.apache.org/viewvc?rev=693341&view=rev
Log:
Savepoint commit for Prepared Query cache with 2 tests failing

Added:
    openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/PreparedQueryCacheValue.java
    openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/util/ParameterMap.java
    openjpa/branches/sql-cache/openjpa-kernel/src/test/java/org/apache/openjpa/kernel/
    openjpa/branches/sql-cache/openjpa-kernel/src/test/java/org/apache/openjpa/kernel/TestParameterMap.java
    openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/
    openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Address.java
    openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Company.java
    openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Department.java
    openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Employee.java
    openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java
    openjpa/branches/sql-cache/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PreparedQuery.java
Modified:
    openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java
    openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java
    openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/SQLStoreQuery.java
    openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Param.java
    openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/LogicalUnion.java
    openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SQLBuffer.java
    openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Select.java
    openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java
    openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java
    openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfigurationImpl.java
    openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/QueryLanguages.java
    openjpa/branches/sql-cache/openjpa-kernel/src/main/resources/org/apache/openjpa/util/localizer.properties
    openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/query/TestNativeQueryParameterBinding.java
    openjpa/branches/sql-cache/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerFactoryImpl.java
    openjpa/branches/sql-cache/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java
    openjpa/branches/sql-cache/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java

Modified: openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java?rev=693341&r1=693340&r2=693341&view=diff
==============================================================================
--- openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java (original)
+++ openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java Mon Sep  8 19:03:37 2008
@@ -736,7 +736,8 @@
         ExpressionParser ep = QueryLanguages.parserForLanguage(language);
         if (ep != null)
             return new JDBCStoreQuery(this, ep);
-        if (QueryLanguages.LANG_SQL.equals(language))
+        if (QueryLanguages.LANG_SQL.equals(language) 
+         || QueryLanguages.LANG_PREPARED_SQL.equals(language))
             return new SQLStoreQuery(this);
         return null;
     }

Modified: openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java?rev=693341&r1=693340&r2=693341&view=diff
==============================================================================
--- openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java (original)
+++ openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java Mon Sep  8 19:03:37 2008
@@ -658,14 +658,27 @@
                     idx++;
             }
         }
-
-        String[] sql = new String[sels.size()];
+        // add a sentinel null String to denote that extra SQL will be
+        // required during execution
+        String[] sql = new String[sels.size() + (hasExtraSQL(sels) ? 1 : 0)];
         for (int i = 0; i < sels.size(); i++)
             sql[i] = ((Select) sels.get(i)).toSelect(false, fetch).getSQL(true);
         return sql;
     }
     
     /**
+     * Affirms if any of the given Selects will require extra Select to load
+     * requisite data
+     */
+    boolean hasExtraSQL(List sels) {
+    	for (Object sel : sels) {
+    		if (sel instanceof Select && ((Select)sel).hasNewEagerSelects())
+    			return true;
+    	}
+    	return false;
+    }
+    
+    /**
      * This method is to provide override for non-JDBC or JDBC-like 
      * implementation of executing update.
      */

Modified: openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/SQLStoreQuery.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/SQLStoreQuery.java?rev=693341&r1=693340&r2=693341&view=diff
==============================================================================
--- openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/SQLStoreQuery.java (original)
+++ openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/SQLStoreQuery.java Mon Sep  8 19:03:37 2008
@@ -33,6 +33,7 @@
 import java.util.List;
 import java.util.Set;
 
+import org.apache.commons.collections.map.LinkedMap;
 import org.apache.commons.lang.StringUtils;
 import org.apache.openjpa.jdbc.meta.ClassMapping;
 import org.apache.openjpa.jdbc.meta.MappingRepository;
@@ -409,5 +410,40 @@
             throws SQLException {
             return stmnt.executeQuery();
         }
+        
+        /**
+         * Counts number of bind parameter marker <code>'?'</code> in the
+         * query string. The type of these parameters are unknown but the
+         * count can be used.
+         */
+        public LinkedMap getParameterTypes(StoreQuery q) {
+        	int count = -1;
+        	try {
+        		count = countParamMarker(q.getContext().getQueryString());
+        	} catch (IOException e) {
+        		
+        	}
+            LinkedMap map = new LinkedMap();
+            for (int i = 0; i < count; i++) {
+            	map.put(i+1, null);
+            }
+            return map;
+        }
+        
+    	private static int countParamMarker(String sql) throws IOException {
+    		if (sql.indexOf("?") == -1)
+    			return 0;
+
+    		StreamTokenizer tok = new StreamTokenizer(new StringReader(sql));
+    		tok.resetSyntax();
+    		tok.quoteChar('\'');
+    		tok.wordChars('?', '?');
+    		int count = 0;
+    		for (int ttype; (ttype = tok.nextToken()) != StreamTokenizer.TT_EOF;) {
+    			if (ttype == StreamTokenizer.TT_WORD && "?".equals(tok.sval))
+    				count++;
+    		}
+        	return count;
+    	}
     }
 }

Modified: openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Param.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Param.java?rev=693341&r1=693340&r2=693341&view=diff
==============================================================================
--- openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Param.java (original)
+++ openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Param.java Mon Sep  8 19:03:37 2008
@@ -123,9 +123,9 @@
         SQLBuffer sql, int index) {
         ParamExpState pstate = (ParamExpState) state;
         if (pstate.otherLength > 1)
-            sql.appendValue(((Object[]) pstate.sqlValue)[index], 
+            sql.appendBindParameter(((Object[]) pstate.sqlValue)[index], 
                 pstate.getColumn(index));
         else
-            sql.appendValue(pstate.sqlValue, pstate.getColumn(index));
+            sql.appendBindParameter(pstate.sqlValue, pstate.getColumn(index));
     }
 }

Modified: openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/LogicalUnion.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/LogicalUnion.java?rev=693341&r1=693340&r2=693341&view=diff
==============================================================================
--- openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/LogicalUnion.java (original)
+++ openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/LogicalUnion.java Mon Sep  8 19:03:37 2008
@@ -819,6 +819,10 @@
             SelectExecutor ex = sel.getEager(key);
             return (ex == sel) ? this : ex;
         }
+        
+        public boolean hasNewEagerSelects() {
+        	return sel.hasNewEagerSelects();
+        }
 
         public Joins newJoins() {
             return sel.newJoins();

Modified: openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SQLBuffer.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SQLBuffer.java?rev=693341&r1=693340&r2=693341&view=diff
==============================================================================
--- openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SQLBuffer.java (original)
+++ openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SQLBuffer.java Mon Sep  8 19:03:37 2008
@@ -25,6 +25,7 @@
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
@@ -33,7 +34,6 @@
 import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
 import org.apache.openjpa.jdbc.kernel.exps.Val;
 import org.apache.openjpa.jdbc.schema.Column;
-import org.apache.openjpa.jdbc.schema.ForeignKey;
 import org.apache.openjpa.jdbc.schema.Sequence;
 import org.apache.openjpa.jdbc.schema.Table;
 import serp.util.Numbers;
@@ -41,22 +41,33 @@
 /**
  * Buffer for SQL statements that can be used to create
  * java.sql.PreparedStatements.
+ * 
+ * The buffer holds both the literals and bind variables in the same parameter
+ * list and substitutes them to PreparedStatement. However, a bind variable
+ * is distinguished from the literals by a bit mask on the index of the list
+ * index.
  *
  * @author Marc Prud'hommeaux
  * @author Abe White
+ * @author Pinaki Poddar
  * @since 0.2.4
  */
 public final class SQLBuffer
     implements Serializable, Cloneable {
 
     private static final String PARAMETER_TOKEN = "?";
-
+    private static final char   PARAMETER_TOKEN_CHAR = '?';
+    private static final String SINGLE_QUOTE    = "'";
+    private static final String NULL_STRING     = "NULL";
+    private static final String OPEN_BRACKET    = "(";
+    private static final String CLOSE_BRACKET   = ")";
+    
     private final DBDictionary _dict;
     private final StringBuffer _sql = new StringBuffer();
     private List _subsels = null;
     private List _params = null;
+    private BitSet _varIndex = null;
     private List _cols = null;
-    private List _nonFKParams = null;
 
     /**
      * Default constructor.
@@ -123,8 +134,10 @@
             _sql.insert(sqlIndex, buf._sql.toString());
 
         if (buf._params != null) {
-            if (_params == null)
+            if (_params == null) {
                 _params = new ArrayList();
+                _varIndex = new BitSet();
+            }
             if (_cols == null && buf._cols != null) {
                 _cols = new ArrayList();
                 while (_cols.size() < _params.size())
@@ -133,6 +146,8 @@
 
             if (paramIndex == _params.size()) {
                 _params.addAll(buf._params);
+                for (int i = 0; i < buf._varIndex.size(); i++) 
+                	_varIndex.set(paramIndex + i, buf._varIndex.get(i));
                 if (buf._cols != null)
                     _cols.addAll(buf._cols);
                 else if (_cols != null)
@@ -140,6 +155,11 @@
                         _cols.add(null);
             } else {
                 _params.addAll(paramIndex, buf._params);
+                for (int i = 0; i < buf._varIndex.size(); i++) {
+                	_varIndex.set(paramIndex + i + buf._varIndex.size(), 
+                			_varIndex.get(paramIndex+i));
+                	_varIndex.set(paramIndex + i, buf._varIndex.get(i));
+                }
                 if (buf._cols != null)
                     _cols.addAll(paramIndex, buf._cols);
                 else if (_cols != null)
@@ -147,11 +167,6 @@
                         _cols.add(paramIndex, null);
             }
         }
-        if (buf._nonFKParams != null) {
-            if (_nonFKParams == null)
-                _nonFKParams = new ArrayList();
-            _nonFKParams.addAll(buf._nonFKParams);
-        }
     }
 
     public SQLBuffer append(Table table) {
@@ -193,14 +208,14 @@
      */
     private SQLBuffer append(Select sel, JDBCFetchConfiguration fetch,
         boolean count) {
-        _sql.append("(");
+        _sql.append(OPEN_BRACKET);
         Subselect sub = new Subselect();
         sub.select = sel;
         sub.fetch = fetch;
         sub.count = count;
         sub.sqlIndex = _sql.length();
         sub.paramIndex = (_params == null) ? 0 : _params.size();
-        _sql.append(")");
+        _sql.append(CLOSE_BRACKET);
 
         if (_subsels == null)
             _subsels = new ArrayList(2);
@@ -224,6 +239,13 @@
         }
         return false;
     }
+    
+    /**
+     * Appends a bind variable.
+     */
+    public SQLBuffer appendBindParameter(Object o, Column col) {
+    	return appendValue(o, col, true);
+    }
 
     /**
      * Append a parameter value.
@@ -231,13 +253,17 @@
     public SQLBuffer appendValue(Object o) {
         return appendValue(o, null);
     }
-
+    
+    public SQLBuffer appendValue(Object o, Column col) {
+    	return appendValue(o, col, false);
+    }
+    
     /**
      * Append a parameter value for a specific column.
      */
-    public SQLBuffer appendValue(Object o, Column col) {
+    public SQLBuffer appendValue(Object o, Column col, boolean isParam) {
         if (o == null)
-            _sql.append("NULL");
+            _sql.append(NULL_STRING);
         else if (o instanceof Raw)
             _sql.append(o.toString());
         else {
@@ -245,37 +271,20 @@
 
             // initialize param and col lists; we hold off on col list until
             // we get the first non-null col
-            if (_params == null)
+            if (_params == null) {
                 _params = new ArrayList();
+                _varIndex = new BitSet();
+            }
             if (col != null && _cols == null) {
                 _cols = new ArrayList();
                 while (_cols.size() < _params.size())
                     _cols.add(null);
             }
 
+            _varIndex.set(_params.size(), isParam);
             _params.add(o);
             if (_cols != null)
                 _cols.add(col);
-            if (col == null)
-                return this;
-            boolean isFK = false;
-            ForeignKey[] fks = col.getTable().getForeignKeys();
-            for (int i = 0; i < fks.length; i++) {
-                Column[] cols = fks[i].getColumns();
-                for (int j = 0; j < cols.length; j++) {
-                    if (cols[j] == col) {
-                        isFK = true;
-                        break;
-                    }
-                }
-                if (isFK)
-                    break;
-            }
-            if (!isFK) {
-                if (_nonFKParams == null)
-                    _nonFKParams = new ArrayList();
-                _nonFKParams.add(o);                
-            }
         }
         return this;
     }
@@ -399,21 +408,16 @@
         return (_params == null) ? Collections.EMPTY_LIST : _params;
     }
 
-    public List getNonFKParameters() {
-        return (_nonFKParams == null) ? Collections.EMPTY_LIST : _nonFKParams;
-    }
     /**
      * Return the SQL for this buffer.
      */
     public String getSQL() {
         return getSQL(false);
     }
-    
+
     /**
      * Returns the SQL for this buffer.
      *
-     * @param replaceParams if true, then replace parameters with the
-     * actual parameter values
      */
     public String getSQL(boolean replaceParams) {
         resolveSubselects();
@@ -423,21 +427,26 @@
 
         StringBuffer buf = new StringBuffer();
         Iterator pi = _params.iterator();
+        int iParam = 0;
         for (int i = 0; i < sql.length(); i++) {
-            if (sql.charAt(i) != '?') {
+            if (sql.charAt(i) != PARAMETER_TOKEN_CHAR) {
                 buf.append(sql.charAt(i));
                 continue;
             }
-
             Object param = pi.hasNext() ? pi.next() : null;
+            // bind parameter variables are never replaced
+            if (_varIndex.get(iParam++)) {
+            	buf.append(PARAMETER_TOKEN);
+            	continue;
+            }
             if (param == null)
-                buf.append("NULL");
+                buf.append(NULL_STRING);
             else if (param instanceof Number || param instanceof Boolean)
                 buf.append(param);
             else if (param instanceof String || param instanceof Character)
-                buf.append("'").append(param).append("'");
+                buf.append(SINGLE_QUOTE).append(param).append(SINGLE_QUOTE);
             else
-                buf.append("?");
+                buf.append(PARAMETER_TOKEN);
         }
         return buf.toString();
     }
@@ -598,7 +607,7 @@
             _dict.setUnknown(ps, i + 1, _params.get(i), col);
         }
     }
-
+    
     public int hashCode() {
         int hash = _sql.hashCode();
         return (_params == null) ? hash : hash ^ _params.hashCode();
@@ -629,7 +638,7 @@
      * @param val
      */
     public void addCastForParam(String oper, Val val) {
-        if (_sql.charAt(_sql.length() - 1) == '?') {
+        if (_sql.charAt(_sql.length() - 1) == PARAMETER_TOKEN_CHAR) {
             String castString = _dict.addCastAsType(oper, val);
             if (castString != null)
                 _sql.replace(_sql.length() - 1, _sql.length(), castString);

Modified: openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Select.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Select.java?rev=693341&r1=693340&r2=693341&view=diff
==============================================================================
--- openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Select.java (original)
+++ openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/Select.java Mon Sep  8 19:03:37 2008
@@ -657,6 +657,11 @@
      * Return the eager select for the given key.
      */
     public SelectExecutor getEager(FieldMapping key);
+    
+    /**
+     * Affirms if this select will use extra selects for eager loading.
+     */
+    public boolean hasNewEagerSelects();
 
     /**
      * Return a new instance to use for joining.

Modified: openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java?rev=693341&r1=693340&r2=693341&view=diff
==============================================================================
--- openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java (original)
+++ openjpa/branches/sql-cache/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java Mon Sep  8 19:03:37 2008
@@ -1809,6 +1809,16 @@
     public Map getEagerMap() {
         return _eager;
     }
+    
+    public boolean hasNewEagerSelects() {
+    	if (_eager == null)
+    		return false;
+        for (Object eagerRes : _eager.values()) {
+            if (eagerRes != this)
+                return true;
+        }
+        return false;
+    }
 
     public SelectExecutor getEager(FieldMapping key) {
         if (_eager == null || !_eagerKeys.contains(key))

Modified: openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java?rev=693341&r1=693340&r2=693341&view=diff
==============================================================================
--- openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java (original)
+++ openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java Mon Sep  8 19:03:37 2008
@@ -1526,4 +1526,24 @@
      * @since 1.3.0
      */
     public void setInitializeEagerly(boolean flag);
+    
+    /**
+     * Configuration settings for the Prepared Query Cache to use. 
+     * @see PreparedQueryCacheValue
+     * @since 1.3.0
+     */
+    public String getPreparedQueryCache();
+    
+    /**
+     * Configuration settings for the Prepared Query Cache to use. 
+     * @see PreparedQueryCacheValue
+     * @since 1.3.0
+     */
+    public void setPreparedQueryCache(String cache);    
+    
+    /**
+     * Gets the modifable map of the cached prepared query indexed by query
+     * String.
+     */
+    public Map getPreparedQueryCacheInstance();
 }

Modified: openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfigurationImpl.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfigurationImpl.java?rev=693341&r1=693340&r2=693341&view=diff
==============================================================================
--- openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfigurationImpl.java (original)
+++ openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfigurationImpl.java Mon Sep  8 19:03:37 2008
@@ -136,6 +136,7 @@
     public IntValue runtimeUnenhancedClasses;
     public CacheMarshallersValue cacheMarshallerPlugins;
     public BooleanValue eagerInitialization;
+    public PreparedQueryCacheValue preparedQueryCachePlugin;
 
     // custom values
     public BrokerFactoryValue brokerFactoryPlugin;
@@ -493,6 +494,13 @@
             "getQueryCompilationCacheInstance");
         addValue(queryCompilationCachePlugin);
         
+        preparedQueryCachePlugin = new PreparedQueryCacheValue(
+        	"PreparedQueryCache");
+        preparedQueryCachePlugin.setInstantiatingGetter(
+        	"getPreparedQueryCacheInstance");
+        addValue(preparedQueryCachePlugin);
+        preparedQueryCachePlugin.setDynamic(true);
+        
         runtimeUnenhancedClasses = addInt("RuntimeUnenhancedClasses");
         runtimeUnenhancedClasses.setAliases(new String[] {
             "supported", String.valueOf(
@@ -1395,6 +1403,20 @@
         return (Map) queryCompilationCachePlugin.get();
     }
 
+    public String getPreparedQueryCache() {
+        return preparedQueryCachePlugin.getString();
+    }
+
+    public void setPreparedQueryCache(String queryCompilationCache) {
+    	preparedQueryCachePlugin.setString(queryCompilationCache);
+    }
+    
+    public Map getPreparedQueryCacheInstance() {
+        if (preparedQueryCachePlugin.get() == null)
+        	preparedQueryCachePlugin.instantiate(Map.class, this);
+        return (Map) preparedQueryCachePlugin.get();
+    }
+
     public StoreFacadeTypeRegistry getStoreFacadeTypeRegistry() {
         return _storeFacadeRegistry;
     }

Added: openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/PreparedQueryCacheValue.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/PreparedQueryCacheValue.java?rev=693341&view=auto
==============================================================================
--- openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/PreparedQueryCacheValue.java (added)
+++ openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/conf/PreparedQueryCacheValue.java Mon Sep  8 19:03:37 2008
@@ -0,0 +1,78 @@
+/*
+ * 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.conf;
+
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.openjpa.lib.conf.Configuration;
+import org.apache.openjpa.lib.conf.PluginValue;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.openjpa.lib.util.ParseException;
+import org.apache.openjpa.util.CacheMap;
+
+/**
+ * A cache of prepared queries indexed by an identifier.
+ *
+ * @author Pinaki Poddar
+ * @since 1.3.0 
+ * @nojavadoc
+ */
+public class PreparedQueryCacheValue
+    extends PluginValue {
+
+    public static final String[] ALIASES = {
+        "true", CacheMap.class.getName(),
+        "all", ConcurrentHashMap.class.getName(),
+        "false", null,
+    };
+
+    public PreparedQueryCacheValue(String prop) {
+        super(prop, true);
+        setAliases(ALIASES);
+        setDefault(ALIASES[0]);
+        setClassName(ALIASES[1]);
+    }
+
+    public Object newInstance(String clsName, Class type,
+        Configuration conf, boolean fatal) {
+        // make sure map handles concurrency
+        Map map;
+        
+        try {
+            map = (Map) super.newInstance(clsName, type, conf, fatal);
+        } catch (ParseException pe) {
+        	// For comment on special classloading see QueryCompilationCacheValue  
+            map = (Map) super.newInstance(clsName,
+                PreparedQueryCacheValue.class, conf, fatal);
+        } catch (IllegalArgumentException iae) {
+           map = (Map) super.newInstance(clsName,
+                PreparedQueryCacheValue.class, conf, fatal);
+        }
+
+        if (map != null && !(map instanceof Hashtable)
+            && !(map instanceof CacheMap)
+            && !(map instanceof
+                    org.apache.openjpa.lib.util.concurrent.ConcurrentMap)
+            && !(map instanceof java.util.concurrent.ConcurrentMap))
+            map = Collections.synchronizedMap(map);
+        return map;
+	}
+}

Modified: openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/QueryLanguages.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/QueryLanguages.java?rev=693341&r1=693340&r2=693341&view=diff
==============================================================================
--- openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/QueryLanguages.java (original)
+++ openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/QueryLanguages.java Mon Sep  8 19:03:37 2008
@@ -34,6 +34,7 @@
 public class QueryLanguages {
 
     public static final String LANG_SQL = "openjpa.SQL";
+    public static final String LANG_PREPARED_SQL = "openjpa.SQL.Prepared";
     public static final String LANG_METHODQL = "openjpa.MethodQL";
 
     private static Map _expressionParsers = new HashMap();

Added: openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/util/ParameterMap.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/util/ParameterMap.java?rev=693341&view=auto
==============================================================================
--- openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/util/ParameterMap.java (added)
+++ openjpa/branches/sql-cache/openjpa-kernel/src/main/java/org/apache/openjpa/util/ParameterMap.java Mon Sep  8 19:03:37 2008
@@ -0,0 +1,393 @@
+/*
+ * 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.util;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.apache.commons.collections.map.LinkedMap;
+import org.apache.openjpa.lib.util.Localizer;
+
+/**
+ * Structure for holding binding query parameters.
+ * 
+ * Allows either named or positional parameter keys. The parameter key type is
+ * determined at the first insertion unless specified at construction.
+ *
+ * Maintains parameter ordering.
+ * The parameter ordering for named keys is same as the insertion order.
+ * Positional parameters can use an starting offset that defaults to 1. 
+ * 
+ * Allows to set expected types of values for each parameter.
+ * 
+ * Can validate itself.
+ * 
+ * Can convert a named parameter map to a positional parameter map.
+ * 
+ * @author Pinaki Poddar
+ *
+ */
+public class ParameterMap implements Map {
+	public static enum Type {POSITIONAL, NAMED};
+	
+	private static final Localizer _loc = 
+		Localizer.forPackage(ParameterMap.class);
+	
+	private Map _delegate   = null;
+	private final Map<Object,Class> _valueTypes = new HashMap<Object,Class>();
+	private Type _type = null;
+	private final int _offset;
+	
+	/**
+	 * Construct a map with indefinite key type and positional offset of 1.
+	 */
+	public ParameterMap() {
+		this(null, 1);
+	}
+	
+	/**
+	 * Construct a map with indefinite key type and given positional offset.
+	 */
+	public ParameterMap(int offset) {
+		this(null, offset);
+	}
+	
+	/**
+	 * Construct a map with indefinite key type and positional offset of 1.
+	 */
+	public ParameterMap(Type type) {
+		this(type, 1);
+	}
+	
+	/**
+	 * Construct a map with given key type and given positional offset.
+	 * Even if this map is not positional then the offset is used when the
+	 * map is converted to positional map.
+	 */
+	public ParameterMap(Type type, int offset) {
+		setType(type);
+		_offset = offset;
+			
+	}
+	
+	private void setType(Type type) {
+		_type = type;
+		if (isNamed())
+			_delegate = new LinkedMap();
+		if (isPositional())
+			_delegate = new TreeMap<Integer, Object>();
+	}
+	
+	/**
+	 * Affirms if this receiver contains named parameter keys.
+	 * 
+	 * @return false also denotes that the parameter key type is not yet known.
+	 * Map that are constructed with unspecified key type becomes known at 
+	 * first insertion. 
+	 * 
+	 * @see #put(Object, Object)
+	 */
+	public boolean isNamed() {
+		return Type.NAMED.equals(_type);
+	}
+	
+
+	/**
+	 * Affirms if this receiver contains positional parameter keys.
+	 * 
+	 * @return false also denotes that the parameter key type is not yet known.
+	 * Map that are constructed with unspecified key type becomes known at 
+	 * first insertion. 
+	 * 
+	 * @see #put(Object, Object)
+	 */
+	public boolean isPositional() {
+		return Type.POSITIONAL.equals(_type);
+	}
+	
+	/**
+	 * Gets the enumerated key type allowed in this receiver.
+	 */
+	public Type getType() {
+		return _type;
+	}
+	
+	/**
+	 * Clears this receiver for complete reuse. This includes clearing the
+	 * bound values, their allowed types as well as the type of allowed keys.
+	 */
+	public void clear() {
+		if (_delegate == null)
+			return;
+		_delegate = null;
+		_valueTypes.clear();
+		_type = null;
+	}
+	
+	/**
+	 * Clears all the bound keys by nullifying their values. The allowed value
+	 * type for each key and the allowed key type remain.
+	 */
+	public void clearBinding() {
+		if (_delegate == null)
+			return;
+		for (Object key : keySet())
+			_delegate.put(key, null);
+	}
+
+	/**
+	 * Affirms if this receiver contains the given key.
+	 */
+	public boolean containsKey(Object key) {
+		return _delegate == null ? false : _delegate.containsKey(key);
+	}
+
+	/**
+	 * Affirms if this receiver contains the given value.
+	 */
+	public boolean containsValue(Object value) {
+		return _delegate == null ? null : _delegate.containsValue(value);
+	}
+
+	/**
+	 * Gets the keys known to this receiver.
+	 */
+	public Set entrySet() {
+		return _delegate == null ? Collections.EMPTY_SET : _delegate.entrySet();
+	}
+
+	/**
+	 * Gets the value for the given key.
+	 * 
+	 * @return null if the key does not exist or if the key exists with null
+	 * value.
+	 */
+	public Object get(Object key) {
+		return _delegate == null ? null : _delegate.get(key);
+	}
+
+	/**
+	 * Affirms if this receiver has no key. 
+	 * 
+	 */
+	public boolean isEmpty() {
+		return _delegate == null || _delegate.isEmpty();
+	}
+
+	/**
+	 * Puts the given key-value pair. If this is the first key inserted and the
+	 * key type is unspecified during construction, then the type of the given
+	 * key determines whether this receiver will hold named or positional 
+	 * parameters.
+	 * 
+	 * @param key must be either String or Integer and must match the allowed
+	 * key type of this receiver unless this is a first key insertion and the
+	 * allowed key type is unspecified at construction.
+	 * 
+	 * @param value is validated if an allowed value type for the given key is
+	 * set.
+	 * 
+	 */
+	public Object put(Object key, Object value) {
+		if (key == null)
+			newValidationException("param-null-key", new Object[]{});
+		Type type =  determineKeyType(key);
+		if (_type == null) {
+			setType(type);
+		} else {
+			if (!_type.equals(type))
+				newValidationException("param-mismatch-key-type", key, 
+					key.getClass(), _type);
+					
+			if (isPositional() && ((Integer)key).intValue() < _offset)
+				newValidationException("param-bad-key-index", key, _offset);
+		}
+		
+		assertCompatiableValue(key, value);
+		return _delegate.put(key, value);
+	}
+	
+	public void putAll(Map t) {
+		_delegate.putAll(t);
+	}
+
+	public Object remove(Object key) {
+		return (_delegate == null) ? null : _delegate.remove(key);
+	}
+
+	public int size() {
+		return (_delegate == null) ? 0 : _delegate.size();
+	}
+
+	/**
+	 * Sets the type of value that the given key can be bound to. If a value
+	 * exists for the key, then the value is validated against the given type. 
+	 * 
+	 * @param key the key may or may not have been bound to a value.
+	 * @param type the type of the value that the given key can be bound to. 
+	 */
+	public void setValueType(Object key, Class expectedType) {
+		_valueTypes.put(key, expectedType);
+		if (containsKey(key)) {
+			Object value = get(key);
+			assertCompatiableValue(key, value);
+		}
+	}
+	
+	/**
+	 * Gets the type of value the given key can bind to.
+	 * 
+	 * @return null denotes the value type for the given key is either not set 
+	 * or set to null explicitly.
+	 *  
+	 */
+	public Class getValueType(Object key) {
+		return _valueTypes.get(key);
+	}
+	
+	/**
+	 * Gets the set of keys. The returned set has deterministic iteration order.
+	 * For named parameter type, the resultant order is the order in which
+	 * keys were inserted.
+	 * For positional parameter type, the resultant order is the natural order 
+	 * of the key indices. 
+	 */
+	public Set keySet() {
+		return (_delegate == null) ? Collections.EMPTY_SET : _delegate.keySet();
+	}
+
+	/**
+	 * Gets the values. The returned collection has deterministic iteration 
+	 * order. 
+	 * For named parameter type, the resultant order is the order in which
+	 * key-value pairs were inserted.
+	 * For positional parameter type, the resultant order is the natural order 
+	 * of the key indices.
+	 * 
+	 */
+	public Collection values() {
+		return (_delegate == null) ? Collections.EMPTY_SET : _delegate.values();
+	}
+	
+	/**
+	 * Get the unmodifable map of key-value in deterministic iteration order.
+	 * For named parameter type, the resultant order is the order in which
+	 * key-value pairs were inserted.
+	 * For positional parameter type, the resultant order is the natural order 
+	 * of the key indices.
+	 */
+	public Map getMap() {
+		return isEmpty() ? Collections.EMPTY_MAP : 
+			Collections.unmodifiableMap(_delegate);
+	}
+	
+	/**
+	 * Create a positional map as a copy of this receiver.
+	 * If this receiver is a named map, then the new positional map will have
+	 * integer keys corresponding to insertion order of the original keys. 
+	 * 
+	 */
+	public ParameterMap toPositional() {
+		ParameterMap positional = new ParameterMap(Type.POSITIONAL, _offset);
+		Set keys = keySet();
+		int i = _offset;
+		for (Object key : keys) {
+			Object newKey = (isNamed()) ? new Integer(i++) : key;
+			positional.put(newKey, get(key));
+			Class expectedType = getValueType(key);
+			if (_valueTypes.containsKey(key))
+				setValueType(newKey, expectedType);
+		}
+		return positional;
+	}
+	
+	/**
+	 * Validate the parameters against a set of expected parameter types.
+	 * If strict then also checks that each given expected key exists in
+	 * this receiver.
+	 */
+	public boolean validate(LinkedMap expected, boolean strict) {
+		if (expected.size() != size())
+			newValidationException("param-invalid-size", 
+			    expected.size(), expected, size(), getMap());
+		if (!strict) return true;
+		for (Object key : expected.keySet()) {
+			key = isNamed() ? key.toString() : Integer.parseInt(key.toString());
+			if (!containsKey(key))
+				newValidationException("param-missing", key, expected, getMap());
+		} 
+		return true;
+	}
+	
+	void assertCompatiableValue(Object key, Object value) {
+		Class expected = _valueTypes.get(key);
+		if (expected == null)
+			return;
+		if (value == null) {
+			if (expected.isPrimitive()) 
+				newValidationException("param-null-primitive", key, expected);
+			return;
+		}
+		if (expected.isPrimitive()) {
+			if (unwrap(value.getClass()) != expected) 
+				newValidationException("param-mismatch-value-type", 
+					key, value, value.getClass(), expected);
+		} else if (!expected.isAssignableFrom(value.getClass())) {
+			newValidationException("param-mismatch-value-type", 
+					key, value, value.getClass(), expected);
+		} 
+	}
+	
+	private Type determineKeyType(Object key) {
+		if (key instanceof Integer)
+			return Type.POSITIONAL;
+		else if (key instanceof String)
+			return Type.NAMED;
+		else
+			newValidationException("param-bad-key-type", key, key.getClass());
+		return null;//unreachable
+	}
+
+	
+	Class unwrap(Class c) {
+		if (c == null)
+			return c;
+		if (c == Boolean.class)   return boolean.class;
+		if (c == Byte.class)      return byte.class;
+		if (c == Character.class) return char.class;
+		if (c == Double.class)    return double.class;
+		if (c == Float.class)     return float.class;
+		if (c == Integer.class)   return int.class;
+		if (c == Long.class)      return long.class;
+		if (c == Short.class)     return short.class;
+		return c;
+	}
+	
+	public String toString() {
+		return _type + " values " + _delegate + " types " + _valueTypes;
+	}
+	
+	void newValidationException(String msgKey, Object... args) {
+		throw new IllegalArgumentException(_loc.get(msgKey, args).toString());
+	}
+}

Modified: openjpa/branches/sql-cache/openjpa-kernel/src/main/resources/org/apache/openjpa/util/localizer.properties
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-kernel/src/main/resources/org/apache/openjpa/util/localizer.properties?rev=693341&r1=693340&r2=693341&view=diff
==============================================================================
--- openjpa/branches/sql-cache/openjpa-kernel/src/main/resources/org/apache/openjpa/util/localizer.properties (original)
+++ openjpa/branches/sql-cache/openjpa-kernel/src/main/resources/org/apache/openjpa/util/localizer.properties Mon Sep  8 19:03:37 2008
@@ -73,3 +73,17 @@
 no-store-exts: No store-specific facade found matching "{0}".  Using default.
 objectid-abstract: Cannot create new application identity instance for \
 	abstract class "{0}".
+param-null-key: Null parameter keys are not allowed.
+param-bad-key-type: Parameter key "{0}" of "{1}" is not allowed. Parameter key \
+	can be either String or integer.
+param-mismatch-key-type: Parameter key "{0}" of "{1}" does not match with \
+	its expected "{2}".
+param-bad-key-index: Parameter key "{0}" less than "{1}" is not allowed.
+param-null-primitive: Parameter "{0}" is of primitive type "{1}". Its value \
+	can not be null.
+param-mismatch-value-type: Parameter "{0}" can not be set to "{1}" of "{2}" \
+	as the expected value type is "{3}".
+param-invalid_size: Expected {0} parameter(s) of type "{1}" is different in \
+	number than available {2} parameter(s) of type "{3}".
+param-missing: Parameter "{0}" is missing in expected parameter(s) of type \
+	"{1}". Available parameters are "{2}".
\ No newline at end of file

Added: openjpa/branches/sql-cache/openjpa-kernel/src/test/java/org/apache/openjpa/kernel/TestParameterMap.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-kernel/src/test/java/org/apache/openjpa/kernel/TestParameterMap.java?rev=693341&view=auto
==============================================================================
--- openjpa/branches/sql-cache/openjpa-kernel/src/test/java/org/apache/openjpa/kernel/TestParameterMap.java (added)
+++ openjpa/branches/sql-cache/openjpa-kernel/src/test/java/org/apache/openjpa/kernel/TestParameterMap.java Mon Sep  8 19:03:37 2008
@@ -0,0 +1,286 @@
+/*
+ * 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.kernel;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.openjpa.util.ParameterMap;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the properties of ParameterMap.
+ * 
+ * @author Pinaki Poddar
+ *
+ */
+public class TestParameterMap extends TestCase {
+	protected void setUp() throws Exception {
+		super.setUp();
+	}
+
+	protected void tearDown() throws Exception {
+		super.tearDown();
+	}
+	
+	public void testTypeIsDeterminedOnFirstInsertion() {
+		ParameterMap m1 = new ParameterMap();
+		assertFalse(m1.isNamed());
+		assertFalse(m1.isPositional());
+		m1.put("key", "value");
+		assertTrue(m1.isNamed());
+		assertFalse(m1.isPositional());
+		
+		ParameterMap m2 = new ParameterMap();
+		assertFalse(m2.isNamed());
+		assertFalse(m2.isPositional());
+		m2.put(1, "value");
+		assertFalse(m2.isNamed());
+		assertTrue(m2.isPositional());
+	}
+	
+	public void testTypeIsDeterminedOnConstruction() {
+		assertTrue(new ParameterMap(ParameterMap.Type.NAMED).isNamed());
+		assertTrue(new ParameterMap(ParameterMap.Type.POSITIONAL).isPositional());
+		
+	}
+	
+	public void testConvertNamedToPositional() {
+		ParameterMap m = new ParameterMap();
+		m.put("key1", "value1");
+		m.put("key2", "value2");
+		
+		ParameterMap positional = m.toPositional();
+		assertTrue(positional.isPositional());
+		assertEquals("value1", positional.get(1));
+		assertEquals("value2", positional.get(2));
+	}
+	
+	public void testConvertPositionalToPositional() {
+		ParameterMap m = new ParameterMap();
+		m.put(2, "value2");
+		m.put(1, "value1");
+		
+		ParameterMap positional = m.toPositional();
+		assertTrue(positional.isPositional());
+		assertEquals("value1", positional.get(1));
+		assertEquals("value2", positional.get(2));
+	}
+	
+	public void testNamedKeysAreInInsertionOrder() {
+		ParameterMap m = new ParameterMap();
+		m.put("key1", "value1");
+		m.put("key2", "value2");
+		
+		Iterator keys = m.keySet().iterator();
+		assertEquals("key1", keys.next());
+		assertEquals("key2", keys.next());
+		
+		// Update an old key and insert a new one
+		m.put("key1", "new value in old key");
+		m.put("key3", "new value");
+		keys = m.keySet().iterator();
+		assertEquals("key1", keys.next());
+		assertEquals("key2", keys.next());
+		assertEquals("key3", keys.next());
+		
+	}
+	
+	public void testNamedValuesInInsertionOrder() {
+		ParameterMap map = new ParameterMap();
+		map.put("key1", "value1");
+		map.put("key2", "value2");
+		Iterator values = map.values().iterator();
+		assertEquals("value1", values.next());
+		assertEquals("value2", values.next());
+
+		// Update an old key and insert a new one
+		map.put("key1", "new value in old key");
+		map.put("key3", "new value");
+		values = map.values().iterator();
+		assertEquals("new value in old key", values.next());
+		assertEquals("value2", values.next());
+		assertEquals("new value", values.next());
+	}
+
+	public void testNamedMapInInsertionOrder() {
+		ParameterMap map = new ParameterMap();
+		map.setValueType("key1", int.class);
+		map.setValueType("key2", String.class);
+		
+		map.put("key1", 5);
+		map.put("key2", "value2");
+		
+		Iterator entries = map.getMap().entrySet().iterator();
+		for (int i = 0; i < 2; i++) {
+			Map.Entry entry = (Map.Entry)entries.next();
+			if (i == 0) assertTrue("key1".equals(entry.getKey()) && entry.getValue().equals(5));
+			if (i == 1) assertTrue("key2".equals(entry.getKey()) && entry.getValue().equals("value2"));
+		}
+
+		map.put("key1", 10);
+		map.put("key3", "value3");
+		entries = map.getMap().entrySet().iterator();
+		for (int i = 0; i < 3; i++) {
+			Map.Entry entry = (Map.Entry)entries.next();
+			if (i == 0) assertTrue("key1".equals(entry.getKey()) && entry.getValue().equals(10));
+			if (i == 1) assertTrue("key2".equals(entry.getKey()) && entry.getValue().equals("value2"));
+			if (i == 2) assertTrue("key3".equals(entry.getKey()) && entry.getValue().equals("value3"));
+		}
+	}
+	
+	public void testNamedParameterWithWrongTypeBefore() {
+		ParameterMap map = new ParameterMap();
+		map.setValueType("key1", int.class);
+		map.setValueType("key2", String.class);
+		map.setValueType("key3", Integer.class);
+		map.setValueType("key4", Integer.class);
+		
+		map.put("key1", 5);
+		try {
+			map.put("key2", 7);
+			fail();
+		} catch (IllegalArgumentException ex) {
+			// good
+		}
+		map.put("key3", new Integer(7));
+		map.put("key4", 8);
+		map.put("key1", new Integer(9));
+	}
+
+	public void testNamedParameterWithWrongTypeAfter() {
+		ParameterMap map = new ParameterMap();
+		map.put("key1", 5);
+		map.put("key2", 7);
+		map.setValueType("key1", int.class);
+		
+		try {
+			map.setValueType("key2", String.class);
+			fail();
+		} catch (IllegalArgumentException ex) {
+			
+		}
+		
+		map.put("key3", new Integer(7));
+		map.put("key4", 8);
+		map.setValueType("key3", Integer.class);
+		map.setValueType("key4", Integer.class);
+	}
+	
+	public void testPositionalParameterWithoutSpecificType() {
+		ParameterMap map = new ParameterMap();
+		map.put(1, "value1");
+		map.put(2, "value2");
+		assertFalse(map.isNamed());
+		assertEquals(2, map.size());
+		Iterator values = map.values().iterator();
+		assertEquals("value1", values.next());
+		assertEquals("value2", values.next());
+
+		map.put(1, "new value in old key");
+		values = map.values().iterator();
+		assertEquals("new value in old key", values.next());
+		assertEquals("value2", values.next());
+	}
+
+	public void testPositionalParameterWithSpecificType() {
+		ParameterMap map = new ParameterMap();
+		map.setValueType(1, int.class);
+		map.setValueType(2, String.class);
+		
+		map.put(1, 5);
+		map.put(2, "value2");
+		assertFalse(map.isNamed());
+		assertEquals(2, map.size());
+		Iterator values = map.values().iterator();
+		assertEquals(5, values.next());
+		assertEquals("value2", values.next());
+
+		map.put(1, 10);
+		values = map.values().iterator();
+		assertEquals(10, values.next());
+		assertEquals("value2", values.next());
+	}
+	
+	public void testPositionalParameterWithWrongTypeBefore() {
+		ParameterMap map = new ParameterMap();
+		map.setValueType(1, int.class);
+		map.setValueType(2, String.class);
+		map.setValueType(3, Integer.class);
+		map.setValueType(4, Integer.class);
+		
+		map.put(1, 5);
+		try {
+			map.put(2, 7);
+			fail();
+		} catch (IllegalArgumentException ex) {
+			
+		}
+		map.put(3, new Integer(7));
+		map.put(4, 8);
+		map.put(1, new Integer(9));
+	}
+
+	public void testPositionalParameterWithWrongTypeAfter() {
+		ParameterMap map = new ParameterMap();
+		map.put(1, 5);
+		map.put(2, 7);
+		map.setValueType(1, int.class);
+		
+		try {
+			map.setValueType(2, String.class);
+			fail();
+		} catch (IllegalArgumentException ex) {
+			
+		}
+		
+		map.put(3, new Integer(7));
+		map.put(4, 8);
+		map.setValueType(3, Integer.class);
+		map.setValueType(4, Integer.class);
+	}
+	
+	public void testPositionalParameterInsertedInRandomOrder() {
+		ParameterMap map = new ParameterMap();
+		map.put(3, 30);
+		map.put(1, 10);
+		map.put(2, 20);
+		map.put(5, 50);
+		map.put(4, 40);
+		Iterator values = map.values().iterator();
+		assertEquals(10, values.next());
+		assertEquals(20, values.next());
+		assertEquals(30, values.next());
+		assertEquals(40, values.next());
+		assertEquals(50, values.next());
+	}
+
+	public void testPositionalParameterInsertedWithGap() {
+		ParameterMap map = new ParameterMap();
+		map.put(3, 30);
+		map.put(2, 20);
+		map.put(5, 50);
+		Iterator values = map.values().iterator();
+		assertEquals(20, values.next());
+		assertEquals(30, values.next());
+		assertEquals(50, values.next());
+	}
+
+}

Modified: openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/query/TestNativeQueryParameterBinding.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/query/TestNativeQueryParameterBinding.java?rev=693341&r1=693340&r2=693341&view=diff
==============================================================================
--- openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/query/TestNativeQueryParameterBinding.java (original)
+++ openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/query/TestNativeQueryParameterBinding.java Mon Sep  8 19:03:37 2008
@@ -21,8 +21,6 @@
 import javax.persistence.EntityManager;
 import javax.persistence.Query;
 
-import org.apache.openjpa.persistence.jdbc.query.domain.Applicant;
-import org.apache.openjpa.persistence.jdbc.query.domain.Application;
 import org.apache.openjpa.persistence.test.SingleEMFTestCase;
 
 /**

Added: openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Address.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Address.java?rev=693341&view=auto
==============================================================================
--- openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Address.java (added)
+++ openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Address.java Mon Sep  8 19:03:37 2008
@@ -0,0 +1,86 @@
+/*
+ * 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.jdbc.sqlcache;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+@Entity
+public class Address {
+	@Id
+	@GeneratedValue
+	private long id;
+	
+	private String street;
+	
+	private String city;
+	
+	private String state;
+	
+	private int zip;
+	
+	public Address() {
+		
+	}
+
+	public Address(String street, String city, String state, int zip) {
+		super();
+		this.street = street;
+		this.city = city;
+		this.state = state;
+		this.zip = zip;
+	}
+
+	public String getStreet() {
+		return street;
+	}
+
+	public void setStreet(String street) {
+		this.street = street;
+	}
+
+	public String getCity() {
+		return city;
+	}
+
+	public void setCity(String city) {
+		this.city = city;
+	}
+
+	public String getState() {
+		return state;
+	}
+
+	public void setState(String state) {
+		this.state = state;
+	}
+
+	public int getZip() {
+		return zip;
+	}
+
+	public void setZip(int zip) {
+		this.zip = zip;
+	}
+
+	public long getId() {
+		return id;
+	}
+}

Added: openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Company.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Company.java?rev=693341&view=auto
==============================================================================
--- openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Company.java (added)
+++ openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Company.java Mon Sep  8 19:03:37 2008
@@ -0,0 +1,60 @@
+/*
+ * 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.jdbc.sqlcache;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import javax.persistence.*;
+
+@Entity
+public class Company {
+	@Id
+	@GeneratedValue
+	private long id;
+	
+	private String name;
+	
+	@OneToMany(mappedBy="company", cascade=CascadeType.ALL)
+	private Collection<Department> departments = new HashSet<Department>();
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public Collection<Department> getDepartments() {
+		return departments;
+	}
+
+	public void addDepartment(Department dept) {
+		this.departments.add(dept);
+		dept.setCompany(this);
+	}
+
+	public long getId() {
+		return id;
+	}
+	
+	
+
+}

Added: openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Department.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Department.java?rev=693341&view=auto
==============================================================================
--- openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Department.java (added)
+++ openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Department.java Mon Sep  8 19:03:37 2008
@@ -0,0 +1,70 @@
+/*
+ * 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.jdbc.sqlcache;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import javax.persistence.*;
+
+@Entity
+public class Department {
+	@Id
+	@GeneratedValue
+	private long id;
+	
+	private String name;
+	
+	@ManyToOne
+	private Company company;
+	
+	@OneToMany
+	private Collection<Employee> employees = new HashSet<Employee>();
+
+	public Company getCompany() {
+		return company;
+	}
+
+	public void setCompany(Company company) {
+		this.company = company;
+	}
+
+	public Collection<Employee> getEmployees() {
+		return employees;
+	}
+
+	public void addEmployees(Employee emp) {
+		this.employees.add(emp);
+		emp.setDepartment(this);
+	}
+
+	public long getId() {
+		return id;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+	
+
+}

Added: openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Employee.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Employee.java?rev=693341&view=auto
==============================================================================
--- openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Employee.java (added)
+++ openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/Employee.java Mon Sep  8 19:03:37 2008
@@ -0,0 +1,65 @@
+/*
+ * 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.jdbc.sqlcache;
+
+import javax.persistence.*;
+
+@Entity
+public class Employee {
+	@Id
+	@GeneratedValue
+	private long id;
+	
+	private String name;
+	
+	@ManyToOne
+	private Department department;
+	
+	@OneToOne
+	private Address address;
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public Department getDepartment() {
+		return department;
+	}
+
+	public void setDepartment(Department department) {
+		this.department = department;
+	}
+
+	public Address getAddress() {
+		return address;
+	}
+
+	public void setAddress(Address address) {
+		this.address = address;
+	}
+
+	public long getId() {
+		return id;
+	}
+
+}

Added: openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java
URL: http://svn.apache.org/viewvc/openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java?rev=693341&view=auto
==============================================================================
--- openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java (added)
+++ openjpa/branches/sql-cache/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/sqlcache/TestPreparedQueryCache.java Mon Sep  8 19:03:37 2008
@@ -0,0 +1,180 @@
+/*
+ * 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.jdbc.sqlcache;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.openjpa.conf.OpenJPAConfiguration;
+import org.apache.openjpa.persistence.OpenJPAEntityManager;
+import org.apache.openjpa.persistence.OpenJPAQuery;
+import org.apache.openjpa.persistence.PreparedQuery;
+import org.apache.openjpa.persistence.test.SingleEMFTestCase;
+
+/**
+ * Performance oriented test to see comparative difference in response time with
+ * or with SQL caching.
+ * 
+ * @author Pinaki Poddar
+ * 
+ */
+public class TestPreparedQueryCache extends SingleEMFTestCase {
+	public static final int SAMPLE_SIZE = 100;
+	public static final long NANOS_TO_MILLS = 1000*1000;
+	public static final boolean QUERY_CACHE = true;
+	public static final String[] COMPANY_NAMES = { "acme.org" };
+	public static final String[] DEPARTMENT_NAMES = { "Marketing", "Sales",
+			"Engineering" };
+	public static final String[] EMPLOYEE_NAMES = { "Tom", "Dick", "Harray" };
+
+	public void setUp() throws Exception {
+		super.setUp(Company.class, Department.class, Employee.class,
+				Address.class, "openjpa.Log", "SQL=WARN");
+	}
+
+	public void tearDown() throws Exception {
+		super.tearDown();
+	}
+
+	public void testPreparedQueryCacheIsActiveByDefault() {
+		OpenJPAConfiguration conf = emf.getConfiguration();
+		assertEquals("true", conf.getPreparedQueryCache());
+		assertNotNull(conf.getPreparedQueryCacheInstance());
+	}
+	
+	public void testPreparedQueryCacheCanBeDeactivatedDynamically() {
+		OpenJPAConfiguration conf = emf.getConfiguration();
+		assertNotNull(conf.getPreparedQueryCacheInstance());
+		conf.setPreparedQueryCache("false");
+		assertNull(conf.getPreparedQueryCacheInstance());
+	}
+
+	public void testSimpleQueryCache() {
+		compare("select p from Company p");
+	}
+
+	public void testQueryWithLiteral() {
+		compare("select p from Company p where p.name = 'PObject'");
+	}
+
+	public void testQueryWithParameter() {
+		compare("select p from Company p where p.name = :param", 
+				"param", "x");
+	}
+
+	public void testJoins() {
+		compare("select e from Employee e " + "where e.name = :emp "
+				+ "and e.department.name = :dept " +
+				"and e.department.company.name = :company " +
+				"and e.address.zip = :zip", 
+				
+				"emp", "John", 
+				"dept",	"Engineering",
+				"company", "acme.org",
+				"zip", 12345);
+	}
+
+	/**
+	 * Compare the result of execution of the same query with and without
+	 * Prepared Query Cache.
+	 * 
+	 */
+	void compare(String jpql, Object... params) {
+		// run the query once for warming up 
+		run(jpql, params, !QUERY_CACHE, 1);
+		
+		// run N times without cache
+		long without = run(jpql, params, !QUERY_CACHE, SAMPLE_SIZE);
+		
+		// run N times with cache
+		long with = run(jpql, params, QUERY_CACHE, SAMPLE_SIZE);
+		
+		long delta = (without == 0) ? 0 : (without - with) * 100 / without;
+		
+		String sql = getSQL(jpql);
+		System.err.println("Execution time in millis for " + SAMPLE_SIZE
+				+ " query execution with and without SQL cache:" + with + " "
+				+ without + " (" + delta + "%)");
+		System.err.println("JPQL: " + jpql);
+		System.err.println("SQL : " + sql);
+		assertFalse("change in execution time = " + delta + "%", delta < 0);
+	}
+
+	/**
+	 * Create and run a query N times with the given parameters. The time for 
+	 * each query execution is measured in nanosecond precision) and finally
+	 * median time taken in N observation is returned in nanosecond.  
+	 * 
+	 * returns median time taken for single execution.
+	 */
+	long run(String jpql, Object[] params, boolean cache, int N) {
+		OpenJPAEntityManager em = emf.createEntityManager();
+		em.getConfiguration().setPreparedQueryCache("" + cache);
+		
+		List<Long> stats = new ArrayList<Long>();
+		for (int i = 0; i < N; i++) {
+			long start = System.nanoTime();
+			OpenJPAQuery q = em.createQuery(jpql);
+			for (int j = 0; params != null && j < params.length - 1; j += 2) {
+				String key = params[j].toString();
+				Object val = params[j + 1];
+				if (val instanceof String)
+					q.setParameter(key, val + "-" + i);
+				else if (val instanceof Integer)
+					q.setParameter(key, ((Integer) val).intValue() + i);
+				else
+					q.setParameter(key, val);
+			}
+			q.getResultList();
+			q.closeAll();
+			long end = System.nanoTime();
+			stats.add(end - start);
+		}
+		em.close();
+		Collections.sort(stats);
+		return stats.get(N/2);
+	}	
+	
+	public boolean isCached(String jpql) {
+		Map cache = emf.getConfiguration().getPreparedQueryCacheInstance();
+		return cache != null && cache.get(jpql) != null
+				&& cache.get(jpql) != PreparedQuery.NOT_CACHABLE;
+	}
+	
+	String getSQL(String jpql) {
+		Map cache = emf.getConfiguration().getPreparedQueryCacheInstance();
+		return cache == null ? null : ((PreparedQuery)cache.get(jpql)).getSQL();
+	}
+
+
+
+	public static void main(String[] args) throws Exception {
+		TestPreparedQueryCache _this = new TestPreparedQueryCache();
+		_this.setUp();
+		String jpql = "select e from Employee e where e.name = :emp and "
+				+ "e.department.name = :dept and "
+				+ "e.department.company.name = :company and e.address.zip = :zip";
+		Object[] params = { "emp", "John", "dept", "Engineering", "company",
+				"acme.org", "zip", 12345 };
+		_this.run(jpql, params, true, 100);
+	}
+
+}