Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for != and NOT_IN queries #350

Merged
merged 6 commits into from Sep 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -25,6 +25,8 @@
import static com.google.firestore.v1.StructuredQuery.FieldFilter.Operator.IN;
import static com.google.firestore.v1.StructuredQuery.FieldFilter.Operator.LESS_THAN;
import static com.google.firestore.v1.StructuredQuery.FieldFilter.Operator.LESS_THAN_OR_EQUAL;
import static com.google.firestore.v1.StructuredQuery.FieldFilter.Operator.NOT_EQUAL;
import static com.google.firestore.v1.StructuredQuery.FieldFilter.Operator.NOT_IN;

import com.google.api.core.ApiFuture;
import com.google.api.core.InternalExtensionOnly;
Expand Down Expand Up @@ -492,6 +494,48 @@ public Query whereEqualTo(@Nonnull FieldPath fieldPath, @Nullable Object value)
}
}

/**
* Creates and returns a new Query with the additional filter that documents must contain the
* specified field and its value does not equal the specified value.
*
* @param field The name of the field to compare.
* @param value The value for comparison.
* @return The created Query.
*/
@Nonnull
public Query whereNotEqualTo(@Nonnull String field, @Nullable Object value) {
return whereNotEqualTo(FieldPath.fromDotSeparatedString(field), value);
}

/**
* Creates and returns a new Query with the additional filter that documents must contain the
* specified field and the value does not equal the specified value.
*
* @param fieldPath The path of the field to compare.
* @param value The value for comparison.
* @return The created Query.
*/
@Nonnull
public Query whereNotEqualTo(@Nonnull FieldPath fieldPath, @Nullable Object value) {
Preconditions.checkState(
options.getStartCursor() == null && options.getEndCursor() == null,
"Cannot call whereNotEqualTo() after defining a boundary with startAt(), "
+ "startAfter(), endBefore() or endAt().");

if (isUnaryComparison(value)) {
Builder newOptions = options.toBuilder();
StructuredQuery.UnaryFilter.Operator op =
value == null
? StructuredQuery.UnaryFilter.Operator.IS_NOT_NULL
: StructuredQuery.UnaryFilter.Operator.IS_NOT_NAN;
UnaryFilter newFieldFilter = new UnaryFilter(fieldPath.toProto(), op);
newOptions.setFieldFilters(append(options.getFieldFilters(), newFieldFilter));
return new Query(rpcContext, newOptions.build());
} else {
return whereHelper(fieldPath, NOT_EQUAL, value);
}
}

/**
* Creates and returns a new Query with the additional filter that documents must contain the
* specified field and the value should be less than the specified value.
Expand Down Expand Up @@ -617,7 +661,8 @@ public Query whereGreaterThanOrEqualTo(@Nonnull FieldPath fieldPath, @Nonnull Ob
* specified field, the value must be an array, and that the array must contain the provided
* value.
*
* <p>A Query can have only one whereArrayContains() filter.
* <p>A Query can have only one whereArrayContains() filter and it cannot be combined with
* whereArrayContainsAny().
*
* @param field The name of the field containing an array to search
* @param value The value that must be contained in the array
Expand All @@ -633,7 +678,8 @@ public Query whereArrayContains(@Nonnull String field, @Nonnull Object value) {
* specified field, the value must be an array, and that the array must contain the provided
* value.
*
* <p>A Query can have only one whereArrayContains() filter.
* <p>A Query can have only one whereArrayContains() filter and it cannot be combined with
* whereArrayContainsAny().
*
* @param fieldPath The path of the field containing an array to search
* @param value The value that must be contained in the array
Expand Down Expand Up @@ -732,6 +778,46 @@ public Query whereIn(@Nonnull FieldPath fieldPath, @Nonnull List<? extends Objec
return whereHelper(fieldPath, IN, values);
}

/**
* Creates and returns a new Query with the additional filter that documents must contain the
* specified field and the value does not equal any of the values from the provided list.
*
* <p>A Query can have only one whereNotIn() filter and it cannot be combined with
* whereArrayContains(), whereArrayContainsAny(), whereIn(), or whereNotEqualTo().
*
* @param field The name of the field to search.
* @param values The list that contains the values to match.
* @return The created Query.
*/
@Nonnull
public Query whereNotIn(@Nonnull String field, @Nonnull List<? extends Object> values) {
Preconditions.checkState(
options.getStartCursor() == null && options.getEndCursor() == null,
"Cannot call whereNotIn() after defining a boundary with startAt(), "
+ "startAfter(), endBefore() or endAt().");
return whereHelper(FieldPath.fromDotSeparatedString(field), NOT_IN, values);
}

/**
* Creates and returns a new Query with the additional filter that documents must contain the
* specified field and the value does not equal any of the values from the provided list.
*
* <p>A Query can have only one whereNotIn() filter, and it cannot be combined with
* whereArrayContains(), whereArrayContainsAny(), whereIn(), or whereNotEqualTo().
*
* @param fieldPath The path of the field to search.
* @param values The list that contains the values to match.
* @return The created Query.
*/
@Nonnull
public Query whereNotIn(@Nonnull FieldPath fieldPath, @Nonnull List<? extends Object> values) {
Preconditions.checkState(
options.getStartCursor() == null && options.getEndCursor() == null,
"Cannot call whereNotIn() after defining a boundary with startAt(), "
+ "startAfter(), endBefore() or endAt().");
return whereHelper(fieldPath, NOT_IN, values);
}

private Query whereHelper(
FieldPath fieldPath, StructuredQuery.FieldFilter.Operator operator, Object value) {
Preconditions.checkArgument(
Expand All @@ -745,7 +831,7 @@ private Query whereHelper(
String.format(
"Invalid query. You cannot perform '%s' queries on FieldPath.documentId().",
operator.toString()));
} else if (operator == IN) {
} else if (operator == IN | operator == NOT_IN) {
if (!(value instanceof List) || ((List<?>) value).isEmpty()) {
throw new IllegalArgumentException(
String.format(
Expand Down
Expand Up @@ -219,27 +219,37 @@ public void withFilter() throws Exception {
query.whereEqualTo("foo", null).get().get();
query.whereEqualTo("foo", Double.NaN).get().get();
query.whereEqualTo("foo", Float.NaN).get().get();
query.whereNotEqualTo("foo", "bar").get().get();
query.whereNotEqualTo("foo", null).get().get();
query.whereNotEqualTo("foo", Double.NaN).get().get();
query.whereNotEqualTo("foo", Float.NaN).get().get();
query.whereGreaterThan("foo", "bar").get().get();
query.whereGreaterThanOrEqualTo("foo", "bar").get().get();
query.whereLessThan("foo", "bar").get().get();
query.whereLessThanOrEqualTo("foo", "bar").get().get();
query.whereArrayContains("foo", "bar").get().get();
query.whereIn("foo", Collections.<Object>singletonList("bar"));
query.whereArrayContainsAny("foo", Collections.<Object>singletonList("bar"));
query.whereNotIn("foo", Collections.<Object>singletonList("bar"));

Iterator<RunQueryRequest> expected =
Arrays.asList(
query(filter(StructuredQuery.FieldFilter.Operator.EQUAL)),
query(unaryFilter(StructuredQuery.UnaryFilter.Operator.IS_NULL)),
query(unaryFilter(StructuredQuery.UnaryFilter.Operator.IS_NAN)),
query(unaryFilter(StructuredQuery.UnaryFilter.Operator.IS_NAN)),
query(filter(StructuredQuery.FieldFilter.Operator.NOT_EQUAL)),
query(unaryFilter(StructuredQuery.UnaryFilter.Operator.IS_NOT_NULL)),
query(unaryFilter(StructuredQuery.UnaryFilter.Operator.IS_NOT_NAN)),
query(unaryFilter(StructuredQuery.UnaryFilter.Operator.IS_NOT_NAN)),
query(filter(StructuredQuery.FieldFilter.Operator.GREATER_THAN)),
query(filter(StructuredQuery.FieldFilter.Operator.GREATER_THAN_OR_EQUAL)),
query(filter(StructuredQuery.FieldFilter.Operator.LESS_THAN)),
query(filter(StructuredQuery.FieldFilter.Operator.LESS_THAN_OR_EQUAL)),
query(filter(StructuredQuery.FieldFilter.Operator.ARRAY_CONTAINS)),
query(filter(StructuredQuery.FieldFilter.Operator.IN)),
query(filter(StructuredQuery.FieldFilter.Operator.ARRAY_CONTAINS_ANY)))
query(filter(StructuredQuery.FieldFilter.Operator.ARRAY_CONTAINS_ANY)),
query(filter(StructuredQuery.FieldFilter.Operator.NOT_IN)))
.iterator();

for (RunQueryRequest actual : runQuery.getAllValues()) {
Expand All @@ -257,23 +267,27 @@ public void withFieldPathFilter() throws Exception {
Matchers.<ServerStreamingCallable>any());

query.whereEqualTo(FieldPath.of("foo"), "bar").get().get();
query.whereNotEqualTo(FieldPath.of("foo"), "bar").get().get();
query.whereGreaterThan(FieldPath.of("foo"), "bar").get().get();
query.whereGreaterThanOrEqualTo(FieldPath.of("foo"), "bar").get().get();
query.whereLessThan(FieldPath.of("foo"), "bar").get().get();
query.whereLessThanOrEqualTo(FieldPath.of("foo"), "bar").get().get();
query.whereArrayContains(FieldPath.of("foo"), "bar").get().get();
query.whereIn(FieldPath.of("foo"), Collections.<Object>singletonList("bar"));
query.whereNotIn(FieldPath.of("foo"), Collections.<Object>singletonList("bar"));
query.whereArrayContainsAny(FieldPath.of("foo"), Collections.<Object>singletonList("bar"));

Iterator<RunQueryRequest> expected =
Arrays.asList(
query(filter(StructuredQuery.FieldFilter.Operator.EQUAL)),
query(filter(StructuredQuery.FieldFilter.Operator.NOT_EQUAL)),
query(filter(StructuredQuery.FieldFilter.Operator.GREATER_THAN)),
query(filter(StructuredQuery.FieldFilter.Operator.GREATER_THAN_OR_EQUAL)),
query(filter(StructuredQuery.FieldFilter.Operator.LESS_THAN)),
query(filter(StructuredQuery.FieldFilter.Operator.LESS_THAN_OR_EQUAL)),
query(filter(StructuredQuery.FieldFilter.Operator.ARRAY_CONTAINS)),
query(filter(StructuredQuery.FieldFilter.Operator.IN)),
query(filter(StructuredQuery.FieldFilter.Operator.NOT_IN)),
query(filter(StructuredQuery.FieldFilter.Operator.ARRAY_CONTAINS_ANY)))
.iterator();

Expand Down Expand Up @@ -365,6 +379,56 @@ public void validatesInQueries() {
}
}

@Test
public void notInQueriesWithReferenceArray() throws Exception {
doAnswer(queryResponse())
.when(firestoreMock)
.streamRequest(
runQuery.capture(),
streamObserverCapture.capture(),
Matchers.<ServerStreamingCallable>any());

query
.whereNotIn(
FieldPath.documentId(),
Arrays.<Object>asList("doc", firestoreMock.document("coll/doc")))
.get()
.get();

Value value =
Value.newBuilder()
.setArrayValue(
ArrayValue.newBuilder()
.addValues(reference(DOCUMENT_NAME))
.addValues(reference(DOCUMENT_NAME))
.build())
.build();
RunQueryRequest expectedRequest = query(filter(Operator.NOT_IN, "__name__", value));

assertEquals(expectedRequest, runQuery.getValue());
}

@Test
public void validatesNotInQueries() {
try {
query.whereNotIn(FieldPath.documentId(), Arrays.<Object>asList("foo", 42)).get();
fail();
} catch (IllegalArgumentException e) {
assertEquals(
"The corresponding value for FieldPath.documentId() must be a String or a "
+ "DocumentReference, but was: 42.",
e.getMessage());
}

try {
query.whereNotIn(FieldPath.documentId(), Arrays.<Object>asList()).get();
fail();
} catch (IllegalArgumentException e) {
assertEquals(
"Invalid Query. A non-empty array is required for 'NOT_IN' filters.", e.getMessage());
}
}

@Test
public void validatesQueryOperatorForFieldPathDocumentId() {
try {
Expand Down
Expand Up @@ -1218,6 +1218,39 @@ public void inQueries() throws Exception {
assertEquals(asList("a", "c"), querySnapshotToIds(querySnapshot));
}

@Test
public void notEqualQueries() throws Exception {
setDocument("a", map("zip", Double.NaN));
setDocument("b", map("zip", 91102));
setDocument("c", map("zip", 98101));
setDocument("d", map("zip", 98103));
setDocument("e", map("zip", asList(98101)));
setDocument("f", map("zip", asList("98101", map("zip", 98101))));
setDocument("g", map("zip", map("zip", 98101)));
setDocument("h", map("zip", null));

QuerySnapshot querySnapshot = randomColl.whereNotEqualTo("zip", 98101).get().get();
assertEquals(asList("a", "b", "d", "e", "f", "g"), querySnapshotToIds(querySnapshot));

querySnapshot = randomColl.whereNotEqualTo("zip", Double.NaN).get().get();
assertEquals(asList("b", "c", "d", "e", "f", "g"), querySnapshotToIds(querySnapshot));

querySnapshot = randomColl.whereNotEqualTo("zip", null).get().get();
assertEquals(asList("a", "b", "c", "d", "e", "f", "g"), querySnapshotToIds(querySnapshot));
}

@Test
public void notEqualQueriesWithDocumentId() throws Exception {
DocumentReference doc1 = setDocument("a", map("count", 1));
DocumentReference doc2 = setDocument("b", map("count", 2));
setDocument("c", map("count", 3));

QuerySnapshot querySnapshot =
randomColl.whereNotEqualTo(FieldPath.documentId(), doc1.getId()).get().get();

assertEquals(asList("b", "c"), querySnapshotToIds(querySnapshot));
}

@Test
public void inQueriesWithDocumentId() throws Exception {
DocumentReference doc1 = setDocument("a", map("count", 1));
Expand All @@ -1233,6 +1266,43 @@ public void inQueriesWithDocumentId() throws Exception {
assertEquals(asList("a", "b"), querySnapshotToIds(querySnapshot));
}

@Test
public void notInQueries() throws Exception {
setDocument("a", map("zip", 98101));
setDocument("b", map("zip", 91102));
setDocument("c", map("zip", 98103));
setDocument("d", map("zip", asList(98101)));
setDocument("e", map("zip", asList("98101", map("zip", 98101))));
setDocument("f", map("zip", map("code", 500)));

QuerySnapshot querySnapshot =
randomColl.whereNotIn("zip", Arrays.<Object>asList(98101, 98103)).get().get();
assertEquals(asList("b", "d", "e", "f"), querySnapshotToIds(querySnapshot));

querySnapshot = randomColl.whereNotIn("zip", Arrays.<Object>asList(Double.NaN)).get().get();
assertEquals(asList("b", "a", "c", "d", "e", "f"), querySnapshotToIds(querySnapshot));

List<Object> nullArray = new ArrayList<>();
nullArray.add(null);
querySnapshot = randomColl.whereNotIn("zip", nullArray).get().get();
assertEquals(new ArrayList<>(), querySnapshotToIds(querySnapshot));
}

@Test
public void notInQueriesWithDocumentId() throws Exception {
DocumentReference doc1 = setDocument("a", map("count", 1));
DocumentReference doc2 = setDocument("b", map("count", 2));
setDocument("c", map("count", 3));

QuerySnapshot querySnapshot =
randomColl
.whereNotIn(FieldPath.documentId(), Arrays.<Object>asList(doc1.getId(), doc2))
.get()
.get();

assertEquals(asList("c"), querySnapshotToIds(querySnapshot));
}

@Test
public void arrayContainsAnyQueries() throws Exception {
setDocument("a", map("array", asList(42)));
Expand Down