From 417b288fffe8b2953a1def9837e921e2dab62927 Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Tue, 9 Aug 2022 16:53:54 -0500 Subject: [PATCH 01/16] Remove TODOs and add tests for OR Query support. --- .../com/google/cloud/firestore/Filter.java | 5 +- .../com/google/cloud/firestore/Query.java | 3 +- .../cloud/firestore/LocalFirestoreHelper.java | 3 +- .../cloud/firestore/it/ITQueryTest.java | 323 ++++++++++++++++++ 4 files changed, 327 insertions(+), 7 deletions(-) create mode 100644 google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryTest.java 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..854ea9abec 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 @@ -17,6 +17,7 @@ package com.google.cloud.firestore; import com.google.firestore.v1.StructuredQuery; +import com.google.firestore.v1.StructuredQuery.CompositeFilter; import com.google.firestore.v1.StructuredQuery.FieldFilter.Operator; import java.util.Arrays; import java.util.List; @@ -171,9 +172,7 @@ public static Filter notInArray(@Nonnull FieldPath fieldPath, @Nullable Object v @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); } @Nonnull 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 5218b54e45..5c6ac3eb1d 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,7 @@ public Query whereNotIn(@Nonnull FieldPath fieldPath, @Nonnull List 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(); + 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"); + + // with one inequality: a>2 || b==1. + checkQuerySnapshotContainsDocuments( + collection.where(Filter.or(Filter.greaterThan("a", 2), Filter.equalTo("b", 1))), + "doc5", + "doc2", + "doc3"); + + // (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 (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 without orderBy (the __name__ ordering is the tie breaker). + checkQuerySnapshotContainsDocuments( + collection.where(Filter.or(Filter.equalTo("a", 2), Filter.equalTo("b", 1))).limit(1), + "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 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"); + + // 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 query5 = collection.where(Filter.or(Filter.equalTo("a", 1), Filter.equalTo("b", 1))); + checkQuerySnapshotContainsDocuments(query5, "doc1", "doc2", "doc4", "doc5"); + } + + @Test + public void orQueryWithInAndNotIn() 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 query1 = + collection.where( + Filter.or(Filter.equalTo("a", 2), Filter.inArray("b", Arrays.asList(2, 3)))); + checkQuerySnapshotContainsDocuments(query1, "doc3", "doc4", "doc6"); + + // a==2 || (b != 2 && b != 3) + // Has implicit "orderBy b" + Query query2 = + collection.where( + Filter.or(Filter.equalTo("a", 2), Filter.notInArray("b", Arrays.asList(2, 3)))); + checkQuerySnapshotContainsDocuments(query2, "doc1", "doc2"); + } + + @Test + public void orQueriesWithInAndNotIn() + 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"); + + // 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"); + } +} From 1a8b10309056702863ec4913677569963f151177 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Wed, 10 Aug 2022 16:19:40 +0000 Subject: [PATCH 02/16] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4ab970ce32..09fd25914d 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.3.0' +implementation 'com.google.cloud:google-cloud-firestore:3.4.0' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "3.3.0" +libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "3.4.0" ``` ## Authentication From 96ee72f3c89b46d55add0c13e41b444b964928de Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Tue, 10 Jan 2023 15:04:11 -0800 Subject: [PATCH 03/16] Address comments and backport a new test. --- .../com/google/cloud/firestore/QueryTest.java | 34 ++++++++++++++++- .../cloud/firestore/it/ITQueryTest.java | 37 +++++-------------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryTest.java index c98f05a2cc..f1591c400f 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryTest.java @@ -1331,12 +1331,12 @@ public void serializationTestWithSingleFilterCompositeFilters() { } @Test - public void serializationTestWithNestedCompositeFilters() { + public void serializationTestWithNestedCompositeFiltersOuterAnd() { assertSerialization(query); // a IN [1,2] query.where(inArray("a", Arrays.asList(1, 2))); assertSerialization(query); - // a IN [1,2] && (b==20 || c==30 || (d==40 && e>50)) || 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 index 786a35dd23..257da72c4c 100644 --- 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 @@ -26,6 +26,7 @@ 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; @@ -52,6 +53,7 @@ public class ITQueryTest { @Before public void setUpFirestore() { + // TODO: stop using emulator for these tests once prod is ready. firestore = FirestoreOptions.newBuilder().setHost("localhost:8080").build().getService(); Preconditions.checkNotNull( firestore, @@ -92,7 +94,6 @@ public static CollectionReference testCollectionWithDocs(Map result = snapshot.getDocuments(); List result = snapshot.getDocuments().stream() .map(queryDocumentSnapshot -> queryDocumentSnapshot.getReference().getId()) @@ -184,6 +185,14 @@ public void orQueries() throws Exception { .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 with limits without orderBy (the __name__ ordering is the tie breaker). checkQuerySnapshotContainsDocuments( collection.where(Filter.or(Filter.equalTo("a", 2), Filter.equalTo("b", 1))).limit(1), @@ -238,32 +247,6 @@ public void orQueryDoesNotIncludeDocumentsWithMissingFields() throws Exception { checkQuerySnapshotContainsDocuments(query5, "doc1", "doc2", "doc4", "doc5"); } - @Test - public void orQueryWithInAndNotIn() 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 query1 = - collection.where( - Filter.or(Filter.equalTo("a", 2), Filter.inArray("b", Arrays.asList(2, 3)))); - checkQuerySnapshotContainsDocuments(query1, "doc3", "doc4", "doc6"); - - // a==2 || (b != 2 && b != 3) - // Has implicit "orderBy b" - Query query2 = - collection.where( - Filter.or(Filter.equalTo("a", 2), Filter.notInArray("b", Arrays.asList(2, 3)))); - checkQuerySnapshotContainsDocuments(query2, "doc1", "doc2"); - } - @Test public void orQueriesWithInAndNotIn() throws ExecutionException, InterruptedException, TimeoutException { From 86e64daa0e0d5216854ce6cf929d05daab89e4a6 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Tue, 10 Jan 2023 23:12:15 +0000 Subject: [PATCH 04/16] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0bd4aa4d72..e1ece2778e 100644 --- a/README.md +++ b/README.md @@ -49,20 +49,20 @@ 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.1.4') +implementation platform('com.google.cloud:libraries-bom:26.3.0') 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.7.1' +implementation 'com.google.cloud:google-cloud-firestore:3.7.4' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "3.7.1" +libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "3.7.4" ``` ## Authentication From 498808c6d1fdd470f4664576d4e53a1a6ad31895 Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Wed, 15 Feb 2023 13:39:20 -0800 Subject: [PATCH 05/16] Target backend for integration tests. --- .../test/java/com/google/cloud/firestore/it/ITQueryTest.java | 3 --- 1 file changed, 3 deletions(-) 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 index 257da72c4c..44b33b95e7 100644 --- 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 @@ -23,7 +23,6 @@ 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; @@ -53,8 +52,6 @@ public class ITQueryTest { @Before public void setUpFirestore() { - // TODO: stop using emulator for these tests once prod is ready. - firestore = FirestoreOptions.newBuilder().setHost("localhost:8080").build().getService(); Preconditions.checkNotNull( firestore, "Error instantiating Firestore. Check that the service account credentials were properly set."); From c05d33ceea68b5b10991df2f357bba5cf0869918 Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Wed, 15 Feb 2023 15:06:40 -0800 Subject: [PATCH 06/16] Fix: uninitialized 'firestore' variable. --- .../test/java/com/google/cloud/firestore/it/ITQueryTest.java | 2 ++ 1 file changed, 2 insertions(+) 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 index 44b33b95e7..23187ebd46 100644 --- 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 @@ -23,6 +23,7 @@ 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; @@ -52,6 +53,7 @@ public class ITQueryTest { @Before public void setUpFirestore() { + firestore = FirestoreOptions.newBuilder().build().getService(); Preconditions.checkNotNull( firestore, "Error instantiating Firestore. Check that the service account credentials were properly set."); From 94163ec9cacbc09209bb8969130990770f0365f0 Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Wed, 15 Feb 2023 16:43:57 -0800 Subject: [PATCH 07/16] Add the docs. --- .../com/google/cloud/firestore/Filter.java | 158 ++++++++++++++++++ .../com/google/cloud/firestore/Query.java | 6 + 2 files changed, 164 insertions(+) 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 854ea9abec..bed532dced 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 @@ -70,111 +70,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) { 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 65630f3e89..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,6 +888,12 @@ public Query whereNotIn(@Nonnull FieldPath fieldPath, @Nonnull List Date: Thu, 16 Feb 2023 21:52:07 +0000 Subject: [PATCH 08/16] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 19aaba5c82..1c7eafd9c0 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.7.10' +implementation 'com.google.cloud:google-cloud-firestore:3.8.0' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "3.7.10" +libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "3.8.0" ``` ## Authentication From b412f2fefb27da8d3a21a274526cce5caa460507 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Fri, 17 Feb 2023 22:13:08 +0000 Subject: [PATCH 09/16] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 267a3b54bf..9d06935cce 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.0' +implementation 'com.google.cloud:google-cloud-firestore:3.8.1' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "3.8.0" +libraryDependencies += "com.google.cloud" % "google-cloud-firestore" % "3.8.1" ``` ## Authentication From 4e1e371fbc75956027fa69f4f3395cdb6af591d0 Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Mon, 27 Feb 2023 11:28:52 -0800 Subject: [PATCH 10/16] Add test dependency. --- google-cloud-firestore/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/google-cloud-firestore/pom.xml b/google-cloud-firestore/pom.xml index df2a405553..4ffc56f990 100644 --- a/google-cloud-firestore/pom.xml +++ b/google-cloud-firestore/pom.xml @@ -177,6 +177,11 @@ 3.12.0 test + + com.google.http-client + google-http-client + test + From 4dd8047813702e7dbd127c725ab26020006326d3 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Mon, 27 Feb 2023 19:31:41 +0000 Subject: [PATCH 11/16] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63c7805aae..2a267df19d 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.8.0') +implementation platform('com.google.cloud:libraries-bom:26.9.0') implementation 'com.google.cloud:google-cloud-firestore' ``` From 14a92ef7a6386ca72be90d827793887197752665 Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Fri, 3 Mar 2023 14:43:18 -0800 Subject: [PATCH 12/16] Remove tests that require composite index and unsupported cases. --- .../cloud/firestore/it/ITQueryTest.java | 76 +------------------ 1 file changed, 2 insertions(+), 74 deletions(-) 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 index 23187ebd46..1794d4c2ca 100644 --- 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 @@ -26,7 +26,6 @@ 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; @@ -120,13 +119,6 @@ public void orQueries() throws Exception { "doc4", "doc5"); - // with one inequality: a>2 || b==1. - checkQuerySnapshotContainsDocuments( - collection.where(Filter.or(Filter.greaterThan("a", 2), Filter.equalTo("b", 1))), - "doc5", - "doc2", - "doc3"); - // (a==1 && b==0) || (a==3 && b==2) checkQuerySnapshotContainsDocuments( collection.where( @@ -152,47 +144,7 @@ public void orQueries() throws Exception { Filter.or(Filter.equalTo("a", 3), Filter.equalTo("b", 3)))), "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 with limits without orderBy (the __name__ ordering is the tie breaker). + // 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"); @@ -223,22 +175,6 @@ public void orQueryDoesNotIncludeDocumentsWithMissingFields() throws Exception { 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"); - // 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. @@ -247,8 +183,7 @@ public void orQueryDoesNotIncludeDocumentsWithMissingFields() throws Exception { } @Test - public void orQueriesWithInAndNotIn() - throws ExecutionException, InterruptedException, TimeoutException { + public void orQueriesWithIn() throws ExecutionException, InterruptedException, TimeoutException { Map> testDocs = map( "doc1", map("a", 1, "b", 0), @@ -265,13 +200,6 @@ public void orQueriesWithInAndNotIn() "doc3", "doc4", "doc6"); - - // 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 From 4f9a7f4f48a9a0fba602d0d4c19fd093f7d6aa70 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Fri, 3 Mar 2023 22:45:49 +0000 Subject: [PATCH 13/16] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2a267df19d..0d9fd1cdb1 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 From f49ce68f82b9cd73d6607c903fa8b7c9e25ea002 Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Fri, 3 Mar 2023 14:48:16 -0800 Subject: [PATCH 14/16] Remove the 'hide' annotation. --- .../src/main/java/com/google/cloud/firestore/Filter.java | 2 -- 1 file changed, 2 deletions(-) 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 bed532dced..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 @@ -17,14 +17,12 @@ package com.google.cloud.firestore; import com.google.firestore.v1.StructuredQuery; -import com.google.firestore.v1.StructuredQuery.CompositeFilter; import com.google.firestore.v1.StructuredQuery.FieldFilter.Operator; import java.util.Arrays; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; -/** @hide */ public class Filter { static class UnaryFilter extends Filter { private final FieldPath field; From f0445af455671a81db9e404287d6289aeb8ef7ee Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Fri, 3 Mar 2023 15:40:15 -0800 Subject: [PATCH 15/16] Rearrange/add tests. --- .../cloud/firestore/it/ITQueryTest.java | 244 +++++++++++++++++- 1 file changed, 238 insertions(+), 6 deletions(-) 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 index 1794d4c2ca..8ed74550b2 100644 --- 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 @@ -16,8 +16,10 @@ 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; @@ -26,6 +28,7 @@ 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; @@ -119,6 +122,13 @@ public void orQueries() throws Exception { "doc4", "doc5"); + // with one inequality: a>2 || b==1. + checkQuerySnapshotContainsDocuments( + collection.where(Filter.or(Filter.greaterThan("a", 2), Filter.equalTo("b", 1))), + "doc5", + "doc2", + "doc3"); + // (a==1 && b==0) || (a==3 && b==2) checkQuerySnapshotContainsDocuments( collection.where( @@ -144,12 +154,115 @@ public void orQueries() throws Exception { Filter.or(Filter.equalTo("a", 3), Filter.equalTo("b", 3)))), "doc3"); - // Test with limits without orderBy (the __name__ ordering is the tiebreaker). + // 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 with limits without orderBy (the __name__ ordering is the tie breaker). 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 = @@ -163,6 +276,29 @@ public void orQueryDoesNotIncludeDocumentsWithMissingFields() throws Exception { 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 = @@ -175,11 +311,21 @@ public void orQueryDoesNotIncludeDocumentsWithMissingFields() throws Exception { collection.where(Filter.or(Filter.equalTo("a", 1), Filter.equalTo("b", 1))).orderBy("b"); checkQuerySnapshotContainsDocuments(query2, "doc1", "doc2", "doc4"); - // 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 query5 = collection.where(Filter.or(Filter.equalTo("a", 1), Filter.equalTo("b", 1))); - checkQuerySnapshotContainsDocuments(query5, "doc1", "doc2", "doc4", "doc5"); + // 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 @@ -202,6 +348,30 @@ public void orQueriesWithIn() throws ExecutionException, InterruptedException, T "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 { @@ -230,4 +400,66 @@ public void orQueriesWithArrayMembership() "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"); + } } From 99463b0814c9c7bc5fc74d346d29459f1d2a2665 Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Fri, 3 Mar 2023 15:49:38 -0800 Subject: [PATCH 16/16] remove duplicate test which is run only against emulator. --- .../cloud/firestore/it/ITQueryTest.java | 49 +------------------ 1 file changed, 1 insertion(+), 48 deletions(-) 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 index 8ed74550b2..9707cb25e1 100644 --- 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 @@ -122,13 +122,6 @@ public void orQueries() throws Exception { "doc4", "doc5"); - // with one inequality: a>2 || b==1. - checkQuerySnapshotContainsDocuments( - collection.where(Filter.or(Filter.greaterThan("a", 2), Filter.equalTo("b", 1))), - "doc5", - "doc2", - "doc3"); - // (a==1 && b==0) || (a==3 && b==2) checkQuerySnapshotContainsDocuments( collection.where( @@ -154,47 +147,7 @@ public void orQueries() throws Exception { Filter.or(Filter.equalTo("a", 3), Filter.equalTo("b", 3)))), "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 with limits without orderBy (the __name__ ordering is the tie breaker). + // 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");