Skip to content

Commit 8d99ce0

Browse files
authored
Make OR APIs public (#4692)
* Make OR APIs public. * Enable tests and separate out tests that require composite indexes. * Update api.txt. * Disable two more tests against production.
1 parent 46e7a69 commit 8d99ce0

File tree

5 files changed

+99
-91
lines changed

5 files changed

+99
-91
lines changed

firebase-firestore/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Unreleased
2+
* [feature] Add support for disjunctions in queries (`OR` queries).
23

34
# 24.4.4
45
* [changed] Relaxed certain query validations performed by the SDK (#4231).

firebase-firestore/api.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,32 @@ package com.google.firebase.firestore {
145145
method @NonNull public static com.google.firebase.firestore.FieldValue serverTimestamp();
146146
}
147147

148+
public class Filter {
149+
ctor public Filter();
150+
method @NonNull public static com.google.firebase.firestore.Filter and(com.google.firebase.firestore.Filter...);
151+
method @NonNull public static com.google.firebase.firestore.Filter arrayContains(@NonNull String, @Nullable Object);
152+
method @NonNull public static com.google.firebase.firestore.Filter arrayContains(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
153+
method @NonNull public static com.google.firebase.firestore.Filter arrayContainsAny(@NonNull String, @NonNull java.util.List<?>);
154+
method @NonNull public static com.google.firebase.firestore.Filter arrayContainsAny(@NonNull com.google.firebase.firestore.FieldPath, @NonNull java.util.List<?>);
155+
method @NonNull public static com.google.firebase.firestore.Filter equalTo(@NonNull String, @Nullable Object);
156+
method @NonNull public static com.google.firebase.firestore.Filter equalTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
157+
method @NonNull public static com.google.firebase.firestore.Filter greaterThan(@NonNull String, @Nullable Object);
158+
method @NonNull public static com.google.firebase.firestore.Filter greaterThan(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
159+
method @NonNull public static com.google.firebase.firestore.Filter greaterThanOrEqualTo(@NonNull String, @Nullable Object);
160+
method @NonNull public static com.google.firebase.firestore.Filter greaterThanOrEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
161+
method @NonNull public static com.google.firebase.firestore.Filter inArray(@NonNull String, @NonNull java.util.List<?>);
162+
method @NonNull public static com.google.firebase.firestore.Filter inArray(@NonNull com.google.firebase.firestore.FieldPath, @NonNull java.util.List<?>);
163+
method @NonNull public static com.google.firebase.firestore.Filter lessThan(@NonNull String, @Nullable Object);
164+
method @NonNull public static com.google.firebase.firestore.Filter lessThan(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
165+
method @NonNull public static com.google.firebase.firestore.Filter lessThanOrEqualTo(@NonNull String, @Nullable Object);
166+
method @NonNull public static com.google.firebase.firestore.Filter lessThanOrEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
167+
method @NonNull public static com.google.firebase.firestore.Filter notEqualTo(@NonNull String, @Nullable Object);
168+
method @NonNull public static com.google.firebase.firestore.Filter notEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
169+
method @NonNull public static com.google.firebase.firestore.Filter notInArray(@NonNull String, @NonNull java.util.List<?>);
170+
method @NonNull public static com.google.firebase.firestore.Filter notInArray(@NonNull com.google.firebase.firestore.FieldPath, @NonNull java.util.List<?>);
171+
method @NonNull public static com.google.firebase.firestore.Filter or(com.google.firebase.firestore.Filter...);
172+
}
173+
148174
public class FirebaseFirestore {
149175
method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotsInSyncListener(@NonNull Runnable);
150176
method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotsInSyncListener(@NonNull android.app.Activity, @NonNull Runnable);
@@ -322,6 +348,7 @@ package com.google.firebase.firestore {
322348
method @NonNull public com.google.firebase.firestore.Query startAfter(java.lang.Object...);
323349
method @NonNull public com.google.firebase.firestore.Query startAt(@NonNull com.google.firebase.firestore.DocumentSnapshot);
324350
method @NonNull public com.google.firebase.firestore.Query startAt(java.lang.Object...);
351+
method @NonNull public com.google.firebase.firestore.Query where(@NonNull com.google.firebase.firestore.Filter);
325352
method @NonNull public com.google.firebase.firestore.Query whereArrayContains(@NonNull String, @NonNull Object);
326353
method @NonNull public com.google.firebase.firestore.Query whereArrayContains(@NonNull com.google.firebase.firestore.FieldPath, @NonNull Object);
327354
method @NonNull public com.google.firebase.firestore.Query whereArrayContainsAny(@NonNull String, @NonNull java.util.List<?>);

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/QueryTest.java

Lines changed: 69 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package com.google.firebase.firestore;
1616

17+
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.isRunningAgainstEmulator;
1718
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.nullList;
1819
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.querySnapshotToIds;
1920
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.querySnapshotToValues;
@@ -29,6 +30,7 @@
2930
import static org.junit.Assert.assertFalse;
3031
import static org.junit.Assert.assertNull;
3132
import static org.junit.Assert.assertTrue;
33+
import static org.junit.Assume.assumeTrue;
3234

3335
import androidx.test.ext.junit.runners.AndroidJUnit4;
3436
import com.google.android.gms.tasks.Task;
@@ -42,7 +44,6 @@
4244
import java.util.Map;
4345
import java.util.concurrent.Semaphore;
4446
import org.junit.After;
45-
import org.junit.Ignore;
4647
import org.junit.Test;
4748
import org.junit.runner.RunWith;
4849

@@ -1029,8 +1030,6 @@ public void testMultipleUpdatesWhileOffline() {
10291030
assertEquals(asList(map("foo", "zzyzx", "bar", "2")), querySnapshotToValues(snapshot2));
10301031
}
10311032

1032-
// TODO(orquery): Enable this test when prod supports OR queries.
1033-
@Ignore
10341033
@Test
10351034
public void testOrQueries() {
10361035
Map<String, Map<String, Object>> testDocs =
@@ -1050,13 +1049,6 @@ public void testOrQueries() {
10501049
"doc4",
10511050
"doc5");
10521051

1053-
// with one inequality: a>2 || b==1.
1054-
checkOnlineAndOfflineResultsMatch(
1055-
collection.where(Filter.or(Filter.greaterThan("a", 2), Filter.equalTo("b", 1))),
1056-
"doc5",
1057-
"doc2",
1058-
"doc3");
1059-
10601052
// (a==1 && b==0) || (a==3 && b==2)
10611053
checkOnlineAndOfflineResultsMatch(
10621054
collection.where(
@@ -1082,6 +1074,35 @@ public void testOrQueries() {
10821074
Filter.or(Filter.equalTo("a", 3), Filter.equalTo("b", 3)))),
10831075
"doc3");
10841076

1077+
// Test with limits without orderBy (the __name__ ordering is the tie breaker).
1078+
checkOnlineAndOfflineResultsMatch(
1079+
collection.where(Filter.or(Filter.equalTo("a", 2), Filter.equalTo("b", 1))).limit(1),
1080+
"doc2");
1081+
}
1082+
1083+
@Test
1084+
public void testOrQueriesWithCompositeIndexes() {
1085+
assumeTrue(
1086+
"Skip this test if running against production because it results in a "
1087+
+ "'missing index' error. The Firestore Emulator, however, does serve these "
1088+
+ " queries.",
1089+
isRunningAgainstEmulator());
1090+
Map<String, Map<String, Object>> testDocs =
1091+
map(
1092+
"doc1", map("a", 1, "b", 0),
1093+
"doc2", map("a", 2, "b", 1),
1094+
"doc3", map("a", 3, "b", 2),
1095+
"doc4", map("a", 1, "b", 3),
1096+
"doc5", map("a", 1, "b", 1));
1097+
CollectionReference collection = testCollectionWithDocs(testDocs);
1098+
1099+
// with one inequality: a>2 || b==1.
1100+
checkOnlineAndOfflineResultsMatch(
1101+
collection.where(Filter.or(Filter.greaterThan("a", 2), Filter.equalTo("b", 1))),
1102+
"doc5",
1103+
"doc2",
1104+
"doc3");
1105+
10851106
// Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2
10861107
checkOnlineAndOfflineResultsMatch(
10871108
collection.where(Filter.or(Filter.equalTo("a", 1), Filter.greaterThan("b", 0))).limit(2),
@@ -1113,17 +1134,10 @@ public void testOrQueries() {
11131134
.limitToLast(1)
11141135
.orderBy("a"),
11151136
"doc2");
1116-
1117-
// Test with limits without orderBy (the __name__ ordering is the tie breaker).
1118-
checkOnlineAndOfflineResultsMatch(
1119-
collection.where(Filter.or(Filter.equalTo("a", 2), Filter.equalTo("b", 1))).limit(1),
1120-
"doc2");
11211137
}
11221138

1123-
// TODO(orquery): Enable this test when prod supports OR queries.
1124-
@Ignore
11251139
@Test
1126-
public void testOrQueriesWithInAndNotIn() {
1140+
public void testOrQueriesWithIn() {
11271141
Map<String, Map<String, Object>> testDocs =
11281142
map(
11291143
"doc1", map("a", 1, "b", 0),
@@ -1140,6 +1154,24 @@ public void testOrQueriesWithInAndNotIn() {
11401154
"doc3",
11411155
"doc4",
11421156
"doc6");
1157+
}
1158+
1159+
@Test
1160+
public void testOrQueriesWithNotIn() {
1161+
assumeTrue(
1162+
"Skip this test if running against production because it results in a "
1163+
+ "'missing index' error. The Firestore Emulator, however, does serve these "
1164+
+ " queries",
1165+
isRunningAgainstEmulator());
1166+
Map<String, Map<String, Object>> testDocs =
1167+
map(
1168+
"doc1", map("a", 1, "b", 0),
1169+
"doc2", map("b", 1),
1170+
"doc3", map("a", 3, "b", 2),
1171+
"doc4", map("a", 1, "b", 3),
1172+
"doc5", map("a", 1),
1173+
"doc6", map("a", 2));
1174+
CollectionReference collection = testCollectionWithDocs(testDocs);
11431175

11441176
// a==2 || b not-in [2,3]
11451177
// Has implicit orderBy b.
@@ -1149,8 +1181,6 @@ public void testOrQueriesWithInAndNotIn() {
11491181
"doc2");
11501182
}
11511183

1152-
// TODO(orquery): Enable this test when prod supports OR queries.
1153-
@Ignore
11541184
@Test
11551185
public void testOrQueriesWithArrayMembership() {
11561186
Map<String, Map<String, Object>> testDocs =
@@ -1179,9 +1209,12 @@ public void testOrQueriesWithArrayMembership() {
11791209
"doc6");
11801210
}
11811211

1182-
@Ignore
11831212
@Test
11841213
public void testMultipleInOps() {
1214+
// TODO(orquery): Enable this test against production when possible.
1215+
assumeTrue(
1216+
"Skip this test if running against production because it's not yet supported.",
1217+
isRunningAgainstEmulator());
11851218
Map<String, Map<String, Object>> testDocs =
11861219
map(
11871220
"doc1", map("a", 1, "b", 0),
@@ -1194,63 +1227,24 @@ public void testMultipleInOps() {
11941227

11951228
// Two IN operations on different fields with disjunction.
11961229
Query query1 =
1197-
collection
1198-
.where(Filter.or(Filter.inArray("a", asList(2, 3)), Filter.inArray("b", asList(0, 2))))
1199-
.orderBy("a");
1200-
checkOnlineAndOfflineResultsMatch(query1, "doc1", "doc6", "doc3");
1201-
1202-
// Two IN operations on different fields with conjunction.
1203-
Query query2 =
1204-
collection
1205-
.where(Filter.and(Filter.inArray("a", asList(2, 3)), Filter.inArray("b", asList(0, 2))))
1206-
.orderBy("a");
1207-
checkOnlineAndOfflineResultsMatch(query2, "doc3");
1208-
1209-
// Two IN operations on the same field.
1210-
// a IN [1,2,3] && a IN [0,1,4] should result in "a==1".
1211-
Query query3 =
12121230
collection.where(
1213-
Filter.and(Filter.inArray("a", asList(1, 2, 3)), Filter.inArray("a", asList(0, 1, 4))));
1214-
checkOnlineAndOfflineResultsMatch(query3, "doc1", "doc4", "doc5");
1215-
1216-
// a IN [2,3] && a IN [0,1,4] is never true and so the result should be an empty set.
1217-
Query query4 =
1218-
collection.where(
1219-
Filter.and(Filter.inArray("a", asList(2, 3)), Filter.inArray("a", asList(0, 1, 4))));
1220-
checkOnlineAndOfflineResultsMatch(query4);
1231+
Filter.or(Filter.inArray("a", asList(2, 3)), Filter.inArray("b", asList(0, 2))));
1232+
checkOnlineAndOfflineResultsMatch(query1, "doc1", "doc3", "doc6");
12211233

1234+
// Two IN operations on the same field with disjunction.
12221235
// a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]).
1223-
Query query5 =
1236+
Query query2 =
12241237
collection.where(
12251238
Filter.or(Filter.inArray("a", asList(0, 3)), Filter.inArray("a", asList(0, 2))));
1226-
checkOnlineAndOfflineResultsMatch(query5, "doc3", "doc6");
1227-
1228-
// Nested composite filter on the same field.
1229-
Query query6 =
1230-
collection.where(
1231-
Filter.and(
1232-
Filter.inArray("a", asList(1, 3)),
1233-
Filter.or(
1234-
Filter.inArray("a", asList(0, 2)),
1235-
Filter.and(
1236-
Filter.greaterThanOrEqualTo("b", 1), Filter.inArray("a", asList(1, 3))))));
1237-
checkOnlineAndOfflineResultsMatch(query6, "doc3", "doc4");
1238-
1239-
// Nested composite filter on different fields.
1240-
Query query7 =
1241-
collection.where(
1242-
Filter.and(
1243-
Filter.inArray("b", asList(0, 3)),
1244-
Filter.or(
1245-
Filter.inArray("b", asList(1)),
1246-
Filter.and(
1247-
Filter.inArray("b", asList(2, 3)), Filter.inArray("a", asList(1, 3))))));
1248-
checkOnlineAndOfflineResultsMatch(query7, "doc4");
1239+
checkOnlineAndOfflineResultsMatch(query2, "doc3", "doc6");
12491240
}
12501241

1251-
@Ignore
12521242
@Test
12531243
public void testUsingInWithArrayContainsAny() {
1244+
// TODO(orquery): Enable this test against production when possible.
1245+
assumeTrue(
1246+
"Skip this test if running against production because it's not yet supported.",
1247+
isRunningAgainstEmulator());
12541248
Map<String, Map<String, Object>> testDocs =
12551249
map(
12561250
"doc1", map("a", 1, "b", asList(0)),
@@ -1268,27 +1262,13 @@ public void testUsingInWithArrayContainsAny() {
12681262
checkOnlineAndOfflineResultsMatch(query1, "doc1", "doc3", "doc4", "doc6");
12691263

12701264
Query query2 =
1271-
collection.where(
1272-
Filter.and(
1273-
Filter.inArray("a", asList(2, 3)), Filter.arrayContainsAny("b", asList(0, 7))));
1274-
checkOnlineAndOfflineResultsMatch(query2, "doc3");
1275-
1276-
Query query3 =
12771265
collection.where(
12781266
Filter.or(
12791267
Filter.and(Filter.inArray("a", asList(2, 3)), Filter.equalTo("c", 10)),
12801268
Filter.arrayContainsAny("b", asList(0, 7))));
1281-
checkOnlineAndOfflineResultsMatch(query3, "doc1", "doc3", "doc4");
1282-
1283-
Query query4 =
1284-
collection.where(
1285-
Filter.and(
1286-
Filter.inArray("a", asList(2, 3)),
1287-
Filter.or(Filter.arrayContainsAny("b", asList(0, 7)), Filter.equalTo("c", 20))));
1288-
checkOnlineAndOfflineResultsMatch(query4, "doc3", "doc6");
1269+
checkOnlineAndOfflineResultsMatch(query2, "doc1", "doc3", "doc4");
12891270
}
12901271

1291-
@Ignore
12921272
@Test
12931273
public void testUsingInWithArrayContains() {
12941274
Map<String, Map<String, Object>> testDocs =
@@ -1326,9 +1306,13 @@ public void testUsingInWithArrayContains() {
13261306
checkOnlineAndOfflineResultsMatch(query4, "doc3");
13271307
}
13281308

1329-
@Ignore
13301309
@Test
13311310
public void testOrderByEquality() {
1311+
// TODO(orquery): Enable this test against production when possible.
1312+
assumeTrue(
1313+
"Skip this test if running against production because order-by-equality is "
1314+
+ "not supported yet.",
1315+
isRunningAgainstEmulator());
13321316
Map<String, Map<String, Object>> testDocs =
13331317
map(
13341318
"doc1", map("a", 1, "b", asList(0)),

firebase-firestore/src/main/java/com/google/firebase/firestore/Filter.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,10 @@
1616

1717
import androidx.annotation.NonNull;
1818
import androidx.annotation.Nullable;
19-
import androidx.annotation.RestrictTo;
2019
import com.google.firebase.firestore.core.FieldFilter.Operator;
2120
import java.util.Arrays;
2221
import java.util.List;
2322

24-
// TODO(orquery): Remove the `hide` and scope annotations.
25-
/** @hide */
26-
@RestrictTo(RestrictTo.Scope.LIBRARY)
2723
/**
2824
* A {@code Filter} represents a restriction on one or more field values and can be used to refine
2925
* the results of a {@code Query}.

firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,14 +388,14 @@ public Query whereNotIn(@NonNull FieldPath fieldPath, @NonNull List<? extends Ob
388388
return where(Filter.notInArray(fieldPath, values));
389389
}
390390

391-
// TODO(orquery): This method will become public API. Change visibility and add documentation.
392391
/**
393392
* Creates and returns a new {@code Query} with the additional filter.
394393
*
395394
* @param filter The new filter to apply to the existing query.
396395
* @return The newly created {@code Query}.
397396
*/
398-
Query where(Filter filter) {
397+
@NonNull
398+
public Query where(@NonNull Filter filter) {
399399
com.google.firebase.firestore.core.Filter parsedFilter = parseFilter(filter);
400400
if (parsedFilter.getFilters().isEmpty()) {
401401
// Return the existing query if not adding any more filters (e.g. an empty composite filter).

0 commit comments

Comments
 (0)