Skip to content

Commit 99b7843

Browse files
authored
feat: OR query support (#993)
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [ ] Make sure to open an issue as a [bug/issue](https://togithub.com/googleapis/java-datastore/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass - [ ] Code coverage does not decrease (if any source code was changed) - [ ] Appropriate docs were updated (if necessary) Fixes #<issue_number_goes_here> ☕️ If you write sample code, please follow the [samples format]( https://togithub.com/GoogleCloudPlatform/java-docs-samples/blob/main/SAMPLE_FORMAT.md).
1 parent 3e155e6 commit 99b7843

File tree

5 files changed

+133
-7
lines changed

5 files changed

+133
-7
lines changed

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ If you are using Maven without BOM, add this to your dependencies:
4949
If you are using Gradle 5.x or later, add this to your dependencies:
5050

5151
```Groovy
52-
implementation platform('com.google.cloud:libraries-bom:26.9.0')
52+
implementation platform('com.google.cloud:libraries-bom:26.10.0')
5353
5454
implementation 'com.google.cloud:google-cloud-datastore'
5555
```

‎google-cloud-datastore/src/main/java/com/google/cloud/datastore/StructuredQuery.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import static com.google.common.base.Preconditions.checkNotNull;
2727

2828
import com.google.api.core.ApiFunction;
29+
import com.google.api.core.BetaApi;
2930
import com.google.api.core.InternalApi;
3031
import com.google.cloud.StringEnumType;
3132
import com.google.cloud.StringEnumValue;
@@ -151,6 +152,8 @@ public Operator apply(String constant) {
151152

152153
static final Operator AND = type.createAndRegister("AND");
153154

155+
static final Operator OR = type.createAndRegister("OR");
156+
154157
com.google.datastore.v1.CompositeFilter.Operator toPb() {
155158
return com.google.datastore.v1.CompositeFilter.Operator.valueOf(name());
156159
}
@@ -231,6 +234,11 @@ public static CompositeFilter and(Filter first, Filter... other) {
231234
return new CompositeFilter(Operator.AND, first, other);
232235
}
233236

237+
@BetaApi
238+
public static CompositeFilter or(Filter first, Filter... other) {
239+
return new CompositeFilter(Operator.OR, first, other);
240+
}
241+
234242
@Override
235243
com.google.datastore.v1.Filter toPb() {
236244
com.google.datastore.v1.Filter.Builder filterPb = com.google.datastore.v1.Filter.newBuilder();

‎google-cloud-datastore/src/test/java/com/google/cloud/datastore/SerializationTest.java‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,19 @@ public class SerializationTest extends BaseSerializationTest {
6969
.addDistinctOn("p")
7070
.addOrderBy(OrderBy.asc("p"))
7171
.build();
72+
private static final Query<ProjectionEntity> QUERY4 =
73+
Query.newProjectionEntityQueryBuilder()
74+
.setKind("k")
75+
.setNamespace("ns1")
76+
.addProjection("p")
77+
.setLimit(100)
78+
.setOffset(5)
79+
.setStartCursor(CURSOR1)
80+
.setEndCursor(CURSOR2)
81+
.setFilter(CompositeFilter.or(PropertyFilter.gt("p1", 10), PropertyFilter.eq("a", "v")))
82+
.addDistinctOn("p")
83+
.addOrderBy(OrderBy.asc("p"))
84+
.build();
7285
private static final KeyValue KEY_VALUE = KeyValue.of(KEY1);
7386
private static final NullValue NULL_VALUE =
7487
NullValue.newBuilder().setExcludeFromIndexes(true).build();
@@ -136,6 +149,7 @@ protected java.io.Serializable[] serializableObjects() {
136149
QUERY1,
137150
QUERY2,
138151
QUERY3,
152+
QUERY4,
139153
NULL_VALUE,
140154
KEY_VALUE,
141155
STRING_VALUE,

‎google-cloud-datastore/src/test/java/com/google/cloud/datastore/StructuredQueryTest.java‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ public class StructuredQueryTest {
3737
private static final Cursor END_CURSOR = Cursor.copyFrom(new byte[] {10});
3838
private static final int OFFSET = 42;
3939
private static final Integer LIMIT = 43;
40-
private static final Filter FILTER =
40+
private static final Filter AND_FILTER =
4141
CompositeFilter.and(PropertyFilter.gt("p1", 10), PropertyFilter.eq("a", "v"));
42+
private static final Filter OR_FILTER =
43+
CompositeFilter.or(PropertyFilter.gt("p1", 10), PropertyFilter.eq("a", "v"));
4244
private static final OrderBy ORDER_BY_1 = OrderBy.asc("p2");
4345
private static final OrderBy ORDER_BY_2 = OrderBy.desc("p3");
4446
private static final List<OrderBy> ORDER_BY = ImmutableList.of(ORDER_BY_1, ORDER_BY_2);
@@ -56,7 +58,7 @@ public class StructuredQueryTest {
5658
.setEndCursor(END_CURSOR)
5759
.setOffset(OFFSET)
5860
.setLimit(LIMIT)
59-
.setFilter(FILTER)
61+
.setFilter(AND_FILTER)
6062
.setOrderBy(ORDER_BY_1, ORDER_BY_2)
6163
.build();
6264
private static final KeyQuery KEY_QUERY =
@@ -67,7 +69,7 @@ public class StructuredQueryTest {
6769
.setEndCursor(END_CURSOR)
6870
.setOffset(OFFSET)
6971
.setLimit(LIMIT)
70-
.setFilter(FILTER)
72+
.setFilter(OR_FILTER)
7173
.setOrderBy(ORDER_BY_1, ORDER_BY_2)
7274
.build();
7375
private static final ProjectionEntityQuery PROJECTION_QUERY =
@@ -78,7 +80,7 @@ public class StructuredQueryTest {
7880
.setEndCursor(END_CURSOR)
7981
.setOffset(OFFSET)
8082
.setLimit(LIMIT)
81-
.setFilter(FILTER)
83+
.setFilter(AND_FILTER)
8284
.setOrderBy(ORDER_BY_1, ORDER_BY_2)
8385
.setProjection(PROJECTION1, PROJECTION2)
8486
.setDistinctOn(DISTINCT_ON1, DISTINCT_ON2)
@@ -93,7 +95,14 @@ public void testEntityQueryBuilder() {
9395

9496
@Test
9597
public void testKeyQueryBuilder() {
96-
compareBaseBuilderFields(KEY_QUERY);
98+
assertEquals(NAMESPACE, KEY_QUERY.getNamespace());
99+
assertEquals(KIND, KEY_QUERY.getKind());
100+
assertEquals(START_CURSOR, KEY_QUERY.getStartCursor());
101+
assertEquals(END_CURSOR, KEY_QUERY.getEndCursor());
102+
assertEquals(OFFSET, KEY_QUERY.getOffset());
103+
assertEquals(LIMIT, KEY_QUERY.getLimit());
104+
assertEquals(OR_FILTER, KEY_QUERY.getFilter());
105+
assertEquals(ORDER_BY, KEY_QUERY.getOrderBy());
97106
assertEquals(ImmutableList.of(StructuredQuery.KEY_PROPERTY_NAME), KEY_QUERY.getProjection());
98107
assertTrue(KEY_QUERY.getDistinctOn().isEmpty());
99108
}
@@ -112,7 +121,7 @@ private void compareBaseBuilderFields(StructuredQuery<?> query) {
112121
assertEquals(END_CURSOR, query.getEndCursor());
113122
assertEquals(OFFSET, query.getOffset());
114123
assertEquals(LIMIT, query.getLimit());
115-
assertEquals(FILTER, query.getFilter());
124+
assertEquals(AND_FILTER, query.getFilter());
116125
assertEquals(ORDER_BY, query.getOrderBy());
117126
}
118127

‎google-cloud-datastore/src/test/java/com/google/cloud/datastore/it/ITDatastoreTest.java‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import com.google.cloud.datastore.ReadOption;
6060
import com.google.cloud.datastore.StringValue;
6161
import com.google.cloud.datastore.StructuredQuery;
62+
import com.google.cloud.datastore.StructuredQuery.CompositeFilter;
6263
import com.google.cloud.datastore.StructuredQuery.OrderBy;
6364
import com.google.cloud.datastore.StructuredQuery.PropertyFilter;
6465
import com.google.cloud.datastore.TimestampValue;
@@ -223,6 +224,85 @@ private <T> List<T> makeResultsCopy(QueryResults<T> scResults) {
223224
return results;
224225
}
225226

227+
@Test
228+
public void orQuery() {
229+
Key key = Key.newBuilder(KEY1, KIND2, 2).build();
230+
Entity entity3 =
231+
Entity.newBuilder(ENTITY1)
232+
.setKey(key)
233+
.remove("str")
234+
.set("name", "Dan")
235+
.setNull("null")
236+
.set("age", 19)
237+
.build();
238+
DATASTORE.put(entity3);
239+
240+
// age == 19 || age == 20
241+
CompositeFilter orFilter =
242+
CompositeFilter.or(PropertyFilter.eq("age", 19), PropertyFilter.eq("age", 20));
243+
Query<Entity> simpleOrQuery =
244+
Query.newEntityQueryBuilder()
245+
.setNamespace(NAMESPACE)
246+
.setKind(KIND2)
247+
.setFilter(orFilter)
248+
.build();
249+
QueryResults<Entity> results = DATASTORE.run(simpleOrQuery);
250+
assertTrue(results.hasNext());
251+
assertEquals(ENTITY2, results.next());
252+
assertTrue(results.hasNext());
253+
assertEquals(entity3, results.next());
254+
assertFalse(results.hasNext());
255+
256+
// age == 19 || age == 20 with limit of 1
257+
Query<Entity> simpleOrQueryLimit =
258+
Query.newEntityQueryBuilder()
259+
.setNamespace(NAMESPACE)
260+
.setKind(KIND2)
261+
.setFilter(orFilter)
262+
.setLimit(1)
263+
.build();
264+
QueryResults<Entity> results2 = DATASTORE.run(simpleOrQueryLimit);
265+
assertTrue(results2.hasNext());
266+
assertEquals(ENTITY2, results2.next());
267+
assertFalse(results2.hasNext());
268+
269+
// (age == 18 && name == Dan) || (age == 20 && name == Dan)
270+
CompositeFilter nestedOr =
271+
CompositeFilter.or(
272+
CompositeFilter.and(PropertyFilter.eq("age", 18), PropertyFilter.eq("name", "Dan")),
273+
CompositeFilter.and(PropertyFilter.eq("age", 20), PropertyFilter.eq("name", "Dan")));
274+
CompositeFilter compositeFilter =
275+
CompositeFilter.and(PropertyFilter.hasAncestor(ROOT_KEY), nestedOr);
276+
Query<Entity> orQueryNested =
277+
Query.newEntityQueryBuilder()
278+
.setNamespace(NAMESPACE)
279+
.setKind(KIND2)
280+
.setFilter(compositeFilter)
281+
.build();
282+
QueryResults<Entity> results3 = DATASTORE.run(orQueryNested);
283+
assertTrue(results3.hasNext());
284+
assertEquals(ENTITY2, results3.next());
285+
assertFalse(results3.hasNext());
286+
287+
// age == 20 && (name == Bob || name == Dan)
288+
CompositeFilter nestedOr2 =
289+
CompositeFilter.or(PropertyFilter.eq("name", "Dan"), PropertyFilter.eq("name", "Bob"));
290+
CompositeFilter andFilter = CompositeFilter.and(PropertyFilter.eq("age", 20), nestedOr2);
291+
CompositeFilter ancestorAndFilter =
292+
CompositeFilter.and(PropertyFilter.hasAncestor(ROOT_KEY), andFilter);
293+
Query<Entity> orQueryNested2 =
294+
Query.newEntityQueryBuilder()
295+
.setNamespace(NAMESPACE)
296+
.setKind(KIND2)
297+
.setFilter(ancestorAndFilter)
298+
.setLimit(1)
299+
.build();
300+
QueryResults<Entity> results4 = DATASTORE.run(orQueryNested2);
301+
assertTrue(results4.hasNext());
302+
assertEquals(ENTITY2, results4.next());
303+
assertFalse(results4.hasNext());
304+
}
305+
226306
@Test
227307
public void testNewTransactionCommit() {
228308
Transaction transaction = DATASTORE.newTransaction();
@@ -946,6 +1026,21 @@ public void testInNotInNeqFilters() throws InterruptedException {
9461026
assertEquals(e2, resultNeq.next());
9471027
assertFalse(resultNeq.hasNext());
9481028

1029+
Query<Entity> scQueryInEqOr =
1030+
Query.newEntityQueryBuilder()
1031+
.setKind(KIND1)
1032+
.setFilter(
1033+
CompositeFilter.or(
1034+
PropertyFilter.in("v_int", ListValue.of(10, 50000)),
1035+
PropertyFilter.eq("v_int", 10000)))
1036+
.build();
1037+
1038+
QueryResults<Entity> run = DATASTORE.run(scQueryInEqOr);
1039+
1040+
assertTrue(run.hasNext());
1041+
assertEquals(e1, run.next());
1042+
assertFalse(run.hasNext());
1043+
9491044
DATASTORE.delete(e1.getKey());
9501045
DATASTORE.delete(e2.getKey());
9511046
}

0 commit comments

Comments
 (0)