diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/Filter.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/Filter.java
index 0df73bce122..882a803cdac 100644
--- a/firebase-firestore/src/main/java/com/google/firebase/firestore/Filter.java
+++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/Filter.java
@@ -17,13 +17,17 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import com.google.firebase.firestore.core.CompositeFilter;
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}.
+ */
public class Filter {
static class UnaryFilter extends Filter {
private final FieldPath field;
@@ -70,112 +74,274 @@ public com.google.firebase.firestore.core.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.fromDotSeparatedPath(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.fromDotSeparatedPath(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.fromDotSeparatedPath(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.fromDotSeparatedPath(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.fromDotSeparatedPath(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.fromDotSeparatedPath(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.fromDotSeparatedPath(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 values 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.fromDotSeparatedPath(field), value);
+ public static Filter arrayContainsAny(
+ @NonNull String field, @NonNull List extends Object> values) {
+ return arrayContainsAny(FieldPath.fromDotSeparatedPath(field), values);
}
+ /**
+ * 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 values 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);
+ public static Filter arrayContainsAny(
+ @NonNull FieldPath fieldPath, @NonNull List extends Object> values) {
+ return new UnaryFilter(fieldPath, Operator.ARRAY_CONTAINS_ANY, values);
}
+ /**
+ * 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 values 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.fromDotSeparatedPath(field), value);
+ public static Filter inArray(@NonNull String field, @NonNull List extends Object> values) {
+ return inArray(FieldPath.fromDotSeparatedPath(field), values);
}
+ /**
+ * 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 values 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);
+ public static Filter inArray(
+ @NonNull FieldPath fieldPath, @NonNull List extends Object> values) {
+ return new UnaryFilter(fieldPath, Operator.IN, values);
}
+ /**
+ * 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 values 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.fromDotSeparatedPath(field), value);
+ public static Filter notInArray(@NonNull String field, @NonNull List extends Object> values) {
+ return notInArray(FieldPath.fromDotSeparatedPath(field), values);
}
+ /**
+ * 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 values 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);
+ public static Filter notInArray(
+ @NonNull FieldPath fieldPath, @NonNull List extends Object> values) {
+ return new UnaryFilter(fieldPath, Operator.NOT_IN, values);
}
+ /**
+ * 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), com.google.firebase.firestore.core.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 disjunction for.
+ * @return The newly created filter.
+ */
@NonNull
public static Filter and(Filter... filters) {
return new CompositeFilter(
diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java
index 8db1e9ff8db..a4519aacfb8 100644
--- a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java
+++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java
@@ -388,6 +388,23 @@ 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) {
+ 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).
+ return this;
+ }
+ validateNewFilter(parsedFilter);
+ return new Query(query.filter(parsedFilter), firestore);
+ }
+
/**
* Takes a {@link Filter.UnaryFilter} object, parses the value object and returns a new {@link
* FieldFilter} for the field, operator, and parsed value.
@@ -468,17 +485,6 @@ private com.google.firebase.firestore.core.Filter parseFilter(Filter filter) {
return parseCompositeFilter((Filter.CompositeFilter) filter);
}
- // TODO(orquery): This method will become public API. Change visibility and add documentation.
- Query where(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).
- return this;
- }
- validateNewFilter(parsedFilter);
- return new Query(query.filter(parsedFilter), firestore);
- }
-
private void validateOrderByField(com.google.firebase.firestore.model.FieldPath field) {
com.google.firebase.firestore.model.FieldPath inequalityField = query.inequalityField();
if (query.getFirstOrderByField() == null && inequalityField != null) {
diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CompositeFilter.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CompositeFilter.java
index 890e5751fa6..bfe9d2f3feb 100644
--- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CompositeFilter.java
+++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CompositeFilter.java
@@ -161,9 +161,20 @@ public boolean matches(Document doc) {
@Override
public String getCanonicalId() {
- // TODO(orquery): Add special case for flat AND filters.
-
StringBuilder builder = new StringBuilder();
+
+ // Older SDK versions use an implicit AND operation between their filters. In the new SDK
+ // versions, the developer may use an explicit AND filter. To stay consistent with the old
+ // usages, we add a special case to ensure the canonical ID for these two are the same.
+ // For example: `col.whereEquals("a", 1).whereEquals("b", 2)` should have the same canonical ID
+ // as `col.where(and(equals("a",1), equals("b",2)))`.
+ if (isFlatConjunction()) {
+ for (Filter filter : filters) {
+ builder.append(filter.getCanonicalId());
+ }
+ return builder.toString();
+ }
+
builder.append(operator.toString() + "(");
builder.append(TextUtils.join(",", filters));
builder.append(")");
@@ -183,7 +194,6 @@ public boolean equals(Object o) {
CompositeFilter other = (CompositeFilter) o;
// Note: This comparison requires order of filters in the list to be the same, and it does not
// remove duplicate subfilters from each composite filter. It is therefore way less expensive.
- // TODO(orquery): Consider removing duplicates and ignoring order of filters in the list.
return operator == other.operator && filters.equals(other.filters);
}
diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteSerializer.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteSerializer.java
index f916fee9683..57f51a7b8cc 100644
--- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteSerializer.java
+++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteSerializer.java
@@ -643,9 +643,7 @@ private List decodeFilters(StructuredQuery.Filter proto) {
Filter result = decodeFilter(proto);
// Instead of a singletonList containing AND(F1, F2, ...), we can return a list containing F1,
- // F2, ...
- // TODO(orquery): Once proper support for composite filters has been completed, we can remove
- // this flattening from here.
+ // F2, ... to stay consistent with the older SDK versions.
if (result instanceof com.google.firebase.firestore.core.CompositeFilter) {
com.google.firebase.firestore.core.CompositeFilter compositeFilter =
(com.google.firebase.firestore.core.CompositeFilter) result;
diff --git a/firebase-firestore/src/test/java/com/google/firebase/firestore/core/FilterTest.java b/firebase-firestore/src/test/java/com/google/firebase/firestore/core/FilterTest.java
index 1fdbc88951b..0c5e55e2c51 100644
--- a/firebase-firestore/src/test/java/com/google/firebase/firestore/core/FilterTest.java
+++ b/firebase-firestore/src/test/java/com/google/firebase/firestore/core/FilterTest.java
@@ -17,6 +17,7 @@
import static com.google.firebase.firestore.testutil.TestUtil.andFilters;
import static com.google.firebase.firestore.testutil.TestUtil.filter;
import static com.google.firebase.firestore.testutil.TestUtil.orFilters;
+import static com.google.firebase.firestore.testutil.TestUtil.query;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -85,4 +86,11 @@ public void testCompositeFilterNestedChecks() {
assertFalse(orFilter2.isFlat());
assertFalse(orFilter2.isFlatConjunction());
}
+
+ @Test
+ public void testCanonicalIdOfFlatConjunctions() {
+ Target target1 = query("col").filter(A).filter(B).filter(C).toTarget();
+ Target target2 = query("col").filter(andFilters(A, B, C)).toTarget();
+ assertEquals(target1.getCanonicalId(), target2.getCanonicalId());
+ }
}