Skip to content

Commit 6879f54

Browse files
author
Brian Chen
authored
Make != and NOT_in filters public (firebase#1975)
1 parent 4714311 commit 6879f54

File tree

7 files changed

+88
-63
lines changed

7 files changed

+88
-63
lines changed

firebase-firestore/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
# Unreleased
2+
- [feature] Added `Query.whereNotIn()` and `Query.whereNotEqualTo()` query
3+
operators. `Query.whereNotIn()` finds documents where a specified field’s
4+
value is not in a specified array. `Query.whereNotEqualTo()` finds
5+
documents where a specified field's value does not equal the specified value.
6+
Neither query operator will match documents where the specified field is not
7+
present.
8+
9+
# 21.6.0
210
- [fixed] Removed a delay that may have prevented Firestore from immediately
311
reestablishing a network connection if a connectivity change occurred while
412
the app was in the background.

firebase-firestore/api.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,10 @@ package com.google.firebase.firestore {
270270
method @NonNull public com.google.firebase.firestore.Query whereLessThan(@NonNull com.google.firebase.firestore.FieldPath, @NonNull Object);
271271
method @NonNull public com.google.firebase.firestore.Query whereLessThanOrEqualTo(@NonNull String, @NonNull Object);
272272
method @NonNull public com.google.firebase.firestore.Query whereLessThanOrEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @NonNull Object);
273+
method @NonNull public com.google.firebase.firestore.Query whereNotEqualTo(@NonNull String, @Nullable Object);
274+
method @NonNull public com.google.firebase.firestore.Query whereNotEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
275+
method @NonNull public com.google.firebase.firestore.Query whereNotIn(@NonNull String, @NonNull java.util.List<?>);
276+
method @NonNull public com.google.firebase.firestore.Query whereNotIn(@NonNull com.google.firebase.firestore.FieldPath, @NonNull java.util.List<?>);
273277
}
274278

275279
public enum Query.Direction {

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

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import java.util.Map;
4242
import java.util.concurrent.Semaphore;
4343
import org.junit.After;
44-
import org.junit.Ignore;
4544
import org.junit.Test;
4645
import org.junit.runner.RunWith;
4746

@@ -486,20 +485,20 @@ public void testQueriesFireFromCacheWhenOffline() {
486485
listener.remove();
487486
}
488487

489-
// TODO(ne-queries): Re-enable once emulator support is added to CI.
490-
@Ignore
491488
@Test
492489
public void testQueriesCanUseNotEqualFilters() {
493-
Map<String, Object> docA = map("zip", 98101L);
490+
// These documents are ordered by value in "zip" since the notEquals filter is an inequality,
491+
// which results in documents being sorted by value.
492+
Map<String, Object> docA = map("zip", Double.NaN);
494493
Map<String, Object> docB = map("zip", 91102L);
495-
Map<String, Object> docC = map("zip", "98101");
496-
Map<String, Object> docD = map("zip", asList(98101L));
497-
Map<String, Object> docE = map("zip", asList("98101", map("zip", 98101L)));
498-
Map<String, Object> docF = map("zip", map("code", 500L));
499-
Map<String, Object> docG = map("zip", asList(98101L, 98102L));
500-
Map<String, Object> docH = map("code", 500L);
501-
Map<String, Object> docI = map("zip", null);
502-
Map<String, Object> docJ = map("zip", Double.NaN);
494+
Map<String, Object> docC = map("zip", 98101L);
495+
Map<String, Object> docD = map("zip", "98101");
496+
Map<String, Object> docE = map("zip", asList(98101L));
497+
Map<String, Object> docF = map("zip", asList(98101L, 98102L));
498+
Map<String, Object> docG = map("zip", asList("98101", map("zip", 98101L)));
499+
Map<String, Object> docH = map("zip", map("code", 500L));
500+
Map<String, Object> docI = map("code", 500L);
501+
Map<String, Object> docJ = map("zip", null);
503502

504503
Map<String, Map<String, Object>> allDocs =
505504
map(
@@ -509,39 +508,37 @@ public void testQueriesCanUseNotEqualFilters() {
509508

510509
// Search for zips not matching 98101.
511510
Map<String, Map<String, Object>> expectedDocsMap = Maps.newHashMap(allDocs);
512-
expectedDocsMap.remove("a");
513-
expectedDocsMap.remove("h");
511+
expectedDocsMap.remove("c");
514512
expectedDocsMap.remove("i");
513+
expectedDocsMap.remove("j");
515514

516515
QuerySnapshot snapshot = waitFor(collection.whereNotEqualTo("zip", 98101L).get());
517516
assertEquals(Lists.newArrayList(expectedDocsMap.values()), querySnapshotToValues(snapshot));
518517

519518
// With objects.
520519
expectedDocsMap = Maps.newHashMap(allDocs);
521-
expectedDocsMap.remove("f");
522520
expectedDocsMap.remove("h");
523521
expectedDocsMap.remove("i");
522+
expectedDocsMap.remove("j");
524523
snapshot = waitFor(collection.whereNotEqualTo("zip", map("code", 500)).get());
525524
assertEquals(Lists.newArrayList(expectedDocsMap.values()), querySnapshotToValues(snapshot));
526525

527526
// With Null.
528527
expectedDocsMap = Maps.newHashMap(allDocs);
529-
expectedDocsMap.remove("h");
530528
expectedDocsMap.remove("i");
529+
expectedDocsMap.remove("j");
531530
snapshot = waitFor(collection.whereNotEqualTo("zip", null).get());
532531
assertEquals(Lists.newArrayList(expectedDocsMap.values()), querySnapshotToValues(snapshot));
533532

534533
// With NaN.
535534
expectedDocsMap = Maps.newHashMap(allDocs);
536-
expectedDocsMap.remove("h");
535+
expectedDocsMap.remove("a");
537536
expectedDocsMap.remove("i");
538537
expectedDocsMap.remove("j");
539538
snapshot = waitFor(collection.whereNotEqualTo("zip", Double.NaN).get());
540539
assertEquals(Lists.newArrayList(expectedDocsMap.values()), querySnapshotToValues(snapshot));
541540
}
542541

543-
// TODO(ne-queries): Re-enable once emulator support is added to CI.
544-
@Ignore
545542
@Test
546543
public void testQueriesCanUseNotEqualFiltersWithDocIds() {
547544
Map<String, String> docA = map("key", "aa");
@@ -617,20 +614,20 @@ public void testQueriesCanUseInFiltersWithDocIds() {
617614
assertEquals(asList(docA, docB), querySnapshotToValues(docs));
618615
}
619616

620-
// TODO(ne-queries): Re-enable once emulator support is added to CI.
621-
@Ignore
622617
@Test
623618
public void testQueriesCanUseNotInFilters() {
624-
Map<String, Object> docA = map("zip", 98101L);
619+
// These documents are ordered by value in "zip" since the notEquals filter is an inequality,
620+
// which results in documents being sorted by value.
621+
Map<String, Object> docA = map("zip", Double.NaN);
625622
Map<String, Object> docB = map("zip", 91102L);
626-
Map<String, Object> docC = map("zip", 98103L);
627-
Map<String, Object> docD = map("zip", asList(98101L));
628-
Map<String, Object> docE = map("zip", asList("98101", map("zip", 98101L)));
629-
Map<String, Object> docF = map("zip", map("code", 500L));
630-
Map<String, Object> docG = map("zip", asList(98101L, 98102L));
631-
Map<String, Object> docH = map("code", 500L);
632-
Map<String, Object> docI = map("zip", null);
633-
Map<String, Object> docJ = map("zip", Double.NaN);
623+
Map<String, Object> docC = map("zip", 98101L);
624+
Map<String, Object> docD = map("zip", 98103L);
625+
Map<String, Object> docE = map("zip", asList(98101L));
626+
Map<String, Object> docF = map("zip", asList(98101L, 98102L));
627+
Map<String, Object> docG = map("zip", asList("98101", map("zip", 98101L)));
628+
Map<String, Object> docH = map("zip", map("code", 500L));
629+
Map<String, Object> docI = map("code", 500L);
630+
Map<String, Object> docJ = map("zip", null);
634631

635632
Map<String, Map<String, Object>> allDocs =
636633
map(
@@ -640,19 +637,21 @@ public void testQueriesCanUseNotInFilters() {
640637

641638
// Search for zips not matching 98101, 98103, or [98101, 98102].
642639
Map<String, Map<String, Object>> expectedDocsMap = Maps.newHashMap(allDocs);
643-
expectedDocsMap.remove("a");
644640
expectedDocsMap.remove("c");
645-
expectedDocsMap.remove("g");
646-
expectedDocsMap.remove("h");
641+
expectedDocsMap.remove("d");
642+
expectedDocsMap.remove("f");
643+
expectedDocsMap.remove("i");
644+
expectedDocsMap.remove("j");
647645

648646
QuerySnapshot snapshot =
649647
waitFor(collection.whereNotIn("zip", asList(98101L, 98103L, asList(98101L, 98102L))).get());
650648
assertEquals(Lists.newArrayList(expectedDocsMap.values()), querySnapshotToValues(snapshot));
651649

652650
// With objects.
653651
expectedDocsMap = Maps.newHashMap(allDocs);
654-
expectedDocsMap.remove("f");
655652
expectedDocsMap.remove("h");
653+
expectedDocsMap.remove("i");
654+
expectedDocsMap.remove("j");
656655
snapshot = waitFor(collection.whereNotIn("zip", asList(map("code", 500L))).get());
657656
assertEquals(Lists.newArrayList(expectedDocsMap.values()), querySnapshotToValues(snapshot));
658657

@@ -664,22 +663,22 @@ public void testQueriesCanUseNotInFilters() {
664663

665664
// With NaN.
666665
expectedDocsMap = Maps.newHashMap(allDocs);
667-
expectedDocsMap.remove("h");
666+
expectedDocsMap.remove("a");
667+
expectedDocsMap.remove("i");
668668
expectedDocsMap.remove("j");
669669
snapshot = waitFor(collection.whereNotIn("zip", asList(Double.NaN)).get());
670670
assertEquals(Lists.newArrayList(expectedDocsMap.values()), querySnapshotToValues(snapshot));
671671

672672
// With NaN and a number.
673673
expectedDocsMap = Maps.newHashMap(allDocs);
674674
expectedDocsMap.remove("a");
675-
expectedDocsMap.remove("h");
675+
expectedDocsMap.remove("c");
676+
expectedDocsMap.remove("i");
676677
expectedDocsMap.remove("j");
677678
snapshot = waitFor(collection.whereNotIn("zip", asList(Float.NaN, 98101L)).get());
678679
assertEquals(Lists.newArrayList(expectedDocsMap.values()), querySnapshotToValues(snapshot));
679680
}
680681

681-
// TODO(ne-queries): Re-enable once emulator support is added to CI.
682-
@Ignore
683682
@Test
684683
public void testQueriesCanUseNotInFiltersWithDocIds() {
685684
Map<String, String> docA = map("key", "aa");

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -414,10 +414,12 @@ public void queriesWithNullOrNaNFiltersOtherThanEqualityFail() {
414414
CollectionReference collection = testCollection();
415415
expectError(
416416
() -> collection.whereGreaterThan("a", null),
417-
"Invalid Query. Null supports only equality comparisons (via whereEqualTo()).");
417+
"Invalid Query. Null only supports comparisons via "
418+
+ "whereEqualTo() and whereNotEqualTo().");
418419
expectError(
419420
() -> collection.whereArrayContains("a", null),
420-
"Invalid Query. Null supports only equality comparisons (via whereEqualTo()).");
421+
"Invalid Query. Null only supports comparisons via "
422+
+ "whereEqualTo() and whereNotEqualTo().");
421423
expectError(
422424
() -> collection.whereArrayContainsAny("a", null),
423425
"Invalid Query. A non-empty array is required for 'array_contains_any' filters.");
@@ -430,10 +432,12 @@ public void queriesWithNullOrNaNFiltersOtherThanEqualityFail() {
430432

431433
expectError(
432434
() -> collection.whereGreaterThan("a", Double.NaN),
433-
"Invalid Query. NaN supports only equality comparisons (via whereEqualTo()).");
435+
"Invalid Query. NaN only supports comparisons via "
436+
+ "whereEqualTo() and whereNotEqualTo().");
434437
expectError(
435438
() -> collection.whereArrayContains("a", Double.NaN),
436-
"Invalid Query. NaN supports only equality comparisons (via whereEqualTo()).");
439+
"Invalid Query. NaN only supports comparisons via "
440+
+ "whereEqualTo() and whereNotEqualTo().");
437441
}
438442

439443
@Test
@@ -544,8 +548,9 @@ public void queryOrderByKeyBoundsMustBeStringsWithoutSlashes() {
544548
public void queriesWithDifferentInequalityFieldsFail() {
545549
expectError(
546550
() -> testCollection().whereGreaterThan("x", 32).whereLessThan("y", "cat"),
547-
"All where filters other than whereEqualTo() must be on the same field. But you "
548-
+ "have filters on 'x' and 'y'");
551+
"All where filters with an inequality (notEqualTo, notIn, lessThan, "
552+
+ "lessThanOrEqualTo, greaterThan, or greaterThanOrEqualTo) must be on the "
553+
+ "same field. But you have filters on 'x' and 'y'");
549554
}
550555

551556
@Test
@@ -571,8 +576,9 @@ public void queriesWithMultipleNotEqualAndInequalitiesFail() {
571576

572577
expectError(
573578
() -> testCollection().whereNotEqualTo("x", 32).whereGreaterThan("y", 33),
574-
"All where filters other than whereEqualTo() must be on the same field. But you "
575-
+ "have filters on 'x' and 'y'");
579+
"All where filters with an inequality (notEqualTo, notIn, lessThan, "
580+
+ "lessThanOrEqualTo, greaterThan, or greaterThanOrEqualTo) must be on the "
581+
+ "same field. But you have filters on 'x' and 'y'");
576582
}
577583

578584
@Test
@@ -619,7 +625,9 @@ public void queriesWithMultipleDisjunctiveFiltersFail() {
619625

620626
expectError(
621627
() -> testCollection().whereNotIn("foo", asList(1, 2)).whereNotIn("bar", asList(1, 2)),
622-
"Invalid Query. You cannot use more than one 'not_in' filter.");
628+
"All where filters with an inequality (notEqualTo, notIn, lessThan, "
629+
+ "lessThanOrEqualTo, greaterThan, or greaterThanOrEqualTo) must be on the "
630+
+ "same field. But you have filters on 'foo' and 'bar'");
623631

624632
expectError(
625633
() ->

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

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,7 @@ public Query whereEqualTo(@NonNull FieldPath fieldPath, @Nullable Object value)
119119
* @return The created {@code Query}.
120120
*/
121121
@NonNull
122-
// TODO(ne-queries): Make method public once backend is ready.
123-
Query whereNotEqualTo(@NonNull String field, @Nullable Object value) {
122+
public Query whereNotEqualTo(@NonNull String field, @Nullable Object value) {
124123
return whereHelper(FieldPath.fromDotSeparatedPath(field), Operator.NOT_EQUAL, value);
125124
}
126125

@@ -136,8 +135,7 @@ Query whereNotEqualTo(@NonNull String field, @Nullable Object value) {
136135
* @return The created {@code Query}.
137136
*/
138137
@NonNull
139-
// TODO(ne-queries): Make method public once backend is ready.
140-
Query whereNotEqualTo(@NonNull FieldPath fieldPath, @Nullable Object value) {
138+
public Query whereNotEqualTo(@NonNull FieldPath fieldPath, @Nullable Object value) {
141139
return whereHelper(fieldPath, Operator.NOT_EQUAL, value);
142140
}
143141

@@ -352,6 +350,10 @@ public Query whereIn(@NonNull FieldPath fieldPath, @NonNull List<? extends Objec
352350
* Creates and returns a new {@code Query} with the additional filter that documents must contain
353351
* the specified field and the value does not equal any of the values from the provided list.
354352
*
353+
* <p>One special case is that {@coce whereNotIn} cannot match {@code null} values. To query for
354+
* documents where a field exists and is {@code null}, use {@code whereNotEqualTo}, which can
355+
* handle this special case.
356+
*
355357
* <p>A {@code Query} can have only one {@code whereNotIn()} filter, and it cannot be combined
356358
* with {@code whereArrayContains()}, {@code whereArrayContainsAny()}, {@code whereIn()}, or
357359
* {@code whereNotEqualTo()}.
@@ -361,15 +363,18 @@ public Query whereIn(@NonNull FieldPath fieldPath, @NonNull List<? extends Objec
361363
* @return The created {@code Query}.
362364
*/
363365
@NonNull
364-
// TODO(ne-queries): Make method public once backend is ready.
365-
Query whereNotIn(@NonNull String field, @NonNull List<? extends Object> values) {
366+
public Query whereNotIn(@NonNull String field, @NonNull List<? extends Object> values) {
366367
return whereHelper(FieldPath.fromDotSeparatedPath(field), Operator.NOT_IN, values);
367368
}
368369

369370
/**
370371
* Creates and returns a new {@code Query} with the additional filter that documents must contain
371372
* the specified field and the value does not equal any of the values from the provided list.
372373
*
374+
* <p>One special case is that {@coce whereNotIn} cannot match {@code null} values. To query for
375+
* documents where a field exists and is {@code null}, use {@code whereNotEqualTo}, which can
376+
* handle this special case.
377+
*
373378
* <p>A {@code Query} can have only one {@code whereNotIn()} filter, and it cannot be combined
374379
* with {@code whereArrayContains()}, {@code whereArrayContainsAny()}, {@code whereIn()}, or
375380
* {@code whereNotEqualTo()}.
@@ -379,8 +384,7 @@ Query whereNotIn(@NonNull String field, @NonNull List<? extends Object> values)
379384
* @return The created {@code Query}.
380385
*/
381386
@NonNull
382-
// TODO(ne-queries): Make method public once backend is ready.
383-
Query whereNotIn(@NonNull FieldPath fieldPath, @NonNull List<? extends Object> values) {
387+
public Query whereNotIn(@NonNull FieldPath fieldPath, @NonNull List<? extends Object> values) {
384388
return whereHelper(fieldPath, Operator.NOT_IN, values);
385389
}
386390

@@ -569,8 +573,9 @@ private void validateNewFilter(Filter filter) {
569573
if (existingInequality != null && !existingInequality.equals(newInequality)) {
570574
throw new IllegalArgumentException(
571575
String.format(
572-
"All where filters other than whereEqualTo() must be on the same field. But you "
573-
+ "have filters on '%s' and '%s'",
576+
"All where filters with an inequality (notEqualTo, notIn, lessThan, "
577+
+ "lessThanOrEqualTo, greaterThan, or greaterThanOrEqualTo) must be on the "
578+
+ "same field. But you have filters on '%s' and '%s'",
574579
existingInequality.canonicalString(), newInequality.canonicalString()));
575580
}
576581
com.google.firebase.firestore.model.FieldPath firstOrderByField =

firebase-firestore/src/main/java/com/google/firebase/firestore/core/FieldFilter.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,17 +73,17 @@ public static FieldFilter create(FieldPath path, Operator operator, Value value)
7373
return new KeyFieldFilter(path, operator, value);
7474
}
7575
} else if (Values.isNullValue(value)) {
76-
// TODO(ne-queries): Update error message to include != operator.
7776
if (operator != Operator.EQUAL && operator != Operator.NOT_EQUAL) {
7877
throw new IllegalArgumentException(
79-
"Invalid Query. Null supports only equality comparisons (via whereEqualTo()).");
78+
"Invalid Query. Null only supports comparisons via "
79+
+ "whereEqualTo() and whereNotEqualTo().");
8080
}
8181
return new FieldFilter(path, operator, value);
8282
} else if (Values.isNanValue(value)) {
83-
// TODO(ne-queries): Update error message to include != operator.
8483
if (operator != Operator.EQUAL && operator != Operator.NOT_EQUAL) {
8584
throw new IllegalArgumentException(
86-
"Invalid Query. NaN supports only equality comparisons (via whereEqualTo()).");
85+
"Invalid Query. NaN only supports comparisons via "
86+
+ "whereEqualTo() and whereNotEqualTo().");
8787
}
8888
return new FieldFilter(path, operator, value);
8989
} else if (operator == Operator.ARRAY_CONTAINS) {
@@ -137,7 +137,8 @@ public boolean isInequality() {
137137
Operator.LESS_THAN_OR_EQUAL,
138138
Operator.GREATER_THAN,
139139
Operator.GREATER_THAN_OR_EQUAL,
140-
Operator.NOT_EQUAL)
140+
Operator.NOT_EQUAL,
141+
Operator.NOT_IN)
141142
.contains(operator);
142143
}
143144

firebase-firestore/src/test/java/com/google/firebase/firestore/core/QueryTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,7 @@ public void testCanonicalIdsAreStable() {
675675
"collection|f:ain[1,2,3]|ob:__name__asc");
676676
assertCanonicalId(
677677
baseQuery.filter(filter("a", "not-in", Arrays.asList(1, 2, 3))),
678-
"collection|f:anot_in[1,2,3]|ob:__name__asc");
678+
"collection|f:anot_in[1,2,3]|ob:aasc__name__asc");
679679
assertCanonicalId(
680680
baseQuery.filter(filter("a", "array-contains-any", Arrays.asList(1, 2, 3))),
681681
"collection|f:aarray_contains_any[1,2,3]|ob:__name__asc");

0 commit comments

Comments
 (0)