Skip to content

Add documentation, and address/remove some TODOs. #4274

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

Merged
merged 1 commit into from
Nov 29, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 <em>any</em> 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 <em>all</em> 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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(")");
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -643,9 +643,7 @@ private List<Filter> 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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO was released because we'd like the user to see the same canonical ID for a target even after they upgrade their SDK.

if (result instanceof com.google.firebase.firestore.core.CompositeFilter) {
com.google.firebase.firestore.core.CompositeFilter compositeFilter =
(com.google.firebase.firestore.core.CompositeFilter) result;
Expand Down
Loading