diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/EntityQuery.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/EntityQuery.java new file mode 100644 index 000000000000..902168f20f48 --- /dev/null +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/EntityQuery.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.datastore; + +import com.google.api.services.datastore.DatastoreV1; + +/** + * An implementation of a Google Cloud Datastore entity query that can be constructed by providing + * all the specific query elements. + * + * @see Datastore + * queries + */ +public final class EntityQuery extends StructuredQuery { + + private static final long serialVersionUID = 2990565454831019471L; + + /** + * A {@code EntityQuery} builder for queries that return {@link Entity} results. + */ + public static final class Builder extends StructuredQuery.BuilderImpl { + + Builder(EntityQuery query) { + super(query); + } + + Builder() { + super(ResultType.ENTITY); + } + + @Override + Builder mergeFrom(DatastoreV1.Query queryPb) { + super.mergeFrom(queryPb); + clearProjection(); + clearGroupBy(); + return this; + } + + @Override + public EntityQuery build() { + return new EntityQuery(this); + } + } + + EntityQuery(Builder builder) { + super(builder); + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } +} diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/KeyQuery.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/KeyQuery.java new file mode 100644 index 000000000000..7afa0f5099d6 --- /dev/null +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/KeyQuery.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.datastore; + +import com.google.api.services.datastore.DatastoreV1; + +/** + * An implementation of a Google Cloud Datastore key-only query that can be constructed by providing + * all the specific query elements. + * + * @see Datastore + * queries + */ +public final class KeyQuery extends StructuredQuery { + + private static final long serialVersionUID = -746768461459070045L; + + /** + * A {@code KeyQuery} builder for queries that return {@link Key} results. + */ + public static final class Builder extends StructuredQuery.BuilderImpl { + + Builder(KeyQuery query) { + super(query); + } + + Builder() { + super(ResultType.KEY); + projection(Projection.property(KEY_PROPERTY_NAME)); + } + + @Override + Builder mergeFrom(DatastoreV1.Query queryPb) { + super.mergeFrom(queryPb); + projection(Projection.property(KEY_PROPERTY_NAME)); + clearGroupBy(); + return this; + } + + @Override + public KeyQuery build() { + return new KeyQuery(this); + } + } + + KeyQuery(Builder builder) { + super(builder); + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } +} diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/ProjectionEntityQuery.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/ProjectionEntityQuery.java new file mode 100644 index 000000000000..bad9fc5af2d0 --- /dev/null +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/ProjectionEntityQuery.java @@ -0,0 +1,112 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.datastore; + +/** + * An implementation of a Google Cloud Datastore projection entity query that can be constructed by + * providing all the specific query elements. + * + * @see Datastore + * queries + */ +public final class ProjectionEntityQuery extends StructuredQuery { + + private static final long serialVersionUID = 5488451194542425391L; + + /** + * A {@code ProjectionEntityQuery} builder for queries that return {@link ProjectionEntity} + * results. + */ + public static final class Builder extends StructuredQuery.BuilderImpl { + + Builder(ProjectionEntityQuery query) { + super(query); + } + + Builder() { + super(ResultType.PROJECTION_ENTITY); + } + + /** + * Clears the projection clause. + */ + @Override + public Builder clearProjection() { + super.clearProjection(); + return this; + } + + /** + * Sets the query's projection clause (clearing any previously specified Projection settings). + */ + @Override + public Builder projection(Projection projection, Projection... others) { + super.projection(projection, others); + return this; + } + + /** + * Adds one or more projections to the existing projection clause. + */ + @Override + public Builder addProjection(Projection projection, Projection... others) { + super.addProjection(projection, others); + return this; + } + + /** + * Clears the group by clause. + */ + @Override + public Builder clearGroupBy() { + super.clearGroupBy(); + return this; + } + + /** + * Sets the query's group by clause (clearing any previously specified GroupBy settings). + */ + @Override + public Builder groupBy(String property, String... others) { + super.groupBy(property, others); + return this; + } + + /** + * Adds one or more properties to the existing group by clause. + */ + @Override + public Builder addGroupBy(String property, String... others) { + super.addGroupBy(property, others); + return this; + } + + @Override + public ProjectionEntityQuery build() { + return new ProjectionEntityQuery(this); + } + } + + ProjectionEntityQuery(Builder builder) { + super(builder); + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } +} diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Query.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Query.java index 0dbd1633928e..50591a87a6a4 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Query.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Query.java @@ -22,9 +22,6 @@ import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.collect.Maps; -import com.google.gcloud.datastore.StructuredQuery.EntityQueryBuilder; -import com.google.gcloud.datastore.StructuredQuery.KeyQueryBuilder; -import com.google.gcloud.datastore.StructuredQuery.ProjectionEntityQueryBuilder; import com.google.protobuf.GeneratedMessage; import com.google.protobuf.InvalidProtocolBufferException; @@ -217,21 +214,21 @@ public static GqlQuery.Builder gqlQueryBuilder(ResultType resultType, /** * Returns a new {@link StructuredQuery} builder for full (complete entities) queries. */ - public static EntityQueryBuilder entityQueryBuilder() { - return new EntityQueryBuilder(); + public static EntityQuery.Builder entityQueryBuilder() { + return new EntityQuery.Builder(); } /** * Returns a new {@link StructuredQuery} builder for key only queries. */ - public static KeyQueryBuilder keyQueryBuilder() { - return new KeyQueryBuilder(); + public static KeyQuery.Builder keyQueryBuilder() { + return new KeyQuery.Builder(); } /** * Returns a new {@link StructuredQuery} builder for projection queries. */ - public static ProjectionEntityQueryBuilder projectionEntityQueryBuilder() { - return new ProjectionEntityQueryBuilder(); + public static ProjectionEntityQuery.Builder projectionEntityQueryBuilder() { + return new ProjectionEntityQuery.Builder(); } } diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/StructuredQuery.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/StructuredQuery.java index b8d9dfe87902..15cca241e250 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/StructuredQuery.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/StructuredQuery.java @@ -81,10 +81,10 @@ * @see Datastore * queries */ -public class StructuredQuery extends Query { +public abstract class StructuredQuery extends Query { private static final long serialVersionUID = 546838955624019594L; - private static final String KEY_PROPERTY_NAME = "__key__"; + static final String KEY_PROPERTY_NAME = "__key__"; private final transient String kind; private final ImmutableList projection; @@ -609,13 +609,48 @@ public static Projection first(String property) { } } + /** + * Interface for StructuredQuery builders. + * + * @param the type of result the query returns. + */ + public interface Builder { + Builder namespace(String namespace); + + Builder kind(String kind); + + Builder startCursor(Cursor startCursor); + + Builder endCursor(Cursor endCursor); + + Builder offset(int offset); + + Builder limit(Integer limit); + + Builder filter(Filter filter); + + Builder clearOrderBy(); + + /** + * Sets the query's order by clause (clearing any previously specified OrderBy settings). + */ + Builder orderBy(OrderBy orderBy, OrderBy... others); + + /** + * Adds settings to the existing order by clause. + */ + Builder addOrderBy(OrderBy orderBy, OrderBy... others); + + StructuredQuery build(); + } + /** * Base class for StructuredQuery builders. * * @param the type of result the query returns. * @param the query builder. */ - protected static class BaseBuilder> { + abstract static class BuilderImpl> implements Builder { private final ResultType resultType; private String namespace; @@ -629,12 +664,12 @@ protected static class BaseBuilder> { private int offset; private Integer limit; - BaseBuilder(ResultType resultType) { + BuilderImpl(ResultType resultType) { this.resultType = resultType; } - BaseBuilder(StructuredQuery query) { - resultType = query.type(); + BuilderImpl(StructuredQuery query) { + this(query.type()); namespace = query.namespace(); kind = query.kind; projection.addAll(query.projection); @@ -652,60 +687,64 @@ B self() { return (B) this; } + @Override public B namespace(String namespace) { this.namespace = namespace; return self(); } + @Override public B kind(String kind) { this.kind = kind; return self(); } + @Override public B startCursor(Cursor startCursor) { this.startCursor = startCursor; return self(); } + @Override public B endCursor(Cursor endCursor) { this.endCursor = endCursor; return self(); } + @Override public B offset(int offset) { Preconditions.checkArgument(offset >= 0, "offset must not be negative"); this.offset = offset; return self(); } + @Override public B limit(Integer limit) { Preconditions.checkArgument(limit == null || limit > 0, "limit must be positive"); this.limit = limit; return self(); } + @Override public B filter(Filter filter) { this.filter = filter; return self(); } + @Override public B clearOrderBy() { orderBy.clear(); return self(); } - /** - * Sets the query's order by clause (clearing any previously specified OrderBy settings). - */ + @Override public B orderBy(OrderBy orderBy, OrderBy... others) { clearOrderBy(); addOrderBy(orderBy, others); return self(); } - /** - * Adds settings to the existing order by clause. - */ + @Override public B addOrderBy(OrderBy orderBy, OrderBy... others) { this.orderBy.add(orderBy); Collections.addAll(this.orderBy, others); @@ -776,121 +815,9 @@ B mergeFrom(DatastoreV1.Query queryPb) { } return self(); } - - public StructuredQuery build() { - return new StructuredQuery<>(this); - } - } - - public static final class Builder extends BaseBuilder> { - - Builder(ResultType resultType) { - super(resultType); - } - - Builder(StructuredQuery query) { - super(query); - } - } - - /** - * A StructuredQuery builder for queries that return Entity results. - */ - public static final class EntityQueryBuilder extends BaseBuilder { - - EntityQueryBuilder() { - super(ResultType.ENTITY); - } - - @Override - public StructuredQuery build() { - return new StructuredQuery<>(this); - } - } - - /** - * A StructuredQuery builder for queries that return Key results. - */ - public static final class KeyQueryBuilder extends BaseBuilder { - - KeyQueryBuilder() { - super(ResultType.KEY); - projection(Projection.property(KEY_PROPERTY_NAME)); - } - - @Override - KeyQueryBuilder mergeFrom(DatastoreV1.Query queryPb) { - super.mergeFrom(queryPb); - projection(Projection.property(KEY_PROPERTY_NAME)); - clearGroupBy(); - return this; - } - - @Override - public StructuredQuery build() { - return new StructuredQuery<>(this); - } - } - - /** - * A StructuredQuery builder for projection queries. - */ - public static final class ProjectionEntityQueryBuilder - extends BaseBuilder { - - ProjectionEntityQueryBuilder() { - super(ResultType.PROJECTION_ENTITY); - } - - @Override - public StructuredQuery build() { - return new StructuredQuery<>(this); - } - - @Override - public ProjectionEntityQueryBuilder clearProjection() { - return super.clearProjection(); - } - - /** - * Sets the query's projection clause (clearing any previously specified Projection settings). - */ - @Override - public ProjectionEntityQueryBuilder projection(Projection projection, Projection... others) { - return super.projection(projection, others); - } - - /** - * Adds one or more projections to the existing projection clause. - */ - @Override - public ProjectionEntityQueryBuilder addProjection(Projection projection, Projection... others) { - return super.addProjection(projection, others); - } - - @Override - public ProjectionEntityQueryBuilder clearGroupBy() { - return super.clearGroupBy(); - } - - /** - * Sets the query's group by clause (clearing any previously specified GroupBy settings). - */ - @Override - public ProjectionEntityQueryBuilder groupBy(String property, String... others) { - return super.groupBy(property, others); - } - - /** - * Adds one or more properties to the existing group by clause. - */ - @Override - public ProjectionEntityQueryBuilder addGroupBy(String property, String... others) { - return super.addGroupBy(property, others); - } } - StructuredQuery(BaseBuilder builder) { + StructuredQuery(BuilderImpl builder) { super(builder.resultType, builder.namespace); kind = builder.kind; projection = ImmutableList.copyOf(builder.projection); @@ -971,9 +898,7 @@ public Integer limit() { return limit; } - public Builder toBuilder() { - return new Builder<>(this); - } + public abstract Builder toBuilder(); @Override void populatePb(DatastoreV1.RunQueryRequest.Builder requestPb) { @@ -982,8 +907,7 @@ void populatePb(DatastoreV1.RunQueryRequest.Builder requestPb) { @Override StructuredQuery nextQuery(DatastoreV1.QueryResultBatch responsePb) { - Builder builder = new Builder<>(type()); - builder.mergeFrom(toPb()); + Builder builder = toBuilder(); builder.startCursor(new Cursor(responsePb.getEndCursor())); if (offset > 0 && responsePb.getSkippedResults() < offset) { builder.offset(offset - responsePb.getSkippedResults()); @@ -1035,15 +959,15 @@ Object fromPb(ResultType resultType, String namespace, byte[] bytesPb) return fromPb(resultType, namespace, DatastoreV1.Query.parseFrom(bytesPb)); } - private static StructuredQuery fromPb(ResultType resultType, String namespace, + static StructuredQuery fromPb(ResultType resultType, String namespace, DatastoreV1.Query queryPb) { - BaseBuilder builder; + BuilderImpl builder; if (resultType.equals(ResultType.ENTITY)) { - builder = new EntityQueryBuilder(); + builder = new EntityQuery.Builder(); } else if (resultType.equals(ResultType.KEY)) { - builder = new KeyQueryBuilder(); + builder = new KeyQuery.Builder(); } else { - builder = new ProjectionEntityQueryBuilder(); + builder = new ProjectionEntityQuery.Builder(); } return builder.namespace(namespace).mergeFrom(queryPb).build(); } diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/StructuredQueryTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/StructuredQueryTest.java new file mode 100644 index 000000000000..b0d188cae16e --- /dev/null +++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/StructuredQueryTest.java @@ -0,0 +1,171 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.datastore; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.gcloud.datastore.Query.ResultType; +import com.google.gcloud.datastore.StructuredQuery.CompositeFilter; +import com.google.gcloud.datastore.StructuredQuery.Filter; +import com.google.gcloud.datastore.StructuredQuery.OrderBy; +import com.google.gcloud.datastore.StructuredQuery.Projection; +import com.google.gcloud.datastore.StructuredQuery.PropertyFilter; + +import org.junit.Test; + +import java.util.List; + +public class StructuredQueryTest { + + private static final String NAMESPACE = "ns"; + private static final String KIND = "k"; + private static final Cursor START_CURSOR = Cursor.copyFrom(new byte[] {1, 2}); + private static final Cursor END_CURSOR = Cursor.copyFrom(new byte[] {10}); + private static final int OFFSET = 42; + private static final Integer LIMIT = 43; + private static final Filter FILTER = CompositeFilter.and(PropertyFilter.gt("p1", 10), PropertyFilter.eq("a", "v")); + private static final OrderBy ORDER_BY_1 = OrderBy.asc("p2"); + private static final OrderBy ORDER_BY_2 = OrderBy.desc("p3"); + private static final List ORDER_BY = ImmutableList.of(ORDER_BY_1, ORDER_BY_2); + private static final Projection PROJECTION1 = Projection.property("p4"); + private static final Projection PROJECTION2 = Projection.property("p5"); + private static final List PROJECTION = ImmutableList.of(PROJECTION1, PROJECTION2); + private static final String GROUP_BY1 = "p6"; + private static final String GROUP_BY2 = "p7"; + private static final List GROUP_BY = ImmutableList.of(GROUP_BY1, GROUP_BY2); + private static final EntityQuery ENTITY_QUERY = Query.entityQueryBuilder() + .namespace(NAMESPACE) + .kind(KIND) + .startCursor(START_CURSOR) + .endCursor(END_CURSOR) + .offset(OFFSET) + .limit(LIMIT) + .filter(FILTER) + .orderBy(ORDER_BY_1, ORDER_BY_2) + .build(); + private static final KeyQuery KEY_QUERY = Query.keyQueryBuilder() + .namespace(NAMESPACE) + .kind(KIND) + .startCursor(START_CURSOR) + .endCursor(END_CURSOR) + .offset(OFFSET) + .limit(LIMIT) + .filter(FILTER) + .orderBy(ORDER_BY_1, ORDER_BY_2) + .build(); + private static final ProjectionEntityQuery PROJECTION_QUERY = + Query.projectionEntityQueryBuilder() + .namespace(NAMESPACE) + .kind(KIND) + .startCursor(START_CURSOR) + .endCursor(END_CURSOR) + .offset(OFFSET) + .limit(LIMIT) + .filter(FILTER) + .orderBy(ORDER_BY_1, ORDER_BY_2) + .projection(PROJECTION1, PROJECTION2) + .groupBy(GROUP_BY1, GROUP_BY2) + .build(); + + @Test + public void testEntityQueryBuilder() { + compareBaseBuilderFields(ENTITY_QUERY); + assertTrue(ENTITY_QUERY.projection().isEmpty()); + assertTrue(ENTITY_QUERY.groupBy().isEmpty()); + } + + @Test + public void testKeyQueryBuilder() { + compareBaseBuilderFields(KEY_QUERY); + assertEquals( + ImmutableList.of(Projection.property(StructuredQuery.KEY_PROPERTY_NAME)), + KEY_QUERY.projection()); + assertTrue(KEY_QUERY.groupBy().isEmpty()); + } + + @Test + public void testProjectionEntityQueryBuilder() { + compareBaseBuilderFields(PROJECTION_QUERY); + assertEquals(PROJECTION, PROJECTION_QUERY.projection()); + assertEquals(GROUP_BY, PROJECTION_QUERY.groupBy()); + } + + private void compareBaseBuilderFields(StructuredQuery query) { + assertEquals(NAMESPACE, query.namespace()); + assertEquals(KIND, query.kind()); + assertEquals(START_CURSOR, query.startCursor()); + assertEquals(END_CURSOR, query.endCursor()); + assertEquals(OFFSET, query.offset()); + assertEquals(LIMIT, query.limit()); + assertEquals(FILTER, query.filter()); + assertEquals(ORDER_BY, query.orderBy()); + } + + @Test + public void mergeFrom() { + compareMergedQuery( + ENTITY_QUERY, new EntityQuery.Builder().mergeFrom(ENTITY_QUERY.toPb()).build()); + compareMergedQuery(KEY_QUERY, new KeyQuery.Builder().mergeFrom(KEY_QUERY.toPb()).build()); + compareMergedQuery( + PROJECTION_QUERY, + new ProjectionEntityQuery.Builder().mergeFrom(PROJECTION_QUERY.toPb()).build()); + } + + private void compareMergedQuery(StructuredQuery expected, StructuredQuery actual) { + assertEquals(expected.kind(), actual.kind()); + assertEquals(expected.startCursor(), actual.startCursor()); + assertEquals(expected.endCursor(), actual.endCursor()); + assertEquals(expected.offset(), actual.offset()); + assertEquals(expected.limit(), actual.limit()); + assertEquals(expected.filter(), actual.filter()); + assertEquals(expected.orderBy(), actual.orderBy()); + assertEquals(expected.projection(), actual.projection()); + assertEquals(expected.groupBy(), actual.groupBy()); + } + + @Test + public void testToAndFromPb() { + assertEquals( + ENTITY_QUERY, + StructuredQuery.fromPb(ResultType.ENTITY, ENTITY_QUERY.namespace(), ENTITY_QUERY.toPb())); + assertEquals( + KEY_QUERY, StructuredQuery.fromPb(ResultType.KEY, KEY_QUERY.namespace(), KEY_QUERY.toPb())); + assertEquals( + PROJECTION_QUERY, + StructuredQuery.fromPb( + ResultType.PROJECTION_ENTITY, PROJECTION_QUERY.namespace(), PROJECTION_QUERY.toPb())); + } + + @Test + public void testToBuilder() { + List> queries = + ImmutableList.>of(ENTITY_QUERY, KEY_QUERY, PROJECTION_QUERY); + for (StructuredQuery query : queries) { + assertEquals(query, query.toBuilder().build()); + } + } + + @Test + public void testKeyOnly() { + assertTrue(KEY_QUERY.keyOnly()); + assertFalse(ENTITY_QUERY.keyOnly()); + assertFalse(PROJECTION_QUERY.keyOnly()); + } +}