Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / datastore / PreparedQueryImpl.java
blob90affeb788c1bdeb8ef6c20ec5fd2a01f250f2ef
1 // Copyright 2007 Google Inc. All rights reserved.
3 package com.google.appengine.api.datastore;
5 import static com.google.appengine.api.datastore.FetchOptions.Builder.withLimit;
6 import static com.google.common.base.Preconditions.checkArgument;
8 import com.google.appengine.api.datastore.CompositeIndexManager.IndexComponentsOnlyQuery;
9 import com.google.appengine.api.datastore.CompositeIndexManager.IndexSource;
10 import com.google.appengine.api.datastore.ReadPolicy.Consistency;
11 import com.google.appengine.api.utils.FutureWrapper;
12 import com.google.apphosting.api.ApiProxy.ApiConfig;
13 import com.google.apphosting.api.DatastorePb;
14 import com.google.storage.onestore.v3.OnestoreEntity;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.concurrent.Future;
20 /**
21 * Implements PreparedQuery.
24 class PreparedQueryImpl extends BasePreparedQuery {
25 private final DatastoreServiceConfig datastoreServiceConfig;
26 private final ApiConfig apiConfig;
27 private final Query query;
28 private final Transaction txn;
30 public PreparedQueryImpl(ApiConfig apiConfig, DatastoreServiceConfig datastoreServiceConfig,
31 Query query, Transaction txn) {
32 this.apiConfig = apiConfig;
33 this.datastoreServiceConfig = datastoreServiceConfig;
34 this.query = query;
35 this.txn = txn;
37 checkArgument(query.getFilter() == null);
38 checkArgument(txn == null || query.getAncestor() != null,
39 "Only ancestor queries are allowed inside transactions.");
40 TransactionImpl.ensureTxnActive(txn);
43 @Override
44 public List<Entity> asList(FetchOptions fetchOptions) {
45 return new LazyList(runQuery(query, fetchOptions));
48 @Override
49 public QueryResultList<Entity> asQueryResultList(FetchOptions fetchOptions) {
50 FetchOptions override = new FetchOptions(fetchOptions);
51 if (override.getCompile() == null) {
52 override.compile(true);
54 LazyList lazyList = new LazyList(runQuery(query, override));
55 return lazyList;
58 @Override
59 public Iterator<Entity> asIterator(FetchOptions fetchOptions) {
60 return runQuery(query, fetchOptions);
63 @Override
64 public QueryResultIterator<Entity> asQueryResultIterator(FetchOptions fetchOptions) {
65 if (fetchOptions.getCompile() == null) {
66 fetchOptions = new FetchOptions(fetchOptions).compile(true);
68 return runQuery(query, fetchOptions);
71 @Override
72 public Entity asSingleEntity() throws TooManyResultsException {
73 List<Entity> entities = asList(withLimit(2));
74 if (entities.isEmpty()) {
75 return null;
76 } else if (entities.size() != 1) {
77 throw new TooManyResultsException();
79 return entities.get(0);
82 @Override
83 /**
84 * Counts the number of entities in the result set.
86 * This method will run a query that will offset past all results and return
87 * the number of results that have been skipped in the process.
89 * (offset, limit) is converted to (offset + limit, 0) so that no results are
90 * actually returned. The resulting count is max(0, skipped - offset). This
91 * is the number of entities in the range [offset, offset + limit) which is
92 * the count.
94 public int countEntities(FetchOptions fetchOptions) {
95 FetchOptions overrideOptions = new FetchOptions(fetchOptions);
97 overrideOptions.limit(0);
98 if (fetchOptions.getLimit() != null) {
99 if (fetchOptions.getOffset() != null) {
100 int offset = fetchOptions.getLimit() + fetchOptions.getOffset();
101 overrideOptions.offset(offset >= 0 ? offset : Integer.MAX_VALUE);
102 } else {
103 overrideOptions.offset(fetchOptions.getLimit());
105 } else {
106 overrideOptions.offset(Integer.MAX_VALUE);
109 int count = runQuery(query, overrideOptions).getNumSkipped();
110 if (fetchOptions.getOffset() != null) {
111 if (count < fetchOptions.getOffset()) {
112 count = 0;
113 } else {
114 count = count - fetchOptions.getOffset();
117 return count;
120 private QueryResultIteratorImpl runQuery(Query q, FetchOptions fetchOptions) {
121 final DatastorePb.Query queryProto = convertToPb(q, fetchOptions);
122 if (datastoreServiceConfig.getReadPolicy().getConsistency() == Consistency.EVENTUAL) {
123 queryProto.setFailoverMs(DatastoreServiceImpl.ARBITRARY_FAILOVER_READ_MS);
124 queryProto.setStrong(false);
127 Future<DatastorePb.QueryResult> result = DatastoreApiHelper.makeAsyncCall(
128 apiConfig, "RunQuery", queryProto, new DatastorePb.QueryResult());
130 result = new FutureWrapper<DatastorePb.QueryResult, DatastorePb.QueryResult>(result) {
131 @Override
132 protected Throwable convertException(Throwable cause) {
133 if (cause instanceof DatastoreNeedIndexException) {
134 addMissingIndexData(queryProto, (DatastoreNeedIndexException) cause);
136 return cause;
139 @Override
140 protected DatastorePb.QueryResult wrap(DatastorePb.QueryResult result) throws Exception {
141 return result;
145 QueryResultsSourceImpl src = new QueryResultsSourceImpl(apiConfig,
146 datastoreServiceConfig.getDatastoreCallbacks(), fetchOptions, txn, query, result);
147 return new QueryResultIteratorImpl(this, src, fetchOptions, txn);
150 private void addMissingIndexData(
151 DatastorePb.Query queryProto, DatastoreNeedIndexException e) {
152 IndexComponentsOnlyQuery indexQuery = new IndexComponentsOnlyQuery(queryProto);
153 CompositeIndexManager mgr = new CompositeIndexManager();
154 OnestoreEntity.Index index = mgr.compositeIndexForQuery(indexQuery);
155 if (index != null) {
156 String xml = mgr.generateXmlForIndex(index, IndexSource.manual);
157 e.setMissingIndexDefinitionXml(xml);
158 } else {
162 private DatastorePb.Query convertToPb(Query q, FetchOptions fetchOptions) {
163 DatastorePb.Query queryProto = QueryTranslator.convertToPb(q, fetchOptions);
164 if (txn != null) {
165 TransactionImpl.ensureTxnActive(txn);
166 queryProto.setTransaction(BaseDatastoreServiceImpl.localTxnToRemoteTxn(txn));
168 return queryProto;
171 @Override
172 public String toString() {
173 return query.toString() + (txn != null ? " IN " + txn : "");