-
Notifications
You must be signed in to change notification settings - Fork 608
Initial support for CompositeFilter class #3290
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
Changes from 4 commits
dc01726
9238952
d206710
b146b81
651e953
867f3b7
f33b042
993be7a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ | |
import com.google.firebase.firestore.core.ActivityScope; | ||
import com.google.firebase.firestore.core.AsyncEventListener; | ||
import com.google.firebase.firestore.core.Bound; | ||
import com.google.firebase.firestore.core.CompositeFilter; | ||
import com.google.firebase.firestore.core.EventManager.ListenOptions; | ||
import com.google.firebase.firestore.core.FieldFilter; | ||
import com.google.firebase.firestore.core.FieldFilter.Operator; | ||
|
@@ -387,15 +388,16 @@ public Query whereNotIn(@NonNull FieldPath fieldPath, @NonNull List<? extends Ob | |
} | ||
|
||
/** | ||
* Parses the given value object and creates a new {@code FieldFilter} with the given field, | ||
* operator, and value. Also performs validation on the filter before retuning it. | ||
* Takes a {@link Filter.FieldFilter} object, parses the value object and returns a new {@link | ||
* FieldFilter} for the field, operator, and parsed value. | ||
* | ||
* @param fieldPath The field to compare | ||
* @param op The operator | ||
* @param value The value for parsing | ||
* @return The created {@code FieldFilter}. | ||
* @param fieldFilterData The Filter.FieldFilter object to parse. | ||
* @return The created {@link FieldFilter}. | ||
*/ | ||
private FieldFilter parseFieldFilter(@NonNull FieldPath fieldPath, Operator op, Object value) { | ||
private FieldFilter parseFieldFilter(Filter.FieldFilter fieldFilterData) { | ||
FieldPath fieldPath = fieldFilterData.getField(); | ||
Operator op = fieldFilterData.getOperator(); | ||
Object value = fieldFilterData.getValue(); | ||
checkNotNull(fieldPath, "Provided field path must not be null."); | ||
checkNotNull(op, "Provided op must not be null."); | ||
Value fieldValue; | ||
|
@@ -426,15 +428,56 @@ private FieldFilter parseFieldFilter(@NonNull FieldPath fieldPath, Operator op, | |
.parseQueryValue(value, op == Operator.IN || op == Operator.NOT_IN); | ||
} | ||
FieldFilter filter = FieldFilter.create(fieldPath.getInternalPath(), op, fieldValue); | ||
validateNewFilter(filter); | ||
return filter; | ||
} | ||
|
||
/** | ||
* Takes a {@link Filter.CompositeFilter} object, parses each of its subfilters, and a new {@link | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Word missing before "a new". The return type in the comment is also not correct. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
* CompositeFilter} that is constructed using the parsed values. Returns null if the given | ||
* Filter.CompositeFilter does not contain any subfilters. Returns a {@link FieldFilter} if the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Method no longer returns null. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
* given Filter.CompositeFilter contains only one field filter. | ||
*/ | ||
private com.google.firebase.firestore.core.Filter parseCompositeFilter( | ||
Filter.CompositeFilter compositeFilterData) { | ||
List<com.google.firebase.firestore.core.Filter> parsedFilters = new ArrayList<>(); | ||
for (Filter filter : compositeFilterData.getFilters()) { | ||
com.google.firebase.firestore.core.Filter parsedFilter = parseFilter(filter); | ||
if (!parsedFilter.getFlattenedFilters().isEmpty()) { | ||
parsedFilters.add(parsedFilter); | ||
} | ||
} | ||
|
||
// For composite filters containing 1 filter, return the only filter. | ||
// For example: AND(FieldFilter1) == FieldFilter1 | ||
if (parsedFilters.size() == 1) { | ||
return parsedFilters.get(0); | ||
} | ||
return new CompositeFilter(parsedFilters, compositeFilterData.getOperator()); | ||
} | ||
|
||
/** | ||
* Takes a filter whose value has not been parsed, parses the value object and returns a | ||
* FieldFilter or CompositeFilter with parsed values. | ||
*/ | ||
private com.google.firebase.firestore.core.Filter parseFilter(Filter filter) { | ||
hardAssert( | ||
filter instanceof Filter.FieldFilter || filter instanceof Filter.CompositeFilter, | ||
"Parsing is only supported for Filter.FieldFilter and Filter.CompositeFilter."); | ||
if (filter instanceof Filter.FieldFilter) { | ||
return parseFieldFilter((Filter.FieldFilter) filter); | ||
} | ||
return parseCompositeFilter((Filter.CompositeFilter) filter); | ||
} | ||
|
||
// TODO(orquery): This method will become public API. Change visibility and add documentation. | ||
private Query where(Filter filter) { | ||
return new Query( | ||
query.filter(parseFieldFilter(filter.getField(), filter.getOperator(), filter.getValue())), | ||
firestore); | ||
com.google.firebase.firestore.core.Filter parsedFilter = parseFilter(filter); | ||
if (parsedFilter.getFlattenedFilters().isEmpty()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be more efficient to call getFilters() here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it would be. Good suggestion. Done. |
||
// 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) { | ||
|
@@ -553,44 +596,71 @@ private List<Operator> conflictingOps(Operator op) { | |
} | ||
} | ||
|
||
private void validateNewFilter(com.google.firebase.firestore.core.Filter filter) { | ||
if (filter instanceof FieldFilter) { | ||
FieldFilter fieldFilter = (FieldFilter) filter; | ||
Operator filterOp = fieldFilter.getOperator(); | ||
if (fieldFilter.isInequality()) { | ||
com.google.firebase.firestore.model.FieldPath existingInequality = query.inequalityField(); | ||
com.google.firebase.firestore.model.FieldPath newInequality = fieldFilter.getField(); | ||
|
||
if (existingInequality != null && !existingInequality.equals(newInequality)) { | ||
throw new IllegalArgumentException( | ||
String.format( | ||
"All where filters with an inequality (notEqualTo, notIn, lessThan, " | ||
+ "lessThanOrEqualTo, greaterThan, or greaterThanOrEqualTo) must be on the " | ||
+ "same field. But you have filters on '%s' and '%s'", | ||
existingInequality.canonicalString(), newInequality.canonicalString())); | ||
} | ||
com.google.firebase.firestore.model.FieldPath firstOrderByField = | ||
query.getFirstOrderByField(); | ||
if (firstOrderByField != null) { | ||
validateOrderByFieldMatchesInequality(firstOrderByField, newInequality); | ||
} | ||
/** Checks that adding the given field filter to the given query yields a valid query */ | ||
private void validateNewFieldFilter( | ||
com.google.firebase.firestore.core.Query query, | ||
com.google.firebase.firestore.core.FieldFilter fieldFilter) { | ||
Operator filterOp = fieldFilter.getOperator(); | ||
if (fieldFilter.isInequality()) { | ||
com.google.firebase.firestore.model.FieldPath existingInequality = query.inequalityField(); | ||
com.google.firebase.firestore.model.FieldPath newInequality = fieldFilter.getField(); | ||
|
||
if (existingInequality != null && !existingInequality.equals(newInequality)) { | ||
throw new IllegalArgumentException( | ||
String.format( | ||
"All where filters with an inequality (notEqualTo, notIn, lessThan, " | ||
+ "lessThanOrEqualTo, greaterThan, or greaterThanOrEqualTo) must be on the " | ||
+ "same field. But you have filters on '%s' and '%s'", | ||
existingInequality.canonicalString(), newInequality.canonicalString())); | ||
} | ||
Operator conflictingOp = query.findFilterOperator(conflictingOps(filterOp)); | ||
if (conflictingOp != null) { | ||
// We special case when it's a duplicate op to give a slightly clearer error message. | ||
if (conflictingOp == filterOp) { | ||
throw new IllegalArgumentException( | ||
"Invalid Query. You cannot use more than one '" + filterOp.toString() + "' filter."); | ||
} else { | ||
throw new IllegalArgumentException( | ||
"Invalid Query. You cannot use '" | ||
+ filterOp.toString() | ||
+ "' filters with '" | ||
+ conflictingOp.toString() | ||
+ "' filters."); | ||
com.google.firebase.firestore.model.FieldPath firstOrderByField = | ||
query.getFirstOrderByField(); | ||
if (firstOrderByField != null) { | ||
validateOrderByFieldMatchesInequality(firstOrderByField, newInequality); | ||
} | ||
} | ||
Operator conflictingOp = findFilterOperator(query.getFilters(), conflictingOps(filterOp)); | ||
if (conflictingOp != null) { | ||
// We special case when it's a duplicate op to give a slightly clearer error message. | ||
if (conflictingOp == filterOp) { | ||
throw new IllegalArgumentException( | ||
"Invalid Query. You cannot use more than one '" + filterOp.toString() + "' filter."); | ||
} else { | ||
throw new IllegalArgumentException( | ||
"Invalid Query. You cannot use '" | ||
+ filterOp.toString() | ||
+ "' filters with '" | ||
+ conflictingOp.toString() | ||
+ "' filters."); | ||
} | ||
} | ||
} | ||
|
||
/** Checks that adding the given filter to the current query is valid */ | ||
private void validateNewFilter(com.google.firebase.firestore.core.Filter filter) { | ||
com.google.firebase.firestore.core.Query testQuery = query; | ||
for (FieldFilter subfilter : filter.getFlattenedFilters()) { | ||
validateNewFieldFilter(testQuery, subfilter); | ||
testQuery = query.filter(subfilter); | ||
} | ||
} | ||
|
||
/** | ||
* Checks if any of the provided filter operators are included in the given list of filters and | ||
* returns the first one that is, or null if none are. | ||
*/ | ||
schmidt-sebastian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@Nullable | ||
private Operator findFilterOperator( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/findFilterOperator/findFilterByOperarator or findFilterWithOperator? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
List<com.google.firebase.firestore.core.Filter> filters, List<Operator> operators) { | ||
for (com.google.firebase.firestore.core.Filter filter : filters) { | ||
if (filter instanceof FieldFilter) { | ||
Operator filterOp = ((FieldFilter) filter).getOperator(); | ||
if (operators.contains(filterOp)) { | ||
return filterOp; | ||
} | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not public and thus should not need the annotation. Same below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops. Done.