diff --git a/README.md b/README.md index 3c41abbdc..650f21f7c 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ If you are using Maven without BOM, add this to your dependencies: If you are using Gradle 5.x or later, add this to your dependencies: ```Groovy -implementation platform('com.google.cloud:libraries-bom:26.9.0') +implementation platform('com.google.cloud:libraries-bom:26.10.0') implementation 'com.google.cloud:google-cloud-datastore' ``` diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/StructuredQuery.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/StructuredQuery.java index b394dcd97..a6195ad27 100644 --- a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/StructuredQuery.java +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/StructuredQuery.java @@ -26,6 +26,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.api.core.ApiFunction; +import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.cloud.StringEnumType; import com.google.cloud.StringEnumValue; @@ -151,6 +152,8 @@ public Operator apply(String constant) { static final Operator AND = type.createAndRegister("AND"); + static final Operator OR = type.createAndRegister("OR"); + com.google.datastore.v1.CompositeFilter.Operator toPb() { return com.google.datastore.v1.CompositeFilter.Operator.valueOf(name()); } @@ -231,6 +234,11 @@ public static CompositeFilter and(Filter first, Filter... other) { return new CompositeFilter(Operator.AND, first, other); } + @BetaApi + public static CompositeFilter or(Filter first, Filter... other) { + return new CompositeFilter(Operator.OR, first, other); + } + @Override com.google.datastore.v1.Filter toPb() { com.google.datastore.v1.Filter.Builder filterPb = com.google.datastore.v1.Filter.newBuilder(); diff --git a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/SerializationTest.java b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/SerializationTest.java index 105386515..1b8186a54 100644 --- a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/SerializationTest.java +++ b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/SerializationTest.java @@ -69,6 +69,19 @@ public class SerializationTest extends BaseSerializationTest { .addDistinctOn("p") .addOrderBy(OrderBy.asc("p")) .build(); + private static final Query QUERY4 = + Query.newProjectionEntityQueryBuilder() + .setKind("k") + .setNamespace("ns1") + .addProjection("p") + .setLimit(100) + .setOffset(5) + .setStartCursor(CURSOR1) + .setEndCursor(CURSOR2) + .setFilter(CompositeFilter.or(PropertyFilter.gt("p1", 10), PropertyFilter.eq("a", "v"))) + .addDistinctOn("p") + .addOrderBy(OrderBy.asc("p")) + .build(); private static final KeyValue KEY_VALUE = KeyValue.of(KEY1); private static final NullValue NULL_VALUE = NullValue.newBuilder().setExcludeFromIndexes(true).build(); @@ -136,6 +149,7 @@ protected java.io.Serializable[] serializableObjects() { QUERY1, QUERY2, QUERY3, + QUERY4, NULL_VALUE, KEY_VALUE, STRING_VALUE, diff --git a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/StructuredQueryTest.java b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/StructuredQueryTest.java index b3f4b944a..c59337586 100644 --- a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/StructuredQueryTest.java +++ b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/StructuredQueryTest.java @@ -37,8 +37,10 @@ public class StructuredQueryTest { 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 = + private static final Filter AND_FILTER = CompositeFilter.and(PropertyFilter.gt("p1", 10), PropertyFilter.eq("a", "v")); + private static final Filter OR_FILTER = + CompositeFilter.or(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); @@ -56,7 +58,7 @@ public class StructuredQueryTest { .setEndCursor(END_CURSOR) .setOffset(OFFSET) .setLimit(LIMIT) - .setFilter(FILTER) + .setFilter(AND_FILTER) .setOrderBy(ORDER_BY_1, ORDER_BY_2) .build(); private static final KeyQuery KEY_QUERY = @@ -67,7 +69,7 @@ public class StructuredQueryTest { .setEndCursor(END_CURSOR) .setOffset(OFFSET) .setLimit(LIMIT) - .setFilter(FILTER) + .setFilter(OR_FILTER) .setOrderBy(ORDER_BY_1, ORDER_BY_2) .build(); private static final ProjectionEntityQuery PROJECTION_QUERY = @@ -78,7 +80,7 @@ public class StructuredQueryTest { .setEndCursor(END_CURSOR) .setOffset(OFFSET) .setLimit(LIMIT) - .setFilter(FILTER) + .setFilter(AND_FILTER) .setOrderBy(ORDER_BY_1, ORDER_BY_2) .setProjection(PROJECTION1, PROJECTION2) .setDistinctOn(DISTINCT_ON1, DISTINCT_ON2) @@ -93,7 +95,14 @@ public void testEntityQueryBuilder() { @Test public void testKeyQueryBuilder() { - compareBaseBuilderFields(KEY_QUERY); + assertEquals(NAMESPACE, KEY_QUERY.getNamespace()); + assertEquals(KIND, KEY_QUERY.getKind()); + assertEquals(START_CURSOR, KEY_QUERY.getStartCursor()); + assertEquals(END_CURSOR, KEY_QUERY.getEndCursor()); + assertEquals(OFFSET, KEY_QUERY.getOffset()); + assertEquals(LIMIT, KEY_QUERY.getLimit()); + assertEquals(OR_FILTER, KEY_QUERY.getFilter()); + assertEquals(ORDER_BY, KEY_QUERY.getOrderBy()); assertEquals(ImmutableList.of(StructuredQuery.KEY_PROPERTY_NAME), KEY_QUERY.getProjection()); assertTrue(KEY_QUERY.getDistinctOn().isEmpty()); } @@ -112,7 +121,7 @@ private void compareBaseBuilderFields(StructuredQuery query) { assertEquals(END_CURSOR, query.getEndCursor()); assertEquals(OFFSET, query.getOffset()); assertEquals(LIMIT, query.getLimit()); - assertEquals(FILTER, query.getFilter()); + assertEquals(AND_FILTER, query.getFilter()); assertEquals(ORDER_BY, query.getOrderBy()); } diff --git a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreTest.java b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreTest.java index b8c3bb4b6..f010d8135 100644 --- a/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreTest.java +++ b/google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreTest.java @@ -59,6 +59,7 @@ import com.google.cloud.datastore.ReadOption; import com.google.cloud.datastore.StringValue; import com.google.cloud.datastore.StructuredQuery; +import com.google.cloud.datastore.StructuredQuery.CompositeFilter; import com.google.cloud.datastore.StructuredQuery.OrderBy; import com.google.cloud.datastore.StructuredQuery.PropertyFilter; import com.google.cloud.datastore.TimestampValue; @@ -223,6 +224,85 @@ private List makeResultsCopy(QueryResults scResults) { return results; } + @Test + public void orQuery() { + Key key = Key.newBuilder(KEY1, KIND2, 2).build(); + Entity entity3 = + Entity.newBuilder(ENTITY1) + .setKey(key) + .remove("str") + .set("name", "Dan") + .setNull("null") + .set("age", 19) + .build(); + DATASTORE.put(entity3); + + // age == 19 || age == 20 + CompositeFilter orFilter = + CompositeFilter.or(PropertyFilter.eq("age", 19), PropertyFilter.eq("age", 20)); + Query simpleOrQuery = + Query.newEntityQueryBuilder() + .setNamespace(NAMESPACE) + .setKind(KIND2) + .setFilter(orFilter) + .build(); + QueryResults results = DATASTORE.run(simpleOrQuery); + assertTrue(results.hasNext()); + assertEquals(ENTITY2, results.next()); + assertTrue(results.hasNext()); + assertEquals(entity3, results.next()); + assertFalse(results.hasNext()); + + // age == 19 || age == 20 with limit of 1 + Query simpleOrQueryLimit = + Query.newEntityQueryBuilder() + .setNamespace(NAMESPACE) + .setKind(KIND2) + .setFilter(orFilter) + .setLimit(1) + .build(); + QueryResults results2 = DATASTORE.run(simpleOrQueryLimit); + assertTrue(results2.hasNext()); + assertEquals(ENTITY2, results2.next()); + assertFalse(results2.hasNext()); + + // (age == 18 && name == Dan) || (age == 20 && name == Dan) + CompositeFilter nestedOr = + CompositeFilter.or( + CompositeFilter.and(PropertyFilter.eq("age", 18), PropertyFilter.eq("name", "Dan")), + CompositeFilter.and(PropertyFilter.eq("age", 20), PropertyFilter.eq("name", "Dan"))); + CompositeFilter compositeFilter = + CompositeFilter.and(PropertyFilter.hasAncestor(ROOT_KEY), nestedOr); + Query orQueryNested = + Query.newEntityQueryBuilder() + .setNamespace(NAMESPACE) + .setKind(KIND2) + .setFilter(compositeFilter) + .build(); + QueryResults results3 = DATASTORE.run(orQueryNested); + assertTrue(results3.hasNext()); + assertEquals(ENTITY2, results3.next()); + assertFalse(results3.hasNext()); + + // age == 20 && (name == Bob || name == Dan) + CompositeFilter nestedOr2 = + CompositeFilter.or(PropertyFilter.eq("name", "Dan"), PropertyFilter.eq("name", "Bob")); + CompositeFilter andFilter = CompositeFilter.and(PropertyFilter.eq("age", 20), nestedOr2); + CompositeFilter ancestorAndFilter = + CompositeFilter.and(PropertyFilter.hasAncestor(ROOT_KEY), andFilter); + Query orQueryNested2 = + Query.newEntityQueryBuilder() + .setNamespace(NAMESPACE) + .setKind(KIND2) + .setFilter(ancestorAndFilter) + .setLimit(1) + .build(); + QueryResults results4 = DATASTORE.run(orQueryNested2); + assertTrue(results4.hasNext()); + assertEquals(ENTITY2, results4.next()); + assertFalse(results4.hasNext()); + } + @Test public void testNewTransactionCommit() { Transaction transaction = DATASTORE.newTransaction(); @@ -946,6 +1026,21 @@ public void testInNotInNeqFilters() throws InterruptedException { assertEquals(e2, resultNeq.next()); assertFalse(resultNeq.hasNext()); + Query scQueryInEqOr = + Query.newEntityQueryBuilder() + .setKind(KIND1) + .setFilter( + CompositeFilter.or( + PropertyFilter.in("v_int", ListValue.of(10, 50000)), + PropertyFilter.eq("v_int", 10000))) + .build(); + + QueryResults run = DATASTORE.run(scQueryInEqOr); + + assertTrue(run.hasNext()); + assertEquals(e1, run.next()); + assertFalse(run.hasNext()); + DATASTORE.delete(e1.getKey()); DATASTORE.delete(e2.getKey()); }