Skip to content

Commit 23cf19b

Browse files
committed
Support encoding and decoding Composite Filters.
1 parent 39262c8 commit 23cf19b

File tree

2 files changed

+80
-42
lines changed

2 files changed

+80
-42
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/core/CompositeFilter.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@ public boolean isDisjunction() {
7474
return operator == Operator.OPERATOR_UNSPECIFIED;
7575
}
7676

77+
/**
78+
* Returns true if this filter is a conjunction of field filters only. Returns false otherwise.
79+
*/
80+
public boolean isFlatConjunction() {
81+
if (operator != Operator.AND) return false;
82+
for (Filter filter : filters) {
83+
if (filter instanceof CompositeFilter) return false;
84+
}
85+
return true;
86+
}
87+
7788
/**
7889
* Performs a depth-first search to find and return the first FieldFilter in the composite filter
7990
* that satisfies the condition. Returns {@code null} if none of the FieldFilters satisfy the

firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteSerializer.java

Lines changed: 69 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@
6868
import com.google.firestore.v1.StructuredQuery.CollectionSelector;
6969
import com.google.firestore.v1.StructuredQuery.CompositeFilter;
7070
import com.google.firestore.v1.StructuredQuery.FieldReference;
71-
import com.google.firestore.v1.StructuredQuery.Filter.FilterTypeCase;
7271
import com.google.firestore.v1.StructuredQuery.Order;
7372
import com.google.firestore.v1.StructuredQuery.UnaryFilter;
7473
import com.google.firestore.v1.Target;
@@ -634,54 +633,39 @@ public com.google.firebase.firestore.core.Target decodeQueryTarget(QueryTarget t
634633
// Filters
635634

636635
private StructuredQuery.Filter encodeFilters(List<Filter> filters) {
637-
List<StructuredQuery.Filter> protos = new ArrayList<>(filters.size());
638-
for (Filter filter : filters) {
639-
if (filter instanceof FieldFilter) {
640-
protos.add(encodeUnaryOrFieldFilter((FieldFilter) filter));
641-
}
642-
}
643-
if (filters.size() == 1) {
644-
return protos.get(0);
645-
} else {
646-
CompositeFilter.Builder composite = CompositeFilter.newBuilder();
647-
composite.setOp(CompositeFilter.Operator.AND);
648-
composite.addAllFilters(protos);
649-
return StructuredQuery.Filter.newBuilder().setCompositeFilter(composite).build();
650-
}
636+
// A target's filter list is implicitly a composite AND filter.
637+
return encodeFilter(
638+
new com.google.firebase.firestore.core.CompositeFilter(
639+
filters, CompositeFilter.Operator.AND));
651640
}
652641

653642
private List<Filter> decodeFilters(StructuredQuery.Filter proto) {
654-
List<StructuredQuery.Filter> filters;
655-
if (proto.getFilterTypeCase() == FilterTypeCase.COMPOSITE_FILTER) {
656-
hardAssert(
657-
proto.getCompositeFilter().getOp() == CompositeFilter.Operator.AND,
658-
"Only AND-type composite filters are supported, got %d",
659-
proto.getCompositeFilter().getOp());
660-
filters = proto.getCompositeFilter().getFiltersList();
661-
} else {
662-
filters = Collections.singletonList(proto);
643+
Filter result = decodeFilter(proto);
644+
645+
// Instead of a singletonList containing AND(F1, F2, ...), we can return
646+
// a list containing F1, F2, ...
647+
// TODO(orquery): Once proper support for composite filters has been completed, we can remove
648+
// this flattening from here.
649+
if (result instanceof com.google.firebase.firestore.core.CompositeFilter) {
650+
com.google.firebase.firestore.core.CompositeFilter compositeFilter =
651+
(com.google.firebase.firestore.core.CompositeFilter) result;
652+
if (compositeFilter.isFlatConjunction()) {
653+
return compositeFilter.getFilters();
654+
}
663655
}
664656

665-
List<Filter> result = new ArrayList<>(filters.size());
666-
for (StructuredQuery.Filter filter : filters) {
667-
switch (filter.getFilterTypeCase()) {
668-
case COMPOSITE_FILTER:
669-
throw fail("Nested composite filters are not supported.");
670-
671-
case FIELD_FILTER:
672-
result.add(decodeFieldFilter(filter.getFieldFilter()));
673-
break;
674-
675-
case UNARY_FILTER:
676-
result.add(decodeUnaryFilter(filter.getUnaryFilter()));
677-
break;
657+
return Collections.singletonList(result);
658+
}
678659

679-
default:
680-
throw fail("Unrecognized Filter.filterType %d", filter.getFilterTypeCase());
681-
}
660+
@VisibleForTesting
661+
StructuredQuery.Filter encodeFilter(com.google.firebase.firestore.core.Filter filter) {
662+
if (filter instanceof FieldFilter) {
663+
return encodeUnaryOrFieldFilter((FieldFilter) filter);
664+
} else if (filter instanceof com.google.firebase.firestore.core.CompositeFilter) {
665+
return encodeCompositeFilter((com.google.firebase.firestore.core.CompositeFilter) filter);
666+
} else {
667+
throw fail("Unrecognized filter type %s", filter.toString());
682668
}
683-
684-
return result;
685669
}
686670

687671
@VisibleForTesting
@@ -711,6 +695,39 @@ StructuredQuery.Filter encodeUnaryOrFieldFilter(FieldFilter filter) {
711695
return StructuredQuery.Filter.newBuilder().setFieldFilter(proto).build();
712696
}
713697

698+
@VisibleForTesting
699+
StructuredQuery.Filter encodeCompositeFilter(
700+
com.google.firebase.firestore.core.CompositeFilter compositeFilter) {
701+
List<StructuredQuery.Filter> protos = new ArrayList<>(compositeFilter.getFilters().size());
702+
for (Filter filter : compositeFilter.getFilters()) {
703+
protos.add(encodeFilter(filter));
704+
}
705+
706+
// If there's only one filter in the composite filter, use it directly.
707+
if (protos.size() == 1) {
708+
return protos.get(0);
709+
}
710+
711+
CompositeFilter.Builder composite = CompositeFilter.newBuilder();
712+
composite.setOp(compositeFilter.getOperator());
713+
composite.addAllFilters(protos);
714+
return StructuredQuery.Filter.newBuilder().setCompositeFilter(composite).build();
715+
}
716+
717+
@VisibleForTesting
718+
Filter decodeFilter(StructuredQuery.Filter proto) {
719+
switch (proto.getFilterTypeCase()) {
720+
case COMPOSITE_FILTER:
721+
return decodeCompositeFilter(proto.getCompositeFilter());
722+
case FIELD_FILTER:
723+
return decodeFieldFilter(proto.getFieldFilter());
724+
case UNARY_FILTER:
725+
return decodeUnaryFilter(proto.getUnaryFilter());
726+
default:
727+
throw fail("Unrecognized Filter.filterType %d", proto.getFilterTypeCase());
728+
}
729+
}
730+
714731
@VisibleForTesting
715732
FieldFilter decodeFieldFilter(StructuredQuery.FieldFilter proto) {
716733
FieldPath fieldPath = FieldPath.fromServerFormat(proto.getField().getFieldPath());
@@ -734,6 +751,16 @@ private Filter decodeUnaryFilter(StructuredQuery.UnaryFilter proto) {
734751
}
735752
}
736753

754+
@VisibleForTesting
755+
com.google.firebase.firestore.core.CompositeFilter decodeCompositeFilter(
756+
StructuredQuery.CompositeFilter compositeFilter) {
757+
List<Filter> filters = new ArrayList<>();
758+
for (StructuredQuery.Filter filter : compositeFilter.getFiltersList()) {
759+
filters.add(decodeFilter(filter));
760+
}
761+
return new com.google.firebase.firestore.core.CompositeFilter(filters, compositeFilter.getOp());
762+
}
763+
737764
private FieldReference encodeFieldPath(FieldPath field) {
738765
return FieldReference.newBuilder().setFieldPath(field.canonicalString()).build();
739766
}

0 commit comments

Comments
 (0)