|
28 | 28 | import com.google.firebase.firestore.core.ActivityScope;
|
29 | 29 | import com.google.firebase.firestore.core.AsyncEventListener;
|
30 | 30 | import com.google.firebase.firestore.core.Bound;
|
| 31 | +import com.google.firebase.firestore.core.CompositeFilter; |
31 | 32 | import com.google.firebase.firestore.core.EventManager.ListenOptions;
|
32 | 33 | import com.google.firebase.firestore.core.FieldFilter;
|
33 | 34 | import com.google.firebase.firestore.core.FieldFilter.Operator;
|
@@ -387,15 +388,16 @@ public Query whereNotIn(@NonNull FieldPath fieldPath, @NonNull List<? extends Ob
|
387 | 388 | }
|
388 | 389 |
|
389 | 390 | /**
|
390 |
| - * Parses the given value object and creates a new {@code FieldFilter} with the given field, |
391 |
| - * operator, and value. Also performs validation on the filter before retuning it. |
| 391 | + * Takes a {@link Filter.UnaryFilter} object, parses the value object and returns a new {@link |
| 392 | + * FieldFilter} for the field, operator, and parsed value. |
392 | 393 | *
|
393 |
| - * @param fieldPath The field to compare |
394 |
| - * @param op The operator |
395 |
| - * @param value The value for parsing |
396 |
| - * @return The created {@code FieldFilter}. |
| 394 | + * @param fieldFilterData The Filter.UnaryFilter object to parse. |
| 395 | + * @return The created {@link FieldFilter}. |
397 | 396 | */
|
398 |
| - private FieldFilter parseFieldFilter(@NonNull FieldPath fieldPath, Operator op, Object value) { |
| 397 | + private FieldFilter parseFieldFilter(Filter.UnaryFilter fieldFilterData) { |
| 398 | + FieldPath fieldPath = fieldFilterData.getField(); |
| 399 | + Operator op = fieldFilterData.getOperator(); |
| 400 | + Object value = fieldFilterData.getValue(); |
399 | 401 | checkNotNull(fieldPath, "Provided field path must not be null.");
|
400 | 402 | checkNotNull(op, "Provided op must not be null.");
|
401 | 403 | Value fieldValue;
|
@@ -426,15 +428,54 @@ private FieldFilter parseFieldFilter(@NonNull FieldPath fieldPath, Operator op,
|
426 | 428 | .parseQueryValue(value, op == Operator.IN || op == Operator.NOT_IN);
|
427 | 429 | }
|
428 | 430 | FieldFilter filter = FieldFilter.create(fieldPath.getInternalPath(), op, fieldValue);
|
429 |
| - validateNewFilter(filter); |
430 | 431 | return filter;
|
431 | 432 | }
|
432 | 433 |
|
| 434 | + /** |
| 435 | + * Takes a {@link Filter.CompositeFilter} object, parses each of its subfilters, and returns a new |
| 436 | + * {@link Filter} that is constructed using the parsed values. |
| 437 | + */ |
| 438 | + private com.google.firebase.firestore.core.Filter parseCompositeFilter( |
| 439 | + Filter.CompositeFilter compositeFilterData) { |
| 440 | + List<com.google.firebase.firestore.core.Filter> parsedFilters = new ArrayList<>(); |
| 441 | + for (Filter filter : compositeFilterData.getFilters()) { |
| 442 | + com.google.firebase.firestore.core.Filter parsedFilter = parseFilter(filter); |
| 443 | + if (!parsedFilter.getFilters().isEmpty()) { |
| 444 | + parsedFilters.add(parsedFilter); |
| 445 | + } |
| 446 | + } |
| 447 | + |
| 448 | + // For composite filters containing 1 filter, return the only filter. |
| 449 | + // For example: AND(FieldFilter1) == FieldFilter1 |
| 450 | + if (parsedFilters.size() == 1) { |
| 451 | + return parsedFilters.get(0); |
| 452 | + } |
| 453 | + return new CompositeFilter(parsedFilters, compositeFilterData.getOperator()); |
| 454 | + } |
| 455 | + |
| 456 | + /** |
| 457 | + * Takes a filter whose value has not been parsed, parses the value object and returns a |
| 458 | + * FieldFilter or CompositeFilter with parsed values. |
| 459 | + */ |
| 460 | + private com.google.firebase.firestore.core.Filter parseFilter(Filter filter) { |
| 461 | + hardAssert( |
| 462 | + filter instanceof Filter.UnaryFilter || filter instanceof Filter.CompositeFilter, |
| 463 | + "Parsing is only supported for Filter.UnaryFilter and Filter.CompositeFilter."); |
| 464 | + if (filter instanceof Filter.UnaryFilter) { |
| 465 | + return parseFieldFilter((Filter.UnaryFilter) filter); |
| 466 | + } |
| 467 | + return parseCompositeFilter((Filter.CompositeFilter) filter); |
| 468 | + } |
| 469 | + |
433 | 470 | // TODO(orquery): This method will become public API. Change visibility and add documentation.
|
434 | 471 | private Query where(Filter filter) {
|
435 |
| - return new Query( |
436 |
| - query.filter(parseFieldFilter(filter.getField(), filter.getOperator(), filter.getValue())), |
437 |
| - firestore); |
| 472 | + com.google.firebase.firestore.core.Filter parsedFilter = parseFilter(filter); |
| 473 | + if (parsedFilter.getFilters().isEmpty()) { |
| 474 | + // Return the existing query if not adding any more filters (e.g. an empty composite filter). |
| 475 | + return this; |
| 476 | + } |
| 477 | + validateNewFilter(parsedFilter); |
| 478 | + return new Query(query.filter(parsedFilter), firestore); |
438 | 479 | }
|
439 | 480 |
|
440 | 481 | private void validateOrderByField(com.google.firebase.firestore.model.FieldPath field) {
|
@@ -553,44 +594,71 @@ private List<Operator> conflictingOps(Operator op) {
|
553 | 594 | }
|
554 | 595 | }
|
555 | 596 |
|
556 |
| - private void validateNewFilter(com.google.firebase.firestore.core.Filter filter) { |
557 |
| - if (filter instanceof FieldFilter) { |
558 |
| - FieldFilter fieldFilter = (FieldFilter) filter; |
559 |
| - Operator filterOp = fieldFilter.getOperator(); |
560 |
| - if (fieldFilter.isInequality()) { |
561 |
| - com.google.firebase.firestore.model.FieldPath existingInequality = query.inequalityField(); |
562 |
| - com.google.firebase.firestore.model.FieldPath newInequality = fieldFilter.getField(); |
563 |
| - |
564 |
| - if (existingInequality != null && !existingInequality.equals(newInequality)) { |
565 |
| - throw new IllegalArgumentException( |
566 |
| - String.format( |
567 |
| - "All where filters with an inequality (notEqualTo, notIn, lessThan, " |
568 |
| - + "lessThanOrEqualTo, greaterThan, or greaterThanOrEqualTo) must be on the " |
569 |
| - + "same field. But you have filters on '%s' and '%s'", |
570 |
| - existingInequality.canonicalString(), newInequality.canonicalString())); |
571 |
| - } |
572 |
| - com.google.firebase.firestore.model.FieldPath firstOrderByField = |
573 |
| - query.getFirstOrderByField(); |
574 |
| - if (firstOrderByField != null) { |
575 |
| - validateOrderByFieldMatchesInequality(firstOrderByField, newInequality); |
576 |
| - } |
| 597 | + /** Checks that adding the given field filter to the given query yields a valid query */ |
| 598 | + private void validateNewFieldFilter( |
| 599 | + com.google.firebase.firestore.core.Query query, |
| 600 | + com.google.firebase.firestore.core.FieldFilter fieldFilter) { |
| 601 | + Operator filterOp = fieldFilter.getOperator(); |
| 602 | + if (fieldFilter.isInequality()) { |
| 603 | + com.google.firebase.firestore.model.FieldPath existingInequality = query.inequalityField(); |
| 604 | + com.google.firebase.firestore.model.FieldPath newInequality = fieldFilter.getField(); |
| 605 | + |
| 606 | + if (existingInequality != null && !existingInequality.equals(newInequality)) { |
| 607 | + throw new IllegalArgumentException( |
| 608 | + String.format( |
| 609 | + "All where filters with an inequality (notEqualTo, notIn, lessThan, " |
| 610 | + + "lessThanOrEqualTo, greaterThan, or greaterThanOrEqualTo) must be on the " |
| 611 | + + "same field. But you have filters on '%s' and '%s'", |
| 612 | + existingInequality.canonicalString(), newInequality.canonicalString())); |
577 | 613 | }
|
578 |
| - Operator conflictingOp = query.findFilterOperator(conflictingOps(filterOp)); |
579 |
| - if (conflictingOp != null) { |
580 |
| - // We special case when it's a duplicate op to give a slightly clearer error message. |
581 |
| - if (conflictingOp == filterOp) { |
582 |
| - throw new IllegalArgumentException( |
583 |
| - "Invalid Query. You cannot use more than one '" + filterOp.toString() + "' filter."); |
584 |
| - } else { |
585 |
| - throw new IllegalArgumentException( |
586 |
| - "Invalid Query. You cannot use '" |
587 |
| - + filterOp.toString() |
588 |
| - + "' filters with '" |
589 |
| - + conflictingOp.toString() |
590 |
| - + "' filters."); |
| 614 | + com.google.firebase.firestore.model.FieldPath firstOrderByField = |
| 615 | + query.getFirstOrderByField(); |
| 616 | + if (firstOrderByField != null) { |
| 617 | + validateOrderByFieldMatchesInequality(firstOrderByField, newInequality); |
| 618 | + } |
| 619 | + } |
| 620 | + Operator conflictingOp = findFilterWithOperator(query.getFilters(), conflictingOps(filterOp)); |
| 621 | + if (conflictingOp != null) { |
| 622 | + // We special case when it's a duplicate op to give a slightly clearer error message. |
| 623 | + if (conflictingOp == filterOp) { |
| 624 | + throw new IllegalArgumentException( |
| 625 | + "Invalid Query. You cannot use more than one '" + filterOp.toString() + "' filter."); |
| 626 | + } else { |
| 627 | + throw new IllegalArgumentException( |
| 628 | + "Invalid Query. You cannot use '" |
| 629 | + + filterOp.toString() |
| 630 | + + "' filters with '" |
| 631 | + + conflictingOp.toString() |
| 632 | + + "' filters."); |
| 633 | + } |
| 634 | + } |
| 635 | + } |
| 636 | + |
| 637 | + /** Checks that adding the given filter to the current query is valid */ |
| 638 | + private void validateNewFilter(com.google.firebase.firestore.core.Filter filter) { |
| 639 | + com.google.firebase.firestore.core.Query testQuery = query; |
| 640 | + for (FieldFilter subfilter : filter.getFlattenedFilters()) { |
| 641 | + validateNewFieldFilter(testQuery, subfilter); |
| 642 | + testQuery = query.filter(subfilter); |
| 643 | + } |
| 644 | + } |
| 645 | + |
| 646 | + /** |
| 647 | + * Checks if any of the provided filter operators are included in the given list of filters and |
| 648 | + * returns the first one that is, or null if none are. |
| 649 | + */ |
| 650 | + @Nullable |
| 651 | + private Operator findFilterWithOperator( |
| 652 | + List<com.google.firebase.firestore.core.Filter> filters, List<Operator> operators) { |
| 653 | + for (com.google.firebase.firestore.core.Filter filter : filters) { |
| 654 | + if (filter instanceof FieldFilter) { |
| 655 | + Operator filterOp = ((FieldFilter) filter).getOperator(); |
| 656 | + if (operators.contains(filterOp)) { |
| 657 | + return filterOp; |
591 | 658 | }
|
592 | 659 | }
|
593 | 660 | }
|
| 661 | + return null; |
594 | 662 | }
|
595 | 663 |
|
596 | 664 | /**
|
|
0 commit comments