Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / datastore / ValidatedQuery.java
blob17859bd447982ad81146a70042535bf17683b48e
1 // Copyright 2008 Google Inc. All Rights Reserved.
2 package com.google.appengine.api.datastore;
4 import com.google.apphosting.api.ApiProxy;
5 import com.google.apphosting.datastore.DatastoreV3Pb;
6 import com.google.apphosting.datastore.DatastoreV3Pb.Query;
7 import com.google.apphosting.datastore.DatastoreV3Pb.Query.Filter;
8 import com.google.apphosting.datastore.DatastoreV3Pb.Query.Filter.Operator;
9 import com.google.apphosting.datastore.DatastoreV3Pb.Query.Order;
10 import com.google.common.collect.Sets;
11 import com.google.storage.onestore.v3.OnestoreEntity.Property;
12 import com.google.storage.onestore.v3.OnestoreEntity.PropertyValue;
13 import com.google.storage.onestore.v3.OnestoreEntity.PropertyValue.ReferenceValue;
15 import java.util.HashSet;
16 import java.util.Set;
18 /**
19 * Wrapper around {@link Query} that performs validation.
22 class ValidatedQuery extends NormalizedQuery {
23 static final Set<Operator> UNSUPPORTED_OPERATORS = makeImmutableSet(
24 Operator.IN);
26 /**
27 * @throws IllegalQueryException If the provided query fails validation.
29 ValidatedQuery(Query query) {
30 super(query);
31 validateQuery();
34 /**
35 * Determines if a given query is supported by the datastore.
37 * @throws IllegalQueryException If the provided query fails validation.
39 private void validateQuery() {
40 if (query.propertyNameSize() > 0 && query.isKeysOnly()) {
41 throw new IllegalQueryException(
42 "projection and keys_only cannot both be set",
43 IllegalQueryType.ILLEGAL_PROJECTION);
46 Set<String> projectionProperties = new HashSet<String>(query.propertyNameSize());
47 for (String property : query.propertyNames()) {
48 if (Entity.RESERVED_NAME.matcher(property).matches()) {
49 throw new IllegalQueryException(
50 "projections are not supported for the property: " + property,
51 IllegalQueryType.ILLEGAL_PROJECTION);
53 if (!projectionProperties.add(property)) {
54 throw new IllegalQueryException(
55 "cannot project a property multiple times",
56 IllegalQueryType.ILLEGAL_PROJECTION);
60 Set<String> groupBySet = Sets.newHashSetWithExpectedSize(query.groupByPropertyNameSize());
61 for (String name : query.groupByPropertyNames()) {
62 if (!groupBySet.add(name)) {
63 throw new IllegalQueryException("cannot group by a property multiple times",
64 IllegalQueryType.ILLEGAL_GROUPBY);
66 if (Entity.RESERVED_NAME.matcher(name).matches()) {
67 throw new IllegalQueryException(
68 "group by is not supported for the property: " + name,
69 IllegalQueryType.ILLEGAL_GROUPBY);
73 Set<String> groupByInOrderSet =
74 Sets.newHashSetWithExpectedSize(query.groupByPropertyNameSize());
75 for (Order order : query.orders()) {
76 if (groupBySet.contains(order.getProperty())) {
77 groupByInOrderSet.add(order.getProperty());
78 } else if (groupByInOrderSet.size() != groupBySet.size()) {
79 throw new IllegalQueryException(
80 "must specify all group by orderings before any non group by orderings",
81 IllegalQueryType.ILLEGAL_GROUPBY);
85 if (query.hasTransaction() && !query.hasAncestor()) {
86 throw new IllegalQueryException(
87 "Only ancestor queries are allowed inside transactions.",
88 IllegalQueryType.TRANSACTION_REQUIRES_ANCESTOR);
91 if (!query.hasKind()) {
92 for (Filter filter : query.filters()) {
93 if (!filter.getProperty(0).getName().equals(Entity.KEY_RESERVED_PROPERTY)) {
94 throw new IllegalQueryException(
95 "kind is required for non-__key__ filters",
96 IllegalQueryType.KIND_REQUIRED);
99 for (Order order : query.orders()) {
100 if (!(order.getProperty().equals(Entity.KEY_RESERVED_PROPERTY) &&
101 order.getDirection() == Order.Direction.ASCENDING.getValue())) {
102 throw new IllegalQueryException(
103 "kind is required for all orders except __key__ ascending",
104 IllegalQueryType.KIND_REQUIRED);
109 String ineqProp = null;
110 for (Filter filter : query.filters()) {
111 int numProps = filter.propertySize();
112 if (numProps != 1) {
113 throw new IllegalQueryException(
114 String.format("Filter has %s properties, expected 1", numProps),
115 IllegalQueryType.FILTER_WITH_MULTIPLE_PROPS);
118 Property prop = filter.getProperty(0);
119 String propName = prop.getName();
121 if (Entity.KEY_RESERVED_PROPERTY.equals(propName)) {
122 PropertyValue value = prop.getValue();
123 if (!value.hasReferenceValue()) {
124 throw new IllegalQueryException(
125 Entity.KEY_RESERVED_PROPERTY + " filter value must be a Key",
126 IllegalQueryType.ILLEGAL_VALUE);
128 ReferenceValue refVal = value.getReferenceValue();
129 if (!refVal.getApp().equals(query.getApp())) {
130 throw new IllegalQueryException(
131 Entity.KEY_RESERVED_PROPERTY + " filter app is " +
132 refVal.getApp() + " but query app is " + query.getApp(),
133 IllegalQueryType.ILLEGAL_VALUE);
135 if (!refVal.getNameSpace().equals(query.getNameSpace())) {
136 throw new IllegalQueryException(
137 Entity.KEY_RESERVED_PROPERTY + " filter namespace is " +
138 refVal.getNameSpace() + " but query namespace is " + query.getNameSpace(),
139 IllegalQueryType.ILLEGAL_VALUE);
143 if (INEQUALITY_OPERATORS.contains(filter.getOpEnum())) {
144 if (ineqProp == null) {
145 ineqProp = propName;
146 } else if (!ineqProp.equals(propName)) {
147 throw new IllegalQueryException(
148 String.format("Only one inequality filter per query is supported. "
149 + "Encountered both %s and %s", ineqProp, propName),
150 IllegalQueryType.MULTIPLE_INEQ_FILTERS);
152 } else if (filter.getOpEnum() == Operator.EQUAL) {
153 if (projectionProperties.contains(propName)) {
154 throw new IllegalQueryException(
155 "cannot use projection on a property with an equality filter",
156 IllegalQueryType.ILLEGAL_PROJECTION);
157 } else if (groupBySet.contains(propName)) {
158 throw new IllegalQueryException(
159 "cannot use group by on a property with an equality filter",
160 IllegalQueryType.ILLEGAL_GROUPBY);
162 } else if (UNSUPPORTED_OPERATORS.contains(filter.getOpEnum())) {
163 throw new IllegalQueryException(
164 String.format("Unsupported filter operator: %s", filter.getOp()),
165 IllegalQueryType.UNSUPPORTED_FILTER);
169 if (ineqProp != null && query.groupByPropertyNameSize() > 0) {
170 if (!groupBySet.contains(ineqProp)) {
171 throw new IllegalQueryException(
172 String.format("Inequality filter on %s must also be a group by property when "
173 + "group by properties are set.", ineqProp), IllegalQueryType.ILLEGAL_GROUPBY);
177 if (ineqProp != null) {
178 if (query.orderSize() > 0) {
179 if (!ineqProp.equals(query.getOrder(0).getProperty())) {
180 throw new IllegalQueryException(
181 String.format("The first sort property must be the same as the property to which "
182 + "the inequality filter is applied. In your query the first sort property is "
183 + "%s but the inequality filter is on %s",
184 query.getOrder(0).getProperty(), ineqProp),
185 IllegalQueryType.FIRST_SORT_NEQ_INEQ_PROP);
191 @Override
192 public boolean equals(Object o) {
193 if (this == o) {
194 return true;
196 if (o == null || getClass() != o.getClass()) {
197 return false;
200 ValidatedQuery that = (ValidatedQuery) o;
202 if (!query.equals(that.query)) {
203 return false;
206 return true;
209 @Override
210 public int hashCode() {
211 return query.hashCode();
214 enum IllegalQueryType {
215 KIND_REQUIRED,
216 UNSUPPORTED_FILTER,
217 FILTER_WITH_MULTIPLE_PROPS,
218 MULTIPLE_INEQ_FILTERS,
219 FIRST_SORT_NEQ_INEQ_PROP,
220 TRANSACTION_REQUIRES_ANCESTOR,
221 ILLEGAL_VALUE,
222 ILLEGAL_PROJECTION,
223 ILLEGAL_GROUPBY,
226 static class IllegalQueryException extends ApiProxy.ApplicationException {
228 private final IllegalQueryType illegalQueryType;
230 IllegalQueryException(String errorDetail,
231 IllegalQueryType illegalQueryType) {
232 super(DatastoreV3Pb.Error.ErrorCode.BAD_REQUEST.getValue(), errorDetail);
233 this.illegalQueryType = illegalQueryType;
236 IllegalQueryType getIllegalQueryType() {
237 return illegalQueryType;