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
;
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
;
37 checkArgument(query
.getFilter() == null);
38 checkArgument(txn
== null || query
.getAncestor() != null,
39 "Only ancestor queries are allowed inside transactions.");
40 TransactionImpl
.ensureTxnActive(txn
);
44 public List
<Entity
> asList(FetchOptions fetchOptions
) {
45 return new LazyList(runQuery(query
, fetchOptions
));
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
));
59 public Iterator
<Entity
> asIterator(FetchOptions fetchOptions
) {
60 return runQuery(query
, fetchOptions
);
64 public QueryResultIterator
<Entity
> asQueryResultIterator(FetchOptions fetchOptions
) {
65 if (fetchOptions
.getCompile() == null) {
66 fetchOptions
= new FetchOptions(fetchOptions
).compile(true);
68 return runQuery(query
, fetchOptions
);
72 public Entity
asSingleEntity() throws TooManyResultsException
{
73 List
<Entity
> entities
= asList(withLimit(2));
74 if (entities
.isEmpty()) {
76 } else if (entities
.size() != 1) {
77 throw new TooManyResultsException();
79 return entities
.get(0);
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
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
);
103 overrideOptions
.offset(fetchOptions
.getLimit());
106 overrideOptions
.offset(Integer
.MAX_VALUE
);
109 int count
= runQuery(query
, overrideOptions
).getNumSkipped();
110 if (fetchOptions
.getOffset() != null) {
111 if (count
< fetchOptions
.getOffset()) {
114 count
= count
- fetchOptions
.getOffset();
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
) {
132 protected Throwable
convertException(Throwable cause
) {
133 if (cause
instanceof DatastoreNeedIndexException
) {
134 addMissingIndexData(queryProto
, (DatastoreNeedIndexException
) cause
);
140 protected DatastorePb
.QueryResult
wrap(DatastorePb
.QueryResult result
) throws Exception
{
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
);
156 String xml
= mgr
.generateXmlForIndex(index
, IndexSource
.manual
);
157 e
.setMissingIndexDefinitionXml(xml
);
162 private DatastorePb
.Query
convertToPb(Query q
, FetchOptions fetchOptions
) {
163 DatastorePb
.Query queryProto
= QueryTranslator
.convertToPb(q
, fetchOptions
);
165 TransactionImpl
.ensureTxnActive(txn
);
166 queryProto
.setTransaction(BaseDatastoreServiceImpl
.localTxnToRemoteTxn(txn
));
172 public String
toString() {
173 return query
.toString() + (txn
!= null ?
" IN " + txn
: "");