Skip to content

Commit

Permalink
feat: OR query support (#993)
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
kolea2 committed Mar 8, 2023
1 parent 3e155e6 commit 99b7843
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 7 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ public class SerializationTest extends BaseSerializationTest {
.addDistinctOn("p")
.addOrderBy(OrderBy.asc("p"))
.build();
private static final Query<ProjectionEntity> 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();
Expand Down Expand Up @@ -136,6 +149,7 @@ protected java.io.Serializable[] serializableObjects() {
QUERY1,
QUERY2,
QUERY3,
QUERY4,
NULL_VALUE,
KEY_VALUE,
STRING_VALUE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<OrderBy> ORDER_BY = ImmutableList.of(ORDER_BY_1, ORDER_BY_2);
Expand All @@ -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 =
Expand All @@ -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 =
Expand All @@ -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)
Expand All @@ -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());
}
Expand All @@ -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());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -223,6 +224,85 @@ private <T> List<T> makeResultsCopy(QueryResults<T> 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<Entity> simpleOrQuery =
Query.newEntityQueryBuilder()
.setNamespace(NAMESPACE)
.setKind(KIND2)
.setFilter(orFilter)
.build();
QueryResults<Entity> 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<Entity> simpleOrQueryLimit =
Query.newEntityQueryBuilder()
.setNamespace(NAMESPACE)
.setKind(KIND2)
.setFilter(orFilter)
.setLimit(1)
.build();
QueryResults<Entity> 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<Entity> orQueryNested =
Query.newEntityQueryBuilder()
.setNamespace(NAMESPACE)
.setKind(KIND2)
.setFilter(compositeFilter)
.build();
QueryResults<Entity> 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<Entity> orQueryNested2 =
Query.newEntityQueryBuilder()
.setNamespace(NAMESPACE)
.setKind(KIND2)
.setFilter(ancestorAndFilter)
.setLimit(1)
.build();
QueryResults<Entity> results4 = DATASTORE.run(orQueryNested2);
assertTrue(results4.hasNext());
assertEquals(ENTITY2, results4.next());
assertFalse(results4.hasNext());
}

@Test
public void testNewTransactionCommit() {
Transaction transaction = DATASTORE.newTransaction();
Expand Down Expand Up @@ -946,6 +1026,21 @@ public void testInNotInNeqFilters() throws InterruptedException {
assertEquals(e2, resultNeq.next());
assertFalse(resultNeq.hasNext());

Query<Entity> scQueryInEqOr =
Query.newEntityQueryBuilder()
.setKind(KIND1)
.setFilter(
CompositeFilter.or(
PropertyFilter.in("v_int", ListValue.of(10, 50000)),
PropertyFilter.eq("v_int", 10000)))
.build();

QueryResults<Entity> run = DATASTORE.run(scQueryInEqOr);

assertTrue(run.hasNext());
assertEquals(e1, run.next());
assertFalse(run.hasNext());

DATASTORE.delete(e1.getKey());
DATASTORE.delete(e2.getKey());
}
Expand Down

0 comments on commit 99b7843

Please sign in to comment.