/src/com/google/appengine/datanucleus/query/JDOQLQuery.java

http://datanucleus-appengine.googlecode.com/ · Java · 258 lines · 164 code · 28 blank · 66 comment · 48 complexity · aeef8294ce05362a51aba82310f4572b MD5 · raw file

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