1 package com
.google
.appengine
.api
.datastore
;
3 import static com
.google
.common
.base
.Preconditions
.checkArgument
;
5 import com
.google
.apphosting
.datastore
.DatastoreV3Pb
;
6 import com
.google
.apphosting
.datastore
.DatastoreV3Pb
.Query
.Filter
;
7 import com
.google
.apphosting
.datastore
.DatastoreV3Pb
.Query
.Filter
.Operator
;
8 import com
.google
.apphosting
.datastore
.DatastoreV3Pb
.Query
.Order
;
9 import com
.google
.apphosting
.datastore
.DatastoreV3Pb
.Query
.Order
.Direction
;
10 import com
.google
.storage
.onestore
.v3
.OnestoreEntity
.PropertyValue
;
11 import com
.google
.storage
.onestore
.v3
.OnestoreEntity
.Reference
;
13 import java
.util
.List
;
16 * {@code QueryTranslator} contains the logic to translate a {@code
17 * Query} into the protocol buffers that are used to pass it to the
18 * implementation of the API.
20 final class QueryTranslator
{
22 public static DatastoreV3Pb
.Query
convertToPb(Query query
, FetchOptions fetchOptions
) {
23 Key ancestor
= query
.getAncestor();
24 List
<Query
.SortPredicate
> sortPredicates
= query
.getSortPredicates();
26 DatastoreV3Pb
.Query proto
= new DatastoreV3Pb
.Query();
28 if (query
.getKind() != null) {
29 proto
.setKind(query
.getKind());
32 proto
.setApp(query
.getAppIdNamespace().getAppId());
33 String nameSpace
= query
.getAppIdNamespace().getNamespace();
34 if (nameSpace
.length() != 0) {
35 proto
.setNameSpace(nameSpace
);
38 if (fetchOptions
.getOffset() != null) {
39 proto
.setOffset(fetchOptions
.getOffset());
42 if (fetchOptions
.getLimit() != null) {
43 proto
.setLimit(fetchOptions
.getLimit());
46 if (fetchOptions
.getPrefetchSize() != null) {
47 proto
.setCount(fetchOptions
.getPrefetchSize());
48 } else if (fetchOptions
.getChunkSize() != null) {
49 proto
.setCount(fetchOptions
.getChunkSize());
52 if (fetchOptions
.getStartCursor() != null) {
53 if (!proto
.getMutableCompiledCursor().parseFrom(
54 fetchOptions
.getStartCursor().toByteString())) {
55 throw new IllegalArgumentException("Invalid cursor");
59 if (fetchOptions
.getEndCursor() != null) {
60 if (!proto
.getMutableEndCompiledCursor().parseFrom(
61 fetchOptions
.getEndCursor().toByteString())) {
62 throw new IllegalArgumentException("Invalid cursor");
66 if (fetchOptions
.getCompile() != null) {
67 proto
.setCompile(fetchOptions
.getCompile());
70 if (ancestor
!= null) {
71 Reference ref
= KeyTranslator
.convertToPb(ancestor
);
72 if (!ref
.getApp().equals(proto
.getApp())) {
73 throw new IllegalArgumentException("Query and ancestor appid/namespace mismatch");
75 proto
.setAncestor(ref
);
78 if (query
.getDistinct()) {
79 if (query
.getProjections().isEmpty()) {
80 throw new IllegalArgumentException("Projected properties must be set to " +
81 "allow for distinct projections");
83 for (Projection projection
: query
.getProjections()) {
84 proto
.addGroupByPropertyName(projection
.getPropertyName());
88 proto
.setKeysOnly(query
.isKeysOnly());
90 Query
.Filter filter
= query
.getFilter();
92 copyGeoFilterToPb(filter
, proto
);
94 for (Query
.FilterPredicate filterPredicate
: query
.getFilterPredicates()) {
95 Filter filterPb
= proto
.addFilter();
96 filterPb
.copyFrom(convertFilterPredicateToPb(filterPredicate
));
100 for (Query
.SortPredicate sortPredicate
: sortPredicates
) {
101 Order order
= proto
.addOrder();
102 order
.copyFrom(convertSortPredicateToPb(sortPredicate
));
105 for (Projection projection
: query
.getProjections()) {
106 proto
.addPropertyName(projection
.getPropertyName());
112 static Order
convertSortPredicateToPb(Query
.SortPredicate predicate
) {
113 Order order
= new Order();
114 order
.setProperty(predicate
.getPropertyName());
115 order
.setDirection(getSortOp(predicate
.getDirection()));
119 private static Direction
getSortOp(Query
.SortDirection direction
) {
122 return Direction
.ASCENDING
;
124 return Direction
.DESCENDING
;
126 throw new UnsupportedOperationException("direction: " + direction
);
131 * Converts the filter from a geo-spatial query into proto-buf form.
132 * Should only be called when the filter indeed has a geo-spatial
133 * term; but the filter as a whole has not yet been entirely
134 * validated, so we complete the validation here.
136 private static void copyGeoFilterToPb(Query
.Filter filter
, DatastoreV3Pb
.Query proto
) {
137 if (filter
instanceof Query
.CompositeFilter
) {
138 Query
.CompositeFilter conjunction
= (Query
.CompositeFilter
) filter
;
139 checkArgument(conjunction
.getOperator() == Query
.CompositeFilterOperator
.AND
,
140 "Geo-spatial filters may only be composed with CompositeFilterOperator.AND");
141 for (Query
.Filter f
: conjunction
.getSubFilters()) {
142 copyGeoFilterToPb(f
, proto
);
144 } else if (filter
instanceof Query
.StContainsFilter
) {
145 Query
.StContainsFilter containmentFilter
= (Query
.StContainsFilter
) filter
;
146 Filter f
= proto
.addFilter();
147 f
.setOp(Operator
.CONTAINED_IN_REGION
);
148 f
.setGeoRegion(convertGeoRegionToPb(containmentFilter
.getRegion()));
150 .setName(containmentFilter
.getPropertyName())
152 .setValue(new PropertyValue());
154 checkArgument(filter
instanceof Query
.FilterPredicate
);
155 Query
.FilterPredicate predicate
= (Query
.FilterPredicate
) filter
;
156 checkArgument(predicate
.getOperator() == Query
.FilterOperator
.EQUAL
,
157 "Geo-spatial filters may only be combined with equality comparisons");
158 Filter f
= proto
.addFilter();
159 f
.copyFrom(convertFilterPredicateToPb(predicate
));
163 private static Filter
convertFilterPredicateToPb(Query
.FilterPredicate predicate
) {
164 Filter filterPb
= new Filter();
165 filterPb
.setOp(getFilterOp(predicate
.getOperator()));
167 if (predicate
.getValue() instanceof Iterable
<?
>) {
168 if (predicate
.getOperator() != Query
.FilterOperator
.IN
) {
169 throw new IllegalArgumentException("Only the IN operator supports multiple values");
171 for (Object value
: (Iterable
<?
>) predicate
.getValue()) {
172 filterPb
.addProperty()
173 .setName(predicate
.getPropertyName())
174 .setValue(DataTypeTranslator
.toV3Value(value
));
177 filterPb
.addProperty()
178 .setName(predicate
.getPropertyName())
179 .setValue(DataTypeTranslator
.toV3Value(predicate
.getValue()));
185 private static DatastoreV3Pb
.GeoRegion
convertGeoRegionToPb(Query
.GeoRegion region
) {
186 DatastoreV3Pb
.GeoRegion geoRegion
= new DatastoreV3Pb
.GeoRegion();
187 if (region
instanceof Query
.GeoRegion
.Circle
) {
188 Query
.GeoRegion
.Circle circle
= (Query
.GeoRegion
.Circle
) region
;
189 DatastoreV3Pb
.CircleRegion circlePb
= new DatastoreV3Pb
.CircleRegion();
190 circlePb
.setCenter(convertGeoPtToPb(circle
.getCenter()));
191 circlePb
.setRadiusMeters(circle
.getRadius());
192 geoRegion
.setCircle(circlePb
);
193 } else if (region
instanceof Query
.GeoRegion
.Rectangle
) {
194 Query
.GeoRegion
.Rectangle rect
= (Query
.GeoRegion
.Rectangle
) region
;
195 DatastoreV3Pb
.RectangleRegion rectPb
= new DatastoreV3Pb
.RectangleRegion();
196 rectPb
.setSouthwest(convertGeoPtToPb(rect
.getSouthwest()));
197 rectPb
.setNortheast(convertGeoPtToPb(rect
.getNortheast()));
198 geoRegion
.setRectangle(rectPb
);
200 throw new IllegalArgumentException("missing or unknown-type region in StContainsFilter");
205 private static DatastoreV3Pb
.RegionPoint
convertGeoPtToPb(GeoPt point
) {
206 DatastoreV3Pb
.RegionPoint pointPb
= new DatastoreV3Pb
.RegionPoint();
207 pointPb
.setLatitude(point
.getLatitude());
208 pointPb
.setLongitude(point
.getLongitude());
212 private static Operator
getFilterOp(Query
.FilterOperator operator
) {
215 return Operator
.LESS_THAN
;
216 case LESS_THAN_OR_EQUAL
:
217 return Operator
.LESS_THAN_OR_EQUAL
;
219 return Operator
.GREATER_THAN
;
220 case GREATER_THAN_OR_EQUAL
:
221 return Operator
.GREATER_THAN_OR_EQUAL
;
223 return Operator
.EQUAL
;
227 throw new UnsupportedOperationException("operator: " + operator
);
231 private QueryTranslator() {