PageRenderTime 51ms CodeModel.GetById 19ms app.highlight 26ms RepoModel.GetById 1ms app.codeStats 1ms

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

http://datanucleus-appengine.googlecode.com/
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}