Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions firebase-firestore/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Unreleased
* [feature] Add support for disjunctions in queries (`OR` queries).

# 24.4.4
* [changed] Relaxed certain query validations performed by the SDK (#4231).
Expand Down
27 changes: 27 additions & 0 deletions firebase-firestore/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,32 @@ package com.google.firebase.firestore {
method @NonNull public static com.google.firebase.firestore.FieldValue serverTimestamp();
}

public class Filter {
ctor public Filter();
method @NonNull public static com.google.firebase.firestore.Filter and(com.google.firebase.firestore.Filter...);
method @NonNull public static com.google.firebase.firestore.Filter arrayContains(@NonNull String, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter arrayContains(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter arrayContainsAny(@NonNull String, @NonNull java.util.List<?>);
method @NonNull public static com.google.firebase.firestore.Filter arrayContainsAny(@NonNull com.google.firebase.firestore.FieldPath, @NonNull java.util.List<?>);
method @NonNull public static com.google.firebase.firestore.Filter equalTo(@NonNull String, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter equalTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter greaterThan(@NonNull String, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter greaterThan(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter greaterThanOrEqualTo(@NonNull String, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter greaterThanOrEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter inArray(@NonNull String, @NonNull java.util.List<?>);
method @NonNull public static com.google.firebase.firestore.Filter inArray(@NonNull com.google.firebase.firestore.FieldPath, @NonNull java.util.List<?>);
method @NonNull public static com.google.firebase.firestore.Filter lessThan(@NonNull String, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter lessThan(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter lessThanOrEqualTo(@NonNull String, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter lessThanOrEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter notEqualTo(@NonNull String, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter notEqualTo(@NonNull com.google.firebase.firestore.FieldPath, @Nullable Object);
method @NonNull public static com.google.firebase.firestore.Filter notInArray(@NonNull String, @NonNull java.util.List<?>);
method @NonNull public static com.google.firebase.firestore.Filter notInArray(@NonNull com.google.firebase.firestore.FieldPath, @NonNull java.util.List<?>);
method @NonNull public static com.google.firebase.firestore.Filter or(com.google.firebase.firestore.Filter...);
}

public class FirebaseFirestore {
method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotsInSyncListener(@NonNull Runnable);
method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotsInSyncListener(@NonNull android.app.Activity, @NonNull Runnable);
Expand Down Expand Up @@ -322,6 +348,7 @@ package com.google.firebase.firestore {
method @NonNull public com.google.firebase.firestore.Query startAfter(java.lang.Object...);
method @NonNull public com.google.firebase.firestore.Query startAt(@NonNull com.google.firebase.firestore.DocumentSnapshot);
method @NonNull public com.google.firebase.firestore.Query startAt(java.lang.Object...);
method @NonNull public com.google.firebase.firestore.Query where(@NonNull com.google.firebase.firestore.Filter);
method @NonNull public com.google.firebase.firestore.Query whereArrayContains(@NonNull String, @NonNull Object);
method @NonNull public com.google.firebase.firestore.Query whereArrayContains(@NonNull com.google.firebase.firestore.FieldPath, @NonNull Object);
method @NonNull public com.google.firebase.firestore.Query whereArrayContainsAny(@NonNull String, @NonNull java.util.List<?>);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package com.google.firebase.firestore;

import static com.google.firebase.firestore.testutil.IntegrationTestUtil.isRunningAgainstEmulator;
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.nullList;
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.querySnapshotToIds;
import static com.google.firebase.firestore.testutil.IntegrationTestUtil.querySnapshotToValues;
Expand All @@ -29,6 +30,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.gms.tasks.Task;
Expand All @@ -42,7 +44,6 @@
import java.util.Map;
import java.util.concurrent.Semaphore;
import org.junit.After;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;

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

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

// with one inequality: a>2 || b==1.
checkOnlineAndOfflineResultsMatch(
collection.where(Filter.or(Filter.greaterThan("a", 2), Filter.equalTo("b", 1))),
"doc5",
"doc2",
"doc3");

// (a==1 && b==0) || (a==3 && b==2)
checkOnlineAndOfflineResultsMatch(
collection.where(
Expand All @@ -1082,6 +1074,35 @@ public void testOrQueries() {
Filter.or(Filter.equalTo("a", 3), Filter.equalTo("b", 3)))),
"doc3");

// Test with limits without orderBy (the __name__ ordering is the tie breaker).
checkOnlineAndOfflineResultsMatch(
collection.where(Filter.or(Filter.equalTo("a", 2), Filter.equalTo("b", 1))).limit(1),
"doc2");
}

@Test
public void testOrQueriesWithCompositeIndexes() {
assumeTrue(
"Skip this test if running against production because it results in a "
+ "'missing index' error. The Firestore Emulator, however, does serve these "
+ " queries.",
isRunningAgainstEmulator());
Map<String, Map<String, Object>> 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.
checkOnlineAndOfflineResultsMatch(
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
checkOnlineAndOfflineResultsMatch(
collection.where(Filter.or(Filter.equalTo("a", 1), Filter.greaterThan("b", 0))).limit(2),
Expand Down Expand Up @@ -1113,17 +1134,10 @@ public void testOrQueries() {
.limitToLast(1)
.orderBy("a"),
"doc2");

// Test with limits without orderBy (the __name__ ordering is the tie breaker).
checkOnlineAndOfflineResultsMatch(
collection.where(Filter.or(Filter.equalTo("a", 2), Filter.equalTo("b", 1))).limit(1),
"doc2");
}

// TODO(orquery): Enable this test when prod supports OR queries.
@Ignore
@Test
public void testOrQueriesWithInAndNotIn() {
public void testOrQueriesWithIn() {
Map<String, Map<String, Object>> testDocs =
map(
"doc1", map("a", 1, "b", 0),
Expand All @@ -1140,6 +1154,24 @@ public void testOrQueriesWithInAndNotIn() {
"doc3",
"doc4",
"doc6");
}

@Test
public void testOrQueriesWithNotIn() {
assumeTrue(
"Skip this test if running against production because it results in a "
+ "'missing index' error. The Firestore Emulator, however, does serve these "
+ " queries",
isRunningAgainstEmulator());
Map<String, Map<String, Object>> 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.
Expand All @@ -1149,8 +1181,6 @@ public void testOrQueriesWithInAndNotIn() {
"doc2");
}

// TODO(orquery): Enable this test when prod supports OR queries.
@Ignore
@Test
public void testOrQueriesWithArrayMembership() {
Map<String, Map<String, Object>> testDocs =
Expand Down Expand Up @@ -1179,9 +1209,12 @@ public void testOrQueriesWithArrayMembership() {
"doc6");
}

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

// Two IN operations on different fields with disjunction.
Query query1 =
collection
.where(Filter.or(Filter.inArray("a", asList(2, 3)), Filter.inArray("b", asList(0, 2))))
.orderBy("a");
checkOnlineAndOfflineResultsMatch(query1, "doc1", "doc6", "doc3");

// Two IN operations on different fields with conjunction.
Query query2 =
collection
.where(Filter.and(Filter.inArray("a", asList(2, 3)), Filter.inArray("b", asList(0, 2))))
.orderBy("a");
checkOnlineAndOfflineResultsMatch(query2, "doc3");

// Two IN operations on the same field.
// a IN [1,2,3] && a IN [0,1,4] should result in "a==1".
Query query3 =
collection.where(
Filter.and(Filter.inArray("a", asList(1, 2, 3)), Filter.inArray("a", asList(0, 1, 4))));
checkOnlineAndOfflineResultsMatch(query3, "doc1", "doc4", "doc5");

// a IN [2,3] && a IN [0,1,4] is never true and so the result should be an empty set.
Query query4 =
collection.where(
Filter.and(Filter.inArray("a", asList(2, 3)), Filter.inArray("a", asList(0, 1, 4))));
checkOnlineAndOfflineResultsMatch(query4);
Filter.or(Filter.inArray("a", asList(2, 3)), Filter.inArray("b", asList(0, 2))));
checkOnlineAndOfflineResultsMatch(query1, "doc1", "doc3", "doc6");

// Two IN operations on the same field with disjunction.
// a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]).
Query query5 =
Query query2 =
collection.where(
Filter.or(Filter.inArray("a", asList(0, 3)), Filter.inArray("a", asList(0, 2))));
checkOnlineAndOfflineResultsMatch(query5, "doc3", "doc6");

// Nested composite filter on the same field.
Query query6 =
collection.where(
Filter.and(
Filter.inArray("a", asList(1, 3)),
Filter.or(
Filter.inArray("a", asList(0, 2)),
Filter.and(
Filter.greaterThanOrEqualTo("b", 1), Filter.inArray("a", asList(1, 3))))));
checkOnlineAndOfflineResultsMatch(query6, "doc3", "doc4");

// Nested composite filter on different fields.
Query query7 =
collection.where(
Filter.and(
Filter.inArray("b", asList(0, 3)),
Filter.or(
Filter.inArray("b", asList(1)),
Filter.and(
Filter.inArray("b", asList(2, 3)), Filter.inArray("a", asList(1, 3))))));
checkOnlineAndOfflineResultsMatch(query7, "doc4");
checkOnlineAndOfflineResultsMatch(query2, "doc3", "doc6");
}

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

Query query2 =
collection.where(
Filter.and(
Filter.inArray("a", asList(2, 3)), Filter.arrayContainsAny("b", asList(0, 7))));
checkOnlineAndOfflineResultsMatch(query2, "doc3");

Query query3 =
collection.where(
Filter.or(
Filter.and(Filter.inArray("a", asList(2, 3)), Filter.equalTo("c", 10)),
Filter.arrayContainsAny("b", asList(0, 7))));
checkOnlineAndOfflineResultsMatch(query3, "doc1", "doc3", "doc4");

Query query4 =
collection.where(
Filter.and(
Filter.inArray("a", asList(2, 3)),
Filter.or(Filter.arrayContainsAny("b", asList(0, 7)), Filter.equalTo("c", 20))));
checkOnlineAndOfflineResultsMatch(query4, "doc3", "doc6");
checkOnlineAndOfflineResultsMatch(query2, "doc1", "doc3", "doc4");
}

@Ignore
@Test
public void testUsingInWithArrayContains() {
Map<String, Map<String, Object>> testDocs =
Expand Down Expand Up @@ -1326,9 +1306,13 @@ public void testUsingInWithArrayContains() {
checkOnlineAndOfflineResultsMatch(query4, "doc3");
}

@Ignore
@Test
public void testOrderByEquality() {
// TODO(orquery): Enable this test against production when possible.
assumeTrue(
"Skip this test if running against production because order-by-equality is "
+ "not supported yet.",
isRunningAgainstEmulator());
Map<String, Map<String, Object>> testDocs =
map(
"doc1", map("a", 1, "b", asList(0)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,10 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import com.google.firebase.firestore.core.FieldFilter.Operator;
import java.util.Arrays;
import java.util.List;

// TODO(orquery): Remove the `hide` and scope annotations.
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY)
/**
* A {@code Filter} represents a restriction on one or more field values and can be used to refine
* the results of a {@code Query}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,14 +388,14 @@ public Query whereNotIn(@NonNull FieldPath fieldPath, @NonNull List<? extends Ob
return where(Filter.notInArray(fieldPath, values));
}

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