diff --git a/CHANGELOG.md b/CHANGELOG.md index 17e2a96ef7..0bd3629e90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [3.9.0](https://github.com/googleapis/java-firestore/compare/v3.8.2...v3.9.0) (2023-03-06) + + +### Features + +* Enable `OR` Query support. ([#1007](https://github.com/googleapis/java-firestore/issues/1007)) ([e502cd4](https://github.com/googleapis/java-firestore/commit/e502cd4a91396360815f540d6058291a75e15951)) + ## [3.8.2](https://github.com/googleapis/java-firestore/compare/v3.8.1...v3.8.2) (2023-03-02) diff --git a/README.md b/README.md index ef10c2c265..2344e2970d 100644 --- a/README.md +++ b/README.md @@ -56,13 +56,13 @@ implementation 'com.google.cloud:google-cloud-firestore' If you are using Gradle without BOM, add this to your dependencies: ```Groovy -implementation 'com.google.cloud:google-cloud-firestore:3.8.1' +implementation 'com.google.cloud:google-cloud-firestore:3.8.2' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "3.8.1" +libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "3.8.2" ``` ## Authentication diff --git a/google-cloud-firestore-admin/pom.xml b/google-cloud-firestore-admin/pom.xml index a0ca7cf159..470baf9fb1 100644 --- a/google-cloud-firestore-admin/pom.xml +++ b/google-cloud-firestore-admin/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 google-cloud-firestore-admin - 3.8.2 + 3.9.0 jar Google Cloud Firestore Admin Client https://github.com/googleapis/java-firestore @@ -14,7 +14,7 @@ com.google.cloud google-cloud-firestore-parent - 3.8.2 + 3.9.0 diff --git a/google-cloud-firestore-bom/pom.xml b/google-cloud-firestore-bom/pom.xml index b1e7fedaa7..c65ee56860 100644 --- a/google-cloud-firestore-bom/pom.xml +++ b/google-cloud-firestore-bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-firestore-bom - 3.8.2 + 3.9.0 pom com.google.cloud @@ -52,37 +52,37 @@ com.google.cloud google-cloud-firestore - 3.8.2 + 3.9.0 com.google.cloud google-cloud-firestore-admin - 3.8.2 + 3.9.0 com.google.api.grpc grpc-google-cloud-firestore-admin-v1 - 3.8.2 + 3.9.0 com.google.api.grpc grpc-google-cloud-firestore-v1 - 3.8.2 + 3.9.0 com.google.api.grpc proto-google-cloud-firestore-admin-v1 - 3.8.2 + 3.9.0 com.google.api.grpc proto-google-cloud-firestore-v1 - 3.8.2 + 3.9.0 com.google.cloud proto-google-cloud-firestore-bundle-v1 - 3.8.2 + 3.9.0 diff --git a/google-cloud-firestore/pom.xml b/google-cloud-firestore/pom.xml index 9745d70951..2d772b893e 100644 --- a/google-cloud-firestore/pom.xml +++ b/google-cloud-firestore/pom.xml @@ -2,7 +2,7 @@ 4.0.0 google-cloud-firestore - 3.8.2 + 3.9.0 jar Google Cloud Firestore https://github.com/googleapis/java-firestore @@ -12,7 +12,7 @@ com.google.cloud google-cloud-firestore-parent - 3.8.2 + 3.9.0 google-cloud-firestore @@ -177,6 +177,11 @@ 3.12.0 test + + com.google.http-client + google-http-client + test + diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Filter.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Filter.java index 0e25187dd5..6bd93582c6 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Filter.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Filter.java @@ -23,7 +23,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -/** @hide */ public class Filter { static class UnaryFilter extends Filter { private final FieldPath field; @@ -69,113 +68,269 @@ public StructuredQuery.CompositeFilter.Operator getOperator() { } } + /** + * Creates a new filter for checking that the given field is equal to the given value. + * + * @param field The field used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter equalTo(@Nonnull String field, @Nullable Object value) { return equalTo(FieldPath.fromDotSeparatedString(field), value); } + /** + * Creates a new filter for checking that the given field is equal to the given value. + * + * @param fieldPath The field path used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter equalTo(@Nonnull FieldPath fieldPath, @Nullable Object value) { return new UnaryFilter(fieldPath, Operator.EQUAL, value); } + /** + * Creates a new filter for checking that the given field is not equal to the given value. + * + * @param field The field used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter notEqualTo(@Nonnull String field, @Nullable Object value) { return notEqualTo(FieldPath.fromDotSeparatedString(field), value); } + /** + * Creates a new filter for checking that the given field is not equal to the given value. + * + * @param fieldPath The field path used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter notEqualTo(@Nonnull FieldPath fieldPath, @Nullable Object value) { return new UnaryFilter(fieldPath, Operator.NOT_EQUAL, value); } + /** + * Creates a new filter for checking that the given field is greater than the given value. + * + * @param field The field used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter greaterThan(@Nonnull String field, @Nullable Object value) { return greaterThan(FieldPath.fromDotSeparatedString(field), value); } + /** + * Creates a new filter for checking that the given field is greater than the given value. + * + * @param fieldPath The field path used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter greaterThan(@Nonnull FieldPath fieldPath, @Nullable Object value) { return new UnaryFilter(fieldPath, Operator.GREATER_THAN, value); } + /** + * Creates a new filter for checking that the given field is greater than or equal to the given + * value. + * + * @param field The field used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter greaterThanOrEqualTo(@Nonnull String field, @Nullable Object value) { return greaterThanOrEqualTo(FieldPath.fromDotSeparatedString(field), value); } + /** + * Creates a new filter for checking that the given field is greater than or equal to the given + * value. + * + * @param fieldPath The field path used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter greaterThanOrEqualTo(@Nonnull FieldPath fieldPath, @Nullable Object value) { return new UnaryFilter(fieldPath, Operator.GREATER_THAN_OR_EQUAL, value); } + /** + * Creates a new filter for checking that the given field is less than the given value. + * + * @param field The field used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter lessThan(@Nonnull String field, @Nullable Object value) { return lessThan(FieldPath.fromDotSeparatedString(field), value); } + /** + * Creates a new filter for checking that the given field is less than the given value. + * + * @param fieldPath The field path used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter lessThan(@Nonnull FieldPath fieldPath, @Nullable Object value) { return new UnaryFilter(fieldPath, Operator.LESS_THAN, value); } + /** + * Creates a new filter for checking that the given field is less than or equal to the given + * value. + * + * @param field The field used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter lessThanOrEqualTo(@Nonnull String field, @Nullable Object value) { return lessThanOrEqualTo(FieldPath.fromDotSeparatedString(field), value); } + /** + * Creates a new filter for checking that the given field is less than or equal to the given + * value. + * + * @param fieldPath The field path used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter lessThanOrEqualTo(@Nonnull FieldPath fieldPath, @Nullable Object value) { return new UnaryFilter(fieldPath, Operator.LESS_THAN_OR_EQUAL, value); } + /** + * Creates a new filter for checking that the given array field contains the given value. + * + * @param field The field used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter arrayContains(@Nonnull String field, @Nullable Object value) { return arrayContains(FieldPath.fromDotSeparatedString(field), value); } + /** + * Creates a new filter for checking that the given array field contains the given value. + * + * @param fieldPath The field path used for the filter. + * @param value The value used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter arrayContains(@Nonnull FieldPath fieldPath, @Nullable Object value) { return new UnaryFilter(fieldPath, Operator.ARRAY_CONTAINS, value); } + /** + * Creates a new filter for checking that the given array field contains any of the given values. + * + * @param field The field used for the filter. + * @param value The list of values used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter arrayContainsAny(@Nonnull String field, @Nullable Object value) { return arrayContainsAny(FieldPath.fromDotSeparatedString(field), value); } + /** + * Creates a new filter for checking that the given array field contains any of the given values. + * + * @param fieldPath The field path used for the filter. + * @param value The list of values used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter arrayContainsAny(@Nonnull FieldPath fieldPath, @Nullable Object value) { return new UnaryFilter(fieldPath, Operator.ARRAY_CONTAINS_ANY, value); } + /** + * Creates a new filter for checking that the given field equals any of the given values. + * + * @param field The field used for the filter. + * @param value The list of values used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter inArray(@Nonnull String field, @Nullable Object value) { return inArray(FieldPath.fromDotSeparatedString(field), value); } + /** + * Creates a new filter for checking that the given field equals any of the given values. + * + * @param fieldPath The field path used for the filter. + * @param value The list of values used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter inArray(@Nonnull FieldPath fieldPath, @Nullable Object value) { return new UnaryFilter(fieldPath, Operator.IN, value); } + /** + * Creates a new filter for checking that the given field does not equal any of the given values. + * + * @param field The field path used for the filter. + * @param value The list of values used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter notInArray(@Nonnull String field, @Nullable Object value) { return notInArray(FieldPath.fromDotSeparatedString(field), value); } + /** + * Creates a new filter for checking that the given field does not equal any of the given values. + * + * @param fieldPath The field path used for the filter. + * @param value The list of values used for the filter. + * @return The newly created filter. + */ @Nonnull public static Filter notInArray(@Nonnull FieldPath fieldPath, @Nullable Object value) { return new UnaryFilter(fieldPath, Operator.NOT_IN, value); } + /** + * Creates a new filter that is a disjunction of the given filters. A disjunction filter includes + * a document if it satisfies any of the given filters. + * + * @param filters The list of filters to perform a disjunction for. + * @return The newly created filter. + */ @Nonnull public static Filter or(Filter... filters) { - // TODO(orquery): Change this to Operator.OR once it is available. - return new CompositeFilter( - Arrays.asList(filters), StructuredQuery.CompositeFilter.Operator.OPERATOR_UNSPECIFIED); + return new CompositeFilter(Arrays.asList(filters), StructuredQuery.CompositeFilter.Operator.OR); } + /** + * Creates a new filter that is a conjunction of the given filters. A conjunction filter includes + * a document if it satisfies all of the given filters. + * + * @param filters The list of filters to perform a conjunction for. + * @return The newly created filter. + */ @Nonnull public static Filter and(Filter... filters) { return new CompositeFilter( diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java index 410e6b9f4b..6dd52d8fd0 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java @@ -888,8 +888,13 @@ public Query whereNotIn(@Nonnull FieldPath fieldPath, @Nonnull List50)) || f==60 + // a IN [1,2] && (b==20 || c==30 || (d==40 && e>50) || f==60) query.where( or( equalTo("b", 20), @@ -1361,6 +1361,36 @@ public void serializationTestWithNestedCompositeFilters() { assertSerialization(query); } + @Test + public void serializationTestWithNestedCompositeFiltersOuterOr() { + assertSerialization(query); + // a IN [1,2] || (b==20 && c==30 && (d==40 || e>50) && f==60) + query.where( + or( + inArray("a", Arrays.asList(1, 2)), + and( + equalTo("b", 20), + equalTo("c", 30), + or(equalTo("d", 40), greaterThan("e", 50)), + and(equalTo("f", 60)), + or(and())))); + assertSerialization(query); + query = query.orderBy("l"); + assertSerialization(query); + query = query.startAt("o"); + assertSerialization(query); + query = query.startAfter("p"); + assertSerialization(query); + query = query.endBefore("q"); + assertSerialization(query); + query = query.endAt("r"); + assertSerialization(query); + query = query.limit(8); + assertSerialization(query); + query = query.offset(9); + assertSerialization(query); + } + private void assertSerialization(Query query) { RunQueryRequest runQueryRequest = query.toProto(); Query deserializedQuery = Query.fromProto(firestoreMock, runQueryRequest); diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryTest.java new file mode 100644 index 0000000000..9707cb25e1 --- /dev/null +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryTest.java @@ -0,0 +1,418 @@ +/* + * Copyright 2020 Google LLC + * + * 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.cloud.firestore.it; + +import static com.google.cloud.firestore.it.TestHelper.isRunningAgainstFirestoreEmulator; +import static com.google.common.primitives.Ints.asList; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeTrue; + +import com.google.api.client.util.Preconditions; +import com.google.cloud.firestore.CollectionReference; +import com.google.cloud.firestore.Filter; +import com.google.cloud.firestore.Firestore; +import com.google.cloud.firestore.FirestoreOptions; +import com.google.cloud.firestore.LocalFirestoreHelper; +import com.google.cloud.firestore.Query; +import com.google.cloud.firestore.Query.Direction; +import com.google.cloud.firestore.QuerySnapshot; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ITQueryTest { + + private static Firestore firestore; + + @Rule public TestName testName = new TestName(); + + @Before + public void setUpFirestore() { + firestore = FirestoreOptions.newBuilder().build().getService(); + Preconditions.checkNotNull( + firestore, + "Error instantiating Firestore. Check that the service account credentials were properly set."); + } + + @After + public void tearDownFirestore() throws Exception { + if (firestore != null) { + firestore.close(); + firestore = null; + } + } + + private CollectionReference createEmptyCollection() { + String collectionPath = + "java-" + testName.getMethodName() + "-" + LocalFirestoreHelper.autoId(); + return firestore.collection(collectionPath); + } + + public static Map map(Object... entries) { + Map res = new LinkedHashMap<>(); + for (int i = 0; i < entries.length; i += 2) { + res.put((String) entries[i], (T) entries[i + 1]); + } + return res; + } + + public static CollectionReference testCollectionWithDocs(Map> docs) + throws ExecutionException, InterruptedException, TimeoutException { + CollectionReference collection = firestore.collection(LocalFirestoreHelper.autoId()); + for (Map.Entry> doc : docs.entrySet()) { + collection.document(doc.getKey()).set(doc.getValue()).get(5, TimeUnit.SECONDS); + } + return collection; + } + + public static void checkQuerySnapshotContainsDocuments(Query query, String... docs) + throws ExecutionException, InterruptedException { + QuerySnapshot snapshot = query.get().get(); + List result = + snapshot.getDocuments().stream() + .map(queryDocumentSnapshot -> queryDocumentSnapshot.getReference().getId()) + .collect(Collectors.toList()); + assertThat(result).isEqualTo(Arrays.asList(docs)); + } + + @Test + public void orQueries() throws Exception { + Map> testDocs = + map( + "doc1", map("a", 1, "b", 0), + "doc2", map("a", 2, "b", 1), + "doc3", map("a", 3, "b", 2), + "doc4", map("a", 1, "b", 3), + "doc5", map("a", 1, "b", 1)); + + CollectionReference collection = testCollectionWithDocs(testDocs); + + // Two equalities: a==1 || b==1. + checkQuerySnapshotContainsDocuments( + collection.where(Filter.or(Filter.equalTo("a", 1), Filter.equalTo("b", 1))), + "doc1", + "doc2", + "doc4", + "doc5"); + + // (a==1 && b==0) || (a==3 && b==2) + checkQuerySnapshotContainsDocuments( + collection.where( + Filter.or( + Filter.and(Filter.equalTo("a", 1), Filter.equalTo("b", 0)), + Filter.and(Filter.equalTo("a", 3), Filter.equalTo("b", 2)))), + "doc1", + "doc3"); + + // a==1 && (b==0 || b==3). + checkQuerySnapshotContainsDocuments( + collection.where( + Filter.and( + Filter.equalTo("a", 1), Filter.or(Filter.equalTo("b", 0), Filter.equalTo("b", 3)))), + "doc1", + "doc4"); + + // (a==2 || b==2) && (a==3 || b==3) + checkQuerySnapshotContainsDocuments( + collection.where( + Filter.and( + Filter.or(Filter.equalTo("a", 2), Filter.equalTo("b", 2)), + Filter.or(Filter.equalTo("a", 3), Filter.equalTo("b", 3)))), + "doc3"); + + // Test with limits without orderBy (the __name__ ordering is the tiebreaker). + checkQuerySnapshotContainsDocuments( + collection.where(Filter.or(Filter.equalTo("a", 2), Filter.equalTo("b", 1))).limit(1), + "doc2"); + } + + @Test + public void orQueriesWithCompositeIndexes() throws Exception { + assumeTrue( + "Skip this test when running against production because these queries require a composite index.", + isRunningAgainstFirestoreEmulator(firestore)); + Map> testDocs = + map( + "doc1", map("a", 1, "b", 0), + "doc2", map("a", 2, "b", 1), + "doc3", map("a", 3, "b", 2), + "doc4", map("a", 1, "b", 3), + "doc5", map("a", 1, "b", 1)); + + CollectionReference collection = testCollectionWithDocs(testDocs); + + // with one inequality: a>2 || b==1. + checkQuerySnapshotContainsDocuments( + collection.where(Filter.or(Filter.greaterThan("a", 2), Filter.equalTo("b", 1))), + "doc5", + "doc2", + "doc3"); + + // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2 + checkQuerySnapshotContainsDocuments( + collection.where(Filter.or(Filter.equalTo("a", 1), Filter.greaterThan("b", 0))).limit(2), + "doc1", + "doc2"); + + // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2 + // Note: The public query API does not allow implicit ordering when limitToLast is used. + checkQuerySnapshotContainsDocuments( + collection + .where(Filter.or(Filter.equalTo("a", 1), Filter.greaterThan("b", 0))) + .limitToLast(2) + .orderBy("b"), + "doc3", + "doc4"); + + // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1 + checkQuerySnapshotContainsDocuments( + collection + .where(Filter.or(Filter.equalTo("a", 2), Filter.equalTo("b", 1))) + .limit(1) + .orderBy("a"), + "doc5"); + + // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT_TO_LAST 1 + checkQuerySnapshotContainsDocuments( + collection + .where(Filter.or(Filter.equalTo("a", 2), Filter.equalTo("b", 1))) + .limitToLast(1) + .orderBy("a"), + "doc2"); + + // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a DESC LIMIT 1 + checkQuerySnapshotContainsDocuments( + collection + .where(Filter.or(Filter.equalTo("a", 2), Filter.equalTo("b", 1))) + .limit(1) + .orderBy("a", Direction.DESCENDING), + "doc2"); + } + + @Test + public void orQueryDoesNotIncludeDocumentsWithMissingFields() throws Exception { + Map> testDocs = + map( + "doc1", map("a", 1, "b", 0), + "doc2", map("b", 1), + "doc3", map("a", 3, "b", 2), + "doc4", map("a", 1, "b", 3), + "doc5", map("a", 1), + "doc6", map("a", 2)); + + CollectionReference collection = testCollectionWithDocs(testDocs); + + // Query: a==1 || b==1 + // There's no explicit nor implicit orderBy. Documents with missing 'a' or missing 'b' should be + // allowed if the document matches at least one disjunction term. + Query query = collection.where(Filter.or(Filter.equalTo("a", 1), Filter.equalTo("b", 1))); + checkQuerySnapshotContainsDocuments(query, "doc1", "doc2", "doc4", "doc5"); + } + + @Test + public void orQueryDoesNotIncludeDocumentsWithMissingFields2() throws Exception { + assumeTrue( + "Skip this test when running against production because these queries require a composite index.", + isRunningAgainstFirestoreEmulator(firestore)); + Map> testDocs = + map( + "doc1", map("a", 1, "b", 0), + "doc2", map("b", 1), + "doc3", map("a", 3, "b", 2), + "doc4", map("a", 1, "b", 3), + "doc5", map("a", 1), + "doc6", map("a", 2)); + + CollectionReference collection = testCollectionWithDocs(testDocs); + + // Query: a==1 || b==1 order by a. + // doc2 should not be included because it's missing the field 'a', and we have "orderBy a". + Query query1 = + collection.where(Filter.or(Filter.equalTo("a", 1), Filter.equalTo("b", 1))).orderBy("a"); + checkQuerySnapshotContainsDocuments(query1, "doc1", "doc4", "doc5"); + + // Query: a==1 || b==1 order by b. + // doc5 should not be included because it's missing the field 'b', and we have "orderBy b". + Query query2 = + collection.where(Filter.or(Filter.equalTo("a", 1), Filter.equalTo("b", 1))).orderBy("b"); + checkQuerySnapshotContainsDocuments(query2, "doc1", "doc2", "doc4"); + + // Query: a>2 || b==1. + // This query has an implicit 'order by a'. + // doc2 should not be included because it's missing the field 'a'. + Query query3 = collection.where(Filter.or(Filter.greaterThan("a", 2), Filter.equalTo("b", 1))); + checkQuerySnapshotContainsDocuments(query3, "doc3"); + + // Query: a>1 || b==1 order by a order by b. + // doc6 should not be included because it's missing the field 'b'. + // doc2 should not be included because it's missing the field 'a'. + Query query4 = + collection + .where(Filter.or(Filter.greaterThan("a", 1), Filter.equalTo("b", 1))) + .orderBy("a") + .orderBy("b"); + checkQuerySnapshotContainsDocuments(query4, "doc3"); + } + + @Test + public void orQueriesWithIn() throws ExecutionException, InterruptedException, TimeoutException { + Map> testDocs = + map( + "doc1", map("a", 1, "b", 0), + "doc2", map("b", 1), + "doc3", map("a", 3, "b", 2), + "doc4", map("a", 1, "b", 3), + "doc5", map("a", 1), + "doc6", map("a", 2)); + CollectionReference collection = testCollectionWithDocs(testDocs); + + // a==2 || b in [2,3] + checkQuerySnapshotContainsDocuments( + collection.where(Filter.or(Filter.equalTo("a", 2), Filter.inArray("b", asList(2, 3)))), + "doc3", + "doc4", + "doc6"); + } + + @Test + public void orQueriesWithNotIn() + throws ExecutionException, InterruptedException, TimeoutException { + assumeTrue( + "Skip this test when running against production because it is currently not supported.", + isRunningAgainstFirestoreEmulator(firestore)); + Map> testDocs = + map( + "doc1", map("a", 1, "b", 0), + "doc2", map("b", 1), + "doc3", map("a", 3, "b", 2), + "doc4", map("a", 1, "b", 3), + "doc5", map("a", 1), + "doc6", map("a", 2)); + CollectionReference collection = testCollectionWithDocs(testDocs); + + // a==2 || b not-in [2,3] + // Has implicit orderBy b. + checkQuerySnapshotContainsDocuments( + collection.where(Filter.or(Filter.equalTo("a", 2), Filter.notInArray("b", asList(2, 3)))), + "doc1", + "doc2"); + } + + @Test + public void orQueriesWithArrayMembership() + throws ExecutionException, InterruptedException, TimeoutException { + Map> testDocs = + map( + "doc1", map("a", 1, "b", asList(0)), + "doc2", map("b", asList(1)), + "doc3", map("a", 3, "b", asList(2, 7)), + "doc4", map("a", 1, "b", asList(3, 7)), + "doc5", map("a", 1), + "doc6", map("a", 2)); + CollectionReference collection = testCollectionWithDocs(testDocs); + + // a==2 || b array-contains 7 + checkQuerySnapshotContainsDocuments( + collection.where(Filter.or(Filter.equalTo("a", 2), Filter.arrayContains("b", 7))), + "doc3", + "doc4", + "doc6"); + + // a==2 || b array-contains-any [0, 3] + checkQuerySnapshotContainsDocuments( + collection.where( + Filter.or(Filter.equalTo("a", 2), Filter.arrayContainsAny("b", asList(0, 3)))), + "doc1", + "doc4", + "doc6"); + } + + @Test + public void testUsingInWithArrayContains() + throws ExecutionException, InterruptedException, TimeoutException { + Map> testDocs = + map( + "doc1", map("a", 1, "b", asList(0)), + "doc2", map("b", asList(1)), + "doc3", map("a", 3, "b", asList(2, 7)), + "doc4", map("a", 1, "b", asList(3, 7)), + "doc5", map("a", 1), + "doc6", map("a", 2)); + CollectionReference collection = testCollectionWithDocs(testDocs); + + Query query1 = + collection.where( + Filter.or(Filter.inArray("a", asList(2, 3)), Filter.arrayContains("b", 3))); + checkQuerySnapshotContainsDocuments(query1, "doc3", "doc4", "doc6"); + + Query query2 = + collection.where( + Filter.and(Filter.inArray("a", asList(2, 3)), Filter.arrayContains("b", 7))); + checkQuerySnapshotContainsDocuments(query2, "doc3"); + + Query query3 = + collection.where( + Filter.or( + Filter.inArray("a", asList(2, 3)), + Filter.and(Filter.arrayContains("b", 3), Filter.equalTo("a", 1)))); + checkQuerySnapshotContainsDocuments(query3, "doc3", "doc4", "doc6"); + + Query query4 = + collection.where( + Filter.and( + Filter.inArray("a", asList(2, 3)), + Filter.or(Filter.arrayContains("b", 7), Filter.equalTo("a", 1)))); + checkQuerySnapshotContainsDocuments(query4, "doc3"); + } + + @Test + public void testOrderByEquality() + throws ExecutionException, InterruptedException, TimeoutException { + assumeTrue( + "Skip this test if running against production because order-by-equality is " + + "not supported yet.", + isRunningAgainstFirestoreEmulator(firestore)); + Map> testDocs = + map( + "doc1", map("a", 1, "b", asList(0)), + "doc2", map("b", asList(1)), + "doc3", map("a", 3, "b", asList(2, 7), "c", 10), + "doc4", map("a", 1, "b", asList(3, 7)), + "doc5", map("a", 1), + "doc6", map("a", 2, "c", 20)); + CollectionReference collection = testCollectionWithDocs(testDocs); + + Query query1 = collection.where(Filter.equalTo("a", 1)).orderBy("a"); + checkQuerySnapshotContainsDocuments(query1, "doc1", "doc4", "doc5"); + + Query query2 = collection.where(Filter.inArray("a", asList(2, 3))).orderBy("a"); + checkQuerySnapshotContainsDocuments(query2, "doc6", "doc3"); + } +} diff --git a/grpc-google-cloud-firestore-admin-v1/pom.xml b/grpc-google-cloud-firestore-admin-v1/pom.xml index 48e5f39fb3..e1e256de90 100644 --- a/grpc-google-cloud-firestore-admin-v1/pom.xml +++ b/grpc-google-cloud-firestore-admin-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-firestore-admin-v1 - 3.8.2 + 3.9.0 grpc-google-cloud-firestore-admin-v1 GRPC library for grpc-google-cloud-firestore-admin-v1 com.google.cloud google-cloud-firestore-parent - 3.8.2 + 3.9.0 diff --git a/grpc-google-cloud-firestore-v1/pom.xml b/grpc-google-cloud-firestore-v1/pom.xml index f996376413..9e9adde7fe 100644 --- a/grpc-google-cloud-firestore-v1/pom.xml +++ b/grpc-google-cloud-firestore-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-firestore-v1 - 3.8.2 + 3.9.0 grpc-google-cloud-firestore-v1 GRPC library for grpc-google-cloud-firestore-v1 com.google.cloud google-cloud-firestore-parent - 3.8.2 + 3.9.0 diff --git a/pom.xml b/pom.xml index 90d0e87e57..33b7ea2e55 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-firestore-parent pom - 3.8.2 + 3.9.0 Google Cloud Firestore Parent https://github.com/googleapis/java-firestore @@ -150,32 +150,32 @@ com.google.api.grpc proto-google-cloud-firestore-admin-v1 - 3.8.2 + 3.9.0 com.google.cloud google-cloud-firestore - 3.8.2 + 3.9.0 com.google.cloud proto-google-cloud-firestore-bundle-v1 - 3.8.2 + 3.9.0 com.google.api.grpc proto-google-cloud-firestore-v1 - 3.8.2 + 3.9.0 com.google.api.grpc grpc-google-cloud-firestore-admin-v1 - 3.8.2 + 3.9.0 com.google.api.grpc grpc-google-cloud-firestore-v1 - 3.8.2 + 3.9.0 diff --git a/proto-google-cloud-firestore-admin-v1/pom.xml b/proto-google-cloud-firestore-admin-v1/pom.xml index 4c19d1a0c6..23d9af6abf 100644 --- a/proto-google-cloud-firestore-admin-v1/pom.xml +++ b/proto-google-cloud-firestore-admin-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-firestore-admin-v1 - 3.8.2 + 3.9.0 proto-google-cloud-firestore-admin-v1 PROTO library for proto-google-cloud-firestore-admin-v1 com.google.cloud google-cloud-firestore-parent - 3.8.2 + 3.9.0 diff --git a/proto-google-cloud-firestore-bundle-v1/pom.xml b/proto-google-cloud-firestore-bundle-v1/pom.xml index 7403d00e0c..34539ec371 100644 --- a/proto-google-cloud-firestore-bundle-v1/pom.xml +++ b/proto-google-cloud-firestore-bundle-v1/pom.xml @@ -5,14 +5,14 @@ 4.0.0 proto-google-cloud-firestore-bundle-v1 - 3.8.2 + 3.9.0 proto-google-cloud-firestore-bundle-v1 PROTO library for proto-google-cloud-firestore-bundle-v1 com.google.cloud google-cloud-firestore-parent - 3.8.2 + 3.9.0 diff --git a/proto-google-cloud-firestore-v1/pom.xml b/proto-google-cloud-firestore-v1/pom.xml index d44c061f54..a7830ac477 100644 --- a/proto-google-cloud-firestore-v1/pom.xml +++ b/proto-google-cloud-firestore-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-firestore-v1 - 3.8.2 + 3.9.0 proto-google-cloud-firestore-v1 PROTO library for proto-google-cloud-firestore-v1 com.google.cloud google-cloud-firestore-parent - 3.8.2 + 3.9.0 diff --git a/versions.txt b/versions.txt index f7c99516e2..4fd57dab6b 100644 --- a/versions.txt +++ b/versions.txt @@ -1,11 +1,11 @@ # Format: # module:released-version:current-version -google-cloud-firestore:3.8.2:3.8.2 -google-cloud-firestore-admin:3.8.2:3.8.2 -google-cloud-firestore-bom:3.8.2:3.8.2 -grpc-google-cloud-firestore-admin-v1:3.8.2:3.8.2 -grpc-google-cloud-firestore-v1:3.8.2:3.8.2 -proto-google-cloud-firestore-admin-v1:3.8.2:3.8.2 -proto-google-cloud-firestore-v1:3.8.2:3.8.2 -proto-google-cloud-firestore-bundle-v1:3.8.2:3.8.2 +google-cloud-firestore:3.9.0:3.9.0 +google-cloud-firestore-admin:3.9.0:3.9.0 +google-cloud-firestore-bom:3.9.0:3.9.0 +grpc-google-cloud-firestore-admin-v1:3.9.0:3.9.0 +grpc-google-cloud-firestore-v1:3.9.0:3.9.0 +proto-google-cloud-firestore-admin-v1:3.9.0:3.9.0 +proto-google-cloud-firestore-v1:3.9.0:3.9.0 +proto-google-cloud-firestore-bundle-v1:3.9.0:3.9.0