/src/com/google/appengine/datanucleus/query/JPQLQuery.java
Java | 289 lines | 181 code | 32 blank | 76 comment | 51 complexity | df412cba6f7ac6935ff678dcb0084be7 MD5 | raw file
1/********************************************************************** 2Copyright (c) 2009 Google Inc. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15**********************************************************************/ 16package com.google.appengine.datanucleus.query; 17 18import org.datanucleus.ClassLoaderResolver; 19import org.datanucleus.exceptions.NucleusException; 20import org.datanucleus.exceptions.NucleusFatalUserException; 21import org.datanucleus.metadata.AbstractClassMetaData; 22 23import com.google.appengine.datanucleus.DatastoreManager; 24import com.google.appengine.datanucleus.MetaDataUtils; 25 26import org.datanucleus.query.evaluator.JPQLEvaluator; 27import org.datanucleus.query.evaluator.JavaQueryEvaluator; 28import org.datanucleus.ExecutionContext; 29import org.datanucleus.store.Extent; 30import org.datanucleus.store.StoreManager; 31import org.datanucleus.store.connection.ManagedConnection; 32import org.datanucleus.store.connection.ManagedConnectionResourceListener; 33import org.datanucleus.store.query.AbstractJPQLQuery; 34import org.datanucleus.store.query.AbstractQueryResult; 35import org.datanucleus.store.query.CandidateIdsQueryResult; 36import org.datanucleus.store.query.Query; 37import org.datanucleus.store.query.QueryInvalidParametersException; 38import org.datanucleus.util.NucleusLogger; 39 40import java.util.ArrayList; 41import java.util.Iterator; 42import java.util.List; 43import java.util.Map; 44 45/** 46 * Implementation of JPQL for the app engine datastore. 47 * 48 * @author Erick Armbrust <earmbrust@google.com> 49 */ 50public class JPQLQuery extends AbstractJPQLQuery { 51 52 /** 53 * The underlying Datastore query implementation. 54 */ 55 private final DatastoreQuery datastoreQuery; 56 57 /** 58 * Constructs a new query instance that uses the given StoreManager and ExecutionContext. 59 * @param storeMgr StoreManager 60 * @param ec ExecutionContext 61 */ 62 public JPQLQuery(StoreManager storeMgr, ExecutionContext ec) { 63 this(storeMgr, ec, (JPQLQuery) null); 64 } 65 66 /** 67 * Constructs a new query instance having the same criteria as the given query. 68 * @param storeMgr StoreManager 69 * @param ec ExecutionContext 70 * @param q The query from which to copy criteria. 71 */ 72 public JPQLQuery(StoreManager storeMgr, ExecutionContext ec, JPQLQuery q) { 73 super(storeMgr, ec, q); 74 datastoreQuery = new DatastoreQuery(this); 75 } 76 77 /** 78 * Constructor for a JPQL query where the query is specified using the "Single-String" format. 79 * @param storeMgr StoreManager 80 * @param ec ExecutionContext 81 * @param query The JPQL query string. 82 */ 83 public JPQLQuery(StoreManager storeMgr, ExecutionContext ec, String query) { 84 super(storeMgr, ec, query); 85 datastoreQuery = new DatastoreQuery(this); 86 } 87 88 /** 89 * Convenience method to return whether the query should be evaluated in-memory. 90 * @return Use in-memory evaluation? 91 */ 92 protected boolean evaluateInMemory() { 93 if (candidateCollection != null) { 94 if (compilation != null && compilation.getSubqueryAliases() != null) { 95 // TODO In-memory evaluation of subqueries isn't fully implemented yet, so remove this when it is 96 NucleusLogger.QUERY.warn("In-memory evaluator doesn't currently handle subqueries completely so evaluating in datastore"); 97 return false; 98 } 99 100 // Return true unless the user has explicitly said no 101 Object val = getExtension(EXTENSION_EVALUATE_IN_MEMORY); 102 if (val == null) { 103 return true; 104 } 105 return Boolean.valueOf((String)val); 106 } 107 return super.evaluateInMemory(); 108 } 109 110 /** 111 * {@inheritDoc} 112 */ 113 @Override 114 protected Object performExecute(Map parameters) { 115 if (type == org.datanucleus.store.query.Query.BULK_UPDATE) { 116 throw new NucleusFatalUserException("Bulk Update statements are not supported."); 117 } 118 119 long startTime = System.currentTimeMillis(); 120 if (NucleusLogger.QUERY.isDebugEnabled()) { 121 NucleusLogger.QUERY.debug(LOCALISER.msg("021046", "JPQL", getSingleStringQuery(), null)); 122 } 123 124 if (type == Query.BULK_UPDATE) { 125 // TODO Support this 126 throw new NucleusException("JPQL Bulk UPDATE is not yet supported"); 127 } 128 129 if (candidateCollection == null && 130 type == Query.SELECT && resultClass == null && result == null) { 131 // Check for cached query results 132 List<Object> cachedResults = getQueryManager().getDatastoreQueryResult(this, parameters); 133 if (cachedResults != null) { 134 // Query results are cached, so return those 135 return new CandidateIdsQueryResult(this, cachedResults); 136 } 137 } 138 139 Object results = null; 140 if (evaluateInMemory()) { 141 // Evaluating in-memory so build up list of candidates 142 List candidates = null; 143 if (candidateCollection != null) { 144 candidates = new ArrayList(candidateCollection); 145 } else { 146 Extent ext = getStoreManager().getExtent(ec, candidateClass, subclasses); 147 candidates = new ArrayList(); 148 Iterator iter = ext.iterator(); 149 while (iter.hasNext()) { 150 candidates.add(iter.next()); 151 } 152 } 153 154 // Evaluate in-memory over the candidate instances 155 JavaQueryEvaluator resultMapper = new JPQLEvaluator(this, candidates, compilation, 156 parameters, ec.getClassLoaderResolver()); 157 results = resultMapper.execute(true, true, true, true, true); 158 } 159 else { 160 // Evaluate in-datastore 161 boolean inmemoryWhenUnsupported = getEvaluateInMemoryWhenUnsupported(); 162 QueryData qd = datastoreQuery.compile(compilation, parameters, inmemoryWhenUnsupported); 163 if (NucleusLogger.QUERY.isDebugEnabled()) { 164 // Log the query 165 NucleusLogger.QUERY.debug("Query compiled as : " + qd.getDatastoreQueryAsString()); 166 } 167 168 results = datastoreQuery.performExecute(qd); 169 170 boolean filterInMemory = false; 171 boolean orderInMemory = false; 172 boolean resultInMemory = (result != null || grouping != null || having != null || resultClass != null); 173 if (inmemoryWhenUnsupported) { 174 // Set filter/order flags according to what the query can manage in-datastore 175 filterInMemory = !datastoreQuery.isFilterComplete(); 176 177 if (ordering != null) { 178 if (filterInMemory) { 179 orderInMemory = true; 180 } else { 181 if (!datastoreQuery.isOrderComplete()) { 182 orderInMemory = true; 183 } 184 } 185 } 186 } 187 188 // Evaluate any remaining parts in-memory 189 if (filterInMemory || resultInMemory || orderInMemory) { 190 JavaQueryEvaluator resultMapper = new JPQLEvaluator(this, (List)results, compilation, 191 parameters, ec.getClassLoaderResolver()); 192 results = resultMapper.execute(filterInMemory, orderInMemory, 193 resultInMemory, resultClass != null, false); 194 } 195 196 if (results instanceof AbstractQueryResult) { 197 // Lazy loading results : add listener to the connection so we can get a callback when the connection is flushed. 198 final AbstractQueryResult qr = (AbstractQueryResult)results; 199 final ManagedConnection mconn = getStoreManager().getConnection(ec); 200 ManagedConnectionResourceListener listener = new ManagedConnectionResourceListener() { 201 public void managedConnectionPreClose() { 202 // Disconnect the query from this ManagedConnection (read in unread rows etc) 203 qr.disconnect(); 204 } 205 public void managedConnectionPostClose() {} 206 public void resourcePostClose() { 207 mconn.removeListener(this); 208 } 209 public void transactionFlushed() {} 210 public void transactionPreClose() { 211 // Disconnect the query from this ManagedConnection (read in unread rows etc) 212 qr.disconnect(); 213 } 214 }; 215 mconn.addListener(listener); 216 qr.addConnectionListener(listener); 217 } 218 } 219 220 if (NucleusLogger.QUERY.isDebugEnabled()) { 221 NucleusLogger.QUERY.debug(LOCALISER.msg("021074", "JPQL", 222 "" + (System.currentTimeMillis() - startTime))); 223 } 224 225 return results; 226 } 227 228 boolean getEvaluateInMemoryWhenUnsupported() { 229 // Use StoreManager setting and allow override in query extensions 230 boolean inmemory = storeMgr.getBooleanProperty("datanucleus.appengine.query.inMemoryWhenUnsupported"); 231 return getBooleanExtensionProperty(DatastoreManager.QUERYEXT_INMEMORY_WHEN_UNSUPPORTED, inmemory); 232 } 233 234 // Exposed for tests. 235 DatastoreQuery getDatastoreQuery() { 236 return datastoreQuery; 237 } 238 239 @Override 240 protected boolean supportsTimeout() { 241 return true; // GAE/J Datastore supports timeouts 242 } 243 244 @Override 245 public void setUnique(boolean unique) { 246 // Workaround a DataNucleus bug. 247 // The superclass implementation discards the comiled query when this is set, 248 // but since jpql param values are set _before_ the query is executed, 249 // discarding the compiled query discards the parameter values as well and 250 // we have no way of getting them back. 251 this.unique = unique; 252 } 253 254 @Override 255 protected void checkParameterTypesAgainstCompilation(Map parameterValues) { 256 // Disabled as part of our DataNuc 1.1.3 upgrade so that we can be 257 // continue to allow multi-value properties and implicit conversions. 258 259 // TODO(maxr) Re-enable the checks that don't break multi-value filters 260 // and implicit conversions. 261 } 262 263 @Override 264 protected void applyImplicitParameterValueToCompilation(String name, Object value) { 265 try { 266 super.applyImplicitParameterValueToCompilation(name, value); 267 } catch (QueryInvalidParametersException e) { 268 // swallow this exception - need to disable the type checking so we can 269 // be friendly about implicit conversions 270 } 271 } 272 273 @Override 274 public void setSubclasses(boolean subclasses) { 275 // TODO Enable this! 276 // We support only queries that also return subclasses if all subclasses belong to the same kind. 277 if (subclasses) { 278 DatastoreManager storeMgr = (DatastoreManager) ec.getStoreManager(); 279 ClassLoaderResolver clr = ec.getClassLoaderResolver(); 280 AbstractClassMetaData acmd = storeMgr.getMetaDataManager().getMetaDataForClass(getCandidateClass(), clr); 281 if (!MetaDataUtils.isNewOrSuperclassTableInheritanceStrategy(acmd)) { 282 throw new NucleusFatalUserException( 283 "The App Engine datastore only supports queries that return subclass entities with the " + 284 "SINGLE_TABLE interitance mapping strategy."); 285 } 286 } 287 super.setSubclasses(subclasses); 288 } 289}