diff --git a/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/testutil/TestUtil.java b/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/testutil/TestUtil.java index c2ce41b0d8a..c95ae57a1d8 100644 --- a/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/testutil/TestUtil.java +++ b/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/testutil/TestUtil.java @@ -33,7 +33,7 @@ import com.google.firebase.firestore.DocumentReference; import com.google.firebase.firestore.TestAccessHelper; import com.google.firebase.firestore.UserDataConverter; -import com.google.firebase.firestore.core.Filter; +import com.google.firebase.firestore.core.FieldFilter; import com.google.firebase.firestore.core.Filter.Operator; import com.google.firebase.firestore.core.OrderBy; import com.google.firebase.firestore.core.OrderBy.Direction; @@ -220,8 +220,8 @@ public static ImmutableSortedSet keySet(DocumentKey... keys) { return keySet; } - public static Filter filter(String key, String operator, Object value) { - return Filter.create(field(key), operatorFromString(operator), wrap(value)); + public static FieldFilter filter(String key, String operator, Object value) { + return FieldFilter.create(field(key), operatorFromString(operator), wrap(value)); } public static Operator operatorFromString(String s) { diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/QueryTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/QueryTest.java index 79b1028d46f..bde84061e53 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/QueryTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/QueryTest.java @@ -429,10 +429,11 @@ public void testQueriesCanUseArrayContainsFilters() { // much of anything else interesting to test. } - // TODO(in-queries): Re-enable in prod once feature lands in backend. @Test public void testQueriesCanUseInFilters() { + // TODO(in-queries): Re-enable in prod once feature lands in backend. Assume.assumeTrue(isRunningAgainstEmulator()); + Map docA = map("zip", 98101L); Map docB = map("zip", 91102L); Map docC = map("zip", 98103L); @@ -452,10 +453,32 @@ public void testQueriesCanUseInFilters() { assertEquals(asList(docF), querySnapshotToValues(snapshot)); } - // TODO(in-queries): Re-enable in prod once feature lands in backend. + @Test + public void testQueriesCanUseInFiltersWithDocIds() { + // TODO(in-queries): Re-enable in prod once feature lands in backend. + Assume.assumeTrue(isRunningAgainstEmulator()); + + Map docA = map("key", "aa"); + Map docB = map("key", "ab"); + Map docC = map("key", "ba"); + Map docD = map("key", "bb"); + Map> testDocs = + map( + "aa", docA, + "ab", docB, + "ba", docC, + "bb", docD); + CollectionReference collection = testCollectionWithDocs(testDocs); + QuerySnapshot docs = + waitFor(collection.whereIn(FieldPath.documentId(), asList("aa", "ab")).get()); + assertEquals(asList(docA, docB), querySnapshotToValues(docs)); + } + @Test public void testQueriesCanUseArrayContainsAnyFilters() { + // TODO(in-queries): Re-enable in prod once feature lands in backend. Assume.assumeTrue(isRunningAgainstEmulator()); + Map docA = map("array", asList(42L)); Map docB = map("array", asList("a", 42L, "c")); Map docC = map("array", asList(41.999, "42", map("a", asList(42)))); 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 16cc3da0770..36a6d9fa930 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 @@ -29,12 +29,12 @@ import com.google.firebase.firestore.core.AsyncEventListener; import com.google.firebase.firestore.core.Bound; import com.google.firebase.firestore.core.EventManager.ListenOptions; +import com.google.firebase.firestore.core.FieldFilter; import com.google.firebase.firestore.core.Filter; import com.google.firebase.firestore.core.Filter.Operator; import com.google.firebase.firestore.core.ListenerRegistrationImpl; import com.google.firebase.firestore.core.OrderBy; import com.google.firebase.firestore.core.QueryListener; -import com.google.firebase.firestore.core.RelationFilter; import com.google.firebase.firestore.core.ViewSnapshot; import com.google.firebase.firestore.model.Document; import com.google.firebase.firestore.model.DocumentKey; @@ -368,7 +368,7 @@ private Query whereHelper(@NonNull FieldPath fieldPath, Operator op, Object valu } fieldValue = firestore.getDataConverter().parseQueryValue(value); } - Filter filter = Filter.create(fieldPath.getInternalPath(), op, fieldValue); + Filter filter = FieldFilter.create(fieldPath.getInternalPath(), op, fieldValue); validateNewFilter(filter); return new Query(query.filter(filter), firestore); } @@ -465,15 +465,15 @@ private void validateOrderByFieldMatchesInequality( } private void validateNewFilter(Filter filter) { - if (filter instanceof RelationFilter) { - Operator filterOp = ((RelationFilter) filter).getOperator(); + if (filter instanceof FieldFilter) { + FieldFilter fieldFilter = (FieldFilter) filter; + Operator filterOp = fieldFilter.getOperator(); List arrayOps = Arrays.asList(Operator.ARRAY_CONTAINS, Operator.ARRAY_CONTAINS_ANY); List disjunctiveOps = Arrays.asList(Operator.ARRAY_CONTAINS_ANY, Operator.IN); boolean isArrayOp = arrayOps.contains(filterOp); boolean isDisjunctiveOp = disjunctiveOps.contains(filterOp); - RelationFilter relationFilter = (RelationFilter) filter; - if (relationFilter.isInequality()) { + if (fieldFilter.isInequality()) { com.google.firebase.firestore.model.FieldPath existingInequality = query.inequalityField(); com.google.firebase.firestore.model.FieldPath newInequality = filter.getField(); @@ -494,10 +494,10 @@ private void validateNewFilter(Filter filter) { // conflicts with an existing one. Operator conflictingOp = null; if (isDisjunctiveOp) { - conflictingOp = this.query.findOperatorFilter(disjunctiveOps); + conflictingOp = this.query.findFilterOperator(disjunctiveOps); } if (conflictingOp == null && isArrayOp) { - conflictingOp = this.query.findOperatorFilter(arrayOps); + conflictingOp = this.query.findFilterOperator(arrayOps); } if (conflictingOp != null) { // We special case when it's a duplicate op to give a slightly clearer error message. diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/ArrayContainsAnyFilter.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/ArrayContainsAnyFilter.java new file mode 100644 index 00000000000..4caefac30b9 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/ArrayContainsAnyFilter.java @@ -0,0 +1,42 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.firestore.core; + +import com.google.firebase.firestore.model.Document; +import com.google.firebase.firestore.model.FieldPath; +import com.google.firebase.firestore.model.value.ArrayValue; +import com.google.firebase.firestore.model.value.FieldValue; + +/** A Filter that implements the array-contains-any operator. */ +public class ArrayContainsAnyFilter extends FieldFilter { + ArrayContainsAnyFilter(FieldPath field, FieldValue value) { + super(field, Operator.ARRAY_CONTAINS_ANY, value); + } + + @Override + public boolean matches(Document doc) { + ArrayValue arrayValue = (ArrayValue) getValue(); + FieldValue other = doc.getField(getField()); + if (!(other instanceof ArrayValue)) { + return false; + } + for (FieldValue val : ((ArrayValue) other).getInternalValue()) { + if (arrayValue.getInternalValue().contains(val)) { + return true; + } + } + return false; + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/ArrayContainsFilter.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/ArrayContainsFilter.java new file mode 100644 index 00000000000..3a3a404d0c6 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/ArrayContainsFilter.java @@ -0,0 +1,34 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.firestore.core; + +import com.google.firebase.firestore.model.Document; +import com.google.firebase.firestore.model.FieldPath; +import com.google.firebase.firestore.model.value.ArrayValue; +import com.google.firebase.firestore.model.value.FieldValue; + +/** A Filter that implements the array-contains operator. */ +public class ArrayContainsFilter extends FieldFilter { + ArrayContainsFilter(FieldPath field, FieldValue value) { + super(field, Operator.ARRAY_CONTAINS, value); + } + + @Override + public boolean matches(Document doc) { + FieldValue other = doc.getField(getField()); + return other instanceof ArrayValue + && ((ArrayValue) other).getInternalValue().contains(getValue()); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/RelationFilter.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FieldFilter.java similarity index 51% rename from firebase-firestore/src/main/java/com/google/firebase/firestore/core/RelationFilter.java rename to firebase-firestore/src/main/java/com/google/firebase/firestore/core/FieldFilter.java index a67a01040ee..1c64f2c55c4 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/RelationFilter.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FieldFilter.java @@ -17,15 +17,17 @@ import static com.google.firebase.firestore.util.Assert.hardAssert; import com.google.firebase.firestore.model.Document; -import com.google.firebase.firestore.model.DocumentKey; import com.google.firebase.firestore.model.FieldPath; import com.google.firebase.firestore.model.value.ArrayValue; +import com.google.firebase.firestore.model.value.DoubleValue; import com.google.firebase.firestore.model.value.FieldValue; +import com.google.firebase.firestore.model.value.NullValue; +import com.google.firebase.firestore.model.value.ReferenceValue; import com.google.firebase.firestore.util.Assert; import java.util.Arrays; /** Represents a filter to be applied to query. */ -public class RelationFilter extends Filter { +public class FieldFilter extends Filter { private final Operator operator; private final FieldValue value; @@ -36,7 +38,7 @@ public class RelationFilter extends Filter { * Creates a new filter that compares fields and values. Only intended to be called from * Filter.create(). */ - RelationFilter(FieldPath field, Operator operator, FieldValue value) { + protected FieldFilter(FieldPath field, Operator operator, FieldValue value) { this.field = field; this.operator = operator; this.value = value; @@ -55,50 +57,67 @@ public FieldValue getValue() { return value; } - @Override - public boolean matches(Document doc) { - if (this.field.isKeyField()) { - Object refValue = value.value(); - hardAssert( - refValue instanceof DocumentKey, "Comparing on key, but filter value not a DocumentKey"); - hardAssert( - operator != Operator.ARRAY_CONTAINS - && operator != Operator.ARRAY_CONTAINS_ANY - && operator != Operator.IN, - "'" + operator.toString() + "' queries don't make sense on document keys."); - int comparison = DocumentKey.comparator().compare(doc.getKey(), (DocumentKey) refValue); - return matchesComparison(comparison); - } else { - FieldValue value = doc.getField(field); - return value != null && matchesValue(doc.getField(field)); - } - } - - private boolean matchesValue(FieldValue other) { - if (operator == Operator.ARRAY_CONTAINS) { - return other instanceof ArrayValue && ((ArrayValue) other).getInternalValue().contains(value); + /** + * Gets a Filter instance for the provided path, operator, and value. + * + *

Note that if the relation operator is EQUAL and the value is null or NaN, this will return + * the appropriate NullFilter or NaNFilter class instead of a FieldFilter. + */ + public static FieldFilter create(FieldPath path, Operator operator, FieldValue value) { + if (path.isKeyField()) { + if (operator == Operator.IN) { + hardAssert( + value instanceof ArrayValue, + "Comparing on key with IN, but an array value was not a RefValue"); + return new KeyFieldInFilter(path, (ArrayValue) value); + } else { + hardAssert( + value instanceof ReferenceValue, + "Comparing on key, but filter value not a ReferenceValue"); + hardAssert( + operator != Operator.ARRAY_CONTAINS && operator != Operator.ARRAY_CONTAINS_ANY, + operator.toString() + "queries don't make sense on document keys"); + return new KeyFieldFilter(path, operator, (ReferenceValue) value); + } + } else if (value.equals(NullValue.nullValue())) { + if (operator != Filter.Operator.EQUAL) { + throw new IllegalArgumentException( + "Invalid Query. You can only perform equality comparisons on null (via " + + "whereEqualTo())."); + } + return new FieldFilter(path, operator, value); + } else if (value.equals(DoubleValue.NaN)) { + if (operator != Filter.Operator.EQUAL) { + throw new IllegalArgumentException( + "Invalid Query. You can only perform equality comparisons on NaN (via " + + "whereEqualTo())."); + } + return new FieldFilter(path, operator, value); + } else if (operator == Operator.ARRAY_CONTAINS) { + return new ArrayContainsFilter(path, value); } else if (operator == Operator.IN) { - hardAssert(value instanceof ArrayValue, "'in' filter has invalid value: " + value); - return ((ArrayValue) value).getInternalValue().contains(other); + hardAssert(value instanceof ArrayValue, "IN filter has invalid value: " + value.toString()); + return new InFilter(path, (ArrayValue) value); } else if (operator == Operator.ARRAY_CONTAINS_ANY) { hardAssert( - value instanceof ArrayValue, "'array_contains_any' filter has invalid value: " + value); - if (other instanceof ArrayValue) { - for (FieldValue val : ((ArrayValue) other).getInternalValue()) { - if (((ArrayValue) value).getInternalValue().contains(val)) { - return true; - } - } - } - return false; + value instanceof ArrayValue, + "ARRAY_CONTAINS_ANY filter has invalid value: " + value.toString()); + return new ArrayContainsAnyFilter(path, (ArrayValue) value); } else { - // Only compare types with matching backend order (such as double and int). - return value.typeOrder() == other.typeOrder() - && matchesComparison(other.compareTo(this.value)); + return new FieldFilter(path, operator, value); } } - private boolean matchesComparison(int comp) { + @Override + public boolean matches(Document doc) { + FieldValue other = doc.getField(field); + // Only compare types with matching backend order (such as double and int). + return other != null + && value.typeOrder() == other.typeOrder() + && this.matchesComparison(other.compareTo(value)); + } + + protected boolean matchesComparison(int comp) { switch (operator) { case LESS_THAN: return comp < 0; @@ -111,7 +130,7 @@ private boolean matchesComparison(int comp) { case GREATER_THAN_OR_EQUAL: return comp >= 0; default: - throw Assert.fail("Unknown operator: %s", operator); + throw Assert.fail("Unknown FieldFilter operator: %s", operator); } } @@ -138,10 +157,10 @@ public String toString() { @Override public boolean equals(Object o) { - if (o == null || !(o instanceof RelationFilter)) { + if (o == null || !(o instanceof FieldFilter)) { return false; } - RelationFilter other = (RelationFilter) o; + FieldFilter other = (FieldFilter) o; return operator == other.operator && field.equals(other.field) && value.equals(other.value); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/Filter.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/Filter.java index 353ede8ed6d..f1487ff8a8e 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/Filter.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/Filter.java @@ -16,9 +16,6 @@ import com.google.firebase.firestore.model.Document; import com.google.firebase.firestore.model.FieldPath; -import com.google.firebase.firestore.model.value.DoubleValue; -import com.google.firebase.firestore.model.value.FieldValue; -import com.google.firebase.firestore.model.value.NullValue; /** Interface used for all query filters. */ public abstract class Filter { @@ -44,32 +41,6 @@ public String toString() { } } - /** - * Gets a Filter instance for the provided path, operator, and value. - * - *

Note that if the relation operator is EQUAL and the value is null or NaN, this will return - * the appropriate NullFilter or NaNFilter class instead of a RelationFilter. - */ - public static Filter create(FieldPath path, Operator operator, FieldValue value) { - if (value.equals(NullValue.nullValue())) { - if (operator != Filter.Operator.EQUAL) { - throw new IllegalArgumentException( - "Invalid Query. You can only perform equality comparisons on null (via " - + "whereEqualTo())."); - } - return new NullFilter(path); - } else if (value.equals(DoubleValue.NaN)) { - if (operator != Filter.Operator.EQUAL) { - throw new IllegalArgumentException( - "Invalid Query. You can only perform equality comparisons on NaN (via " - + "whereEqualTo())."); - } - return new NaNFilter(path); - } else { - return new RelationFilter(path, operator, value); - } - } - /** Returns the field the Filter operates over. */ public abstract FieldPath getField(); diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/InFilter.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/InFilter.java new file mode 100644 index 00000000000..539d566db6b --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/InFilter.java @@ -0,0 +1,34 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.firestore.core; + +import com.google.firebase.firestore.model.Document; +import com.google.firebase.firestore.model.FieldPath; +import com.google.firebase.firestore.model.value.ArrayValue; +import com.google.firebase.firestore.model.value.FieldValue; + +/** A Filter that implements the IN operator. */ +public class InFilter extends FieldFilter { + InFilter(FieldPath field, ArrayValue value) { + super(field, Operator.IN, value); + } + + @Override + public boolean matches(Document doc) { + ArrayValue arrayValue = (ArrayValue) getValue(); + FieldValue other = doc.getField(getField()); + return other != null && arrayValue.getInternalValue().contains(other); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/KeyFieldFilter.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/KeyFieldFilter.java new file mode 100644 index 00000000000..3307d676007 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/KeyFieldFilter.java @@ -0,0 +1,33 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.firestore.core; + +import com.google.firebase.firestore.model.Document; +import com.google.firebase.firestore.model.FieldPath; +import com.google.firebase.firestore.model.value.ReferenceValue; + +/** Filter that matches on key fields (i.e. '__name__'). */ +public class KeyFieldFilter extends FieldFilter { + KeyFieldFilter(FieldPath field, Operator operator, ReferenceValue value) { + super(field, operator, value); + } + + @Override + public boolean matches(Document doc) { + ReferenceValue referenceValue = (ReferenceValue) getValue(); + int comparator = doc.getKey().compareTo(referenceValue.value()); + return this.matchesComparison(comparator); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/KeyFieldInFilter.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/KeyFieldInFilter.java new file mode 100644 index 00000000000..2931e7418be --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/KeyFieldInFilter.java @@ -0,0 +1,46 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.firestore.core; + +import static com.google.firebase.firestore.util.Assert.hardAssert; + +import com.google.firebase.firestore.model.Document; +import com.google.firebase.firestore.model.FieldPath; +import com.google.firebase.firestore.model.value.ArrayValue; +import com.google.firebase.firestore.model.value.FieldValue; +import com.google.firebase.firestore.model.value.ReferenceValue; + +public class KeyFieldInFilter extends FieldFilter { + KeyFieldInFilter(FieldPath field, ArrayValue value) { + super(field, Operator.IN, value); + ArrayValue arrayValue = (ArrayValue) getValue(); + for (FieldValue refValue : arrayValue.getInternalValue()) { + hardAssert( + refValue instanceof ReferenceValue, + "Comparing on key with IN, but an array value was not a ReferenceValue"); + } + } + + @Override + public boolean matches(Document doc) { + ArrayValue arrayValue = (ArrayValue) getValue(); + for (FieldValue refValue : arrayValue.getInternalValue()) { + if (doc.getKey().equals(((ReferenceValue) refValue).value())) { + return true; + } + } + return false; + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/NaNFilter.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/NaNFilter.java deleted file mode 100644 index acc26e6bf35..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/NaNFilter.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.firebase.firestore.core; - -import com.google.firebase.firestore.model.Document; -import com.google.firebase.firestore.model.FieldPath; -import com.google.firebase.firestore.model.value.DoubleValue; -import com.google.firebase.firestore.model.value.FieldValue; - -/** Filter that matches NaN (not-a-number) fields. */ -public class NaNFilter extends Filter { - private final FieldPath fieldPath; - - public NaNFilter(FieldPath fieldPath) { - this.fieldPath = fieldPath; - } - - @Override - public FieldPath getField() { - return fieldPath; - } - - @Override - public boolean matches(Document doc) { - FieldValue fieldValue = doc.getField(fieldPath); - return fieldValue != null && fieldValue.equals(DoubleValue.NaN); - } - - @Override - public String getCanonicalId() { - return fieldPath.canonicalString() + " IS NaN"; - } - - @Override - public String toString() { - return getCanonicalId(); - } - - @Override - public boolean equals(Object o) { - if (o == null || !(o instanceof NaNFilter)) { - return false; - } - NaNFilter other = (NaNFilter) o; - return fieldPath.equals(other.fieldPath); - } - - @Override - public int hashCode() { - int result = 41; - result = 31 * result + fieldPath.hashCode(); - return result; - } -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/NullFilter.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/NullFilter.java deleted file mode 100644 index d16ab24f0df..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/NullFilter.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.firebase.firestore.core; - -import com.google.firebase.firestore.model.Document; -import com.google.firebase.firestore.model.FieldPath; -import com.google.firebase.firestore.model.value.FieldValue; -import com.google.firebase.firestore.model.value.NullValue; - -/** Filter that matches NULL values. */ -public class NullFilter extends Filter { - private final FieldPath fieldPath; - - public NullFilter(FieldPath fieldPath) { - this.fieldPath = fieldPath; - } - - @Override - public FieldPath getField() { - return fieldPath; - } - - @Override - public boolean matches(Document doc) { - FieldValue fieldValue = doc.getField(fieldPath); - return fieldValue != null && fieldValue.equals(NullValue.nullValue()); - } - - @Override - public String getCanonicalId() { - return fieldPath.canonicalString() + " IS NULL"; - } - - @Override - public String toString() { - return getCanonicalId(); - } - - @Override - public boolean equals(Object o) { - if (o == null || !(o instanceof NullFilter)) { - return false; - } - NullFilter other = (NullFilter) o; - return fieldPath.equals(other.fieldPath); - } - - @Override - public int hashCode() { - int result = 37; - result = 31 * result + fieldPath.hashCode(); - return result; - } -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/Query.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/Query.java index b59971de074..cd2f9f893a9 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/Query.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/Query.java @@ -157,10 +157,10 @@ public FieldPath getFirstOrderByField() { @Nullable public FieldPath inequalityField() { for (Filter filter : filters) { - if (filter instanceof RelationFilter) { - RelationFilter relationFilter = (RelationFilter) filter; - if (relationFilter.isInequality()) { - return relationFilter.getField(); + if (filter instanceof FieldFilter) { + FieldFilter fieldfilter = (FieldFilter) filter; + if (fieldfilter.isInequality()) { + return fieldfilter.getField(); } } } @@ -172,12 +172,12 @@ public FieldPath inequalityField() { * one that is, or null if none are. */ @Nullable - public Operator findOperatorFilter(List filterOps) { + public Operator findFilterOperator(List operators) { for (Filter filter : filters) { - if (filter instanceof RelationFilter) { - Operator queryOp = ((RelationFilter) filter).getOperator(); - if (filterOps.contains(queryOp)) { - return queryOp; + if (filter instanceof FieldFilter) { + Operator filterOp = ((FieldFilter) filter).getOperator(); + if (operators.contains(filterOp)) { + return filterOp; } } } @@ -193,7 +193,7 @@ public Operator findOperatorFilter(List filterOps) { public Query filter(Filter filter) { hardAssert(!isDocumentQuery(), "No filter is allowed for document query"); FieldPath newInequalityField = null; - if (filter instanceof RelationFilter && ((RelationFilter) filter).isInequality()) { + if (filter instanceof FieldFilter && ((FieldFilter) filter).isInequality()) { newInequalityField = filter.getField(); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/local/IndexedQueryEngine.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/local/IndexedQueryEngine.java index 62186ae26e9..11a8e48e15b 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/local/IndexedQueryEngine.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/local/IndexedQueryEngine.java @@ -19,13 +19,11 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.firebase.database.collection.ImmutableSortedMap; +import com.google.firebase.firestore.core.FieldFilter; import com.google.firebase.firestore.core.Filter; import com.google.firebase.firestore.core.Filter.Operator; import com.google.firebase.firestore.core.IndexRange; -import com.google.firebase.firestore.core.NaNFilter; -import com.google.firebase.firestore.core.NullFilter; import com.google.firebase.firestore.core.Query; -import com.google.firebase.firestore.core.RelationFilter; import com.google.firebase.firestore.model.Document; import com.google.firebase.firestore.model.DocumentCollections; import com.google.firebase.firestore.model.DocumentKey; @@ -35,7 +33,6 @@ import com.google.firebase.firestore.model.value.BooleanValue; import com.google.firebase.firestore.model.value.DoubleValue; import com.google.firebase.firestore.model.value.FieldValue; -import com.google.firebase.firestore.model.value.NullValue; import com.google.firebase.firestore.model.value.ObjectValue; import com.google.firebase.firestore.util.Assert; import java.util.Arrays; @@ -156,18 +153,15 @@ private ImmutableSortedMap performQueryUsingIndex( * @return a number from 0.0 to 1.0 (inclusive), where higher numbers indicate higher selectivity */ private static double estimateFilterSelectivity(Filter filter) { - if (filter instanceof NullFilter) { - return HIGH_SELECTIVITY; - } else if (filter instanceof NaNFilter) { + hardAssert(filter instanceof FieldFilter, "Filter type expected to be FieldFilter"); + FieldFilter fieldFilter = (FieldFilter) filter; + if (fieldFilter.getValue().equals(null) || fieldFilter.getValue().equals(DoubleValue.NaN)) { return HIGH_SELECTIVITY; } else { - hardAssert(filter instanceof RelationFilter, "Filter type expected to be RelationFilter"); - RelationFilter relationFilter = (RelationFilter) filter; - double operatorSelectivity = - relationFilter.getOperator().equals(Operator.EQUAL) ? HIGH_SELECTIVITY : LOW_SELECTIVITY; + fieldFilter.getOperator().equals(Operator.EQUAL) ? HIGH_SELECTIVITY : LOW_SELECTIVITY; double typeSelectivity = - lowCardinalityTypes.contains(relationFilter.getValue().getClass()) + lowCardinalityTypes.contains(fieldFilter.getValue().getClass()) ? LOW_SELECTIVITY : HIGH_SELECTIVITY; @@ -216,10 +210,10 @@ static IndexRange extractBestIndexRange(Query query) { */ private static IndexRange convertFilterToIndexRange(Filter filter) { IndexRange.Builder indexRange = IndexRange.builder().setFieldPath(filter.getField()); - if (filter instanceof RelationFilter) { - RelationFilter relationFilter = (RelationFilter) filter; - FieldValue filterValue = relationFilter.getValue(); - switch (relationFilter.getOperator()) { + if (filter instanceof FieldFilter) { + FieldFilter fieldFilter = (FieldFilter) filter; + FieldValue filterValue = fieldFilter.getValue(); + switch (fieldFilter.getOperator()) { case EQUAL: indexRange.setStart(filterValue).setEnd(filterValue); break; @@ -235,10 +229,6 @@ private static IndexRange convertFilterToIndexRange(Filter filter) { // TODO: Add support for ARRAY_CONTAINS. throw Assert.fail("Unexpected operator in query filter"); } - } else if (filter instanceof NaNFilter) { - indexRange.setStart(DoubleValue.NaN).setEnd(DoubleValue.NaN); - } else if (filter instanceof NullFilter) { - indexRange.setStart(NullValue.nullValue()).setEnd(NullValue.nullValue()); } return indexRange.build(); } 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 e2696c25bf5..d7f468f5e55 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 @@ -23,13 +23,11 @@ import com.google.firebase.firestore.Blob; import com.google.firebase.firestore.GeoPoint; import com.google.firebase.firestore.core.Bound; +import com.google.firebase.firestore.core.FieldFilter; import com.google.firebase.firestore.core.Filter; -import com.google.firebase.firestore.core.NaNFilter; -import com.google.firebase.firestore.core.NullFilter; import com.google.firebase.firestore.core.OrderBy; import com.google.firebase.firestore.core.OrderBy.Direction; import com.google.firebase.firestore.core.Query; -import com.google.firebase.firestore.core.RelationFilter; import com.google.firebase.firestore.local.QueryData; import com.google.firebase.firestore.local.QueryPurpose; import com.google.firebase.firestore.model.DatabaseId; @@ -84,7 +82,6 @@ import com.google.firestore.v1.StructuredQuery; import com.google.firestore.v1.StructuredQuery.CollectionSelector; import com.google.firestore.v1.StructuredQuery.CompositeFilter; -import com.google.firestore.v1.StructuredQuery.FieldFilter; import com.google.firestore.v1.StructuredQuery.FieldReference; import com.google.firestore.v1.StructuredQuery.Filter.FilterTypeCase; import com.google.firestore.v1.StructuredQuery.Order; @@ -818,10 +815,8 @@ public Query decodeQueryTarget(QueryTarget target) { private StructuredQuery.Filter encodeFilters(List filters) { List protos = new ArrayList<>(filters.size()); for (Filter filter : filters) { - if (filter instanceof RelationFilter) { - protos.add(encodeRelationFilter((RelationFilter) filter)); - } else { - protos.add(encodeUnaryFilter(filter)); + if (filter instanceof FieldFilter) { + protos.add(encodeUnaryOrFieldFilter((FieldFilter) filter)); } } if (filters.size() == 1) { @@ -853,7 +848,7 @@ private List decodeFilters(StructuredQuery.Filter proto) { throw fail("Nested composite filters are not supported."); case FIELD_FILTER: - result.add(decodeRelationFilter(filter.getFieldFilter())); + result.add(decodeFieldFilter(filter.getFieldFilter())); break; case UNARY_FILTER: @@ -869,42 +864,41 @@ private List decodeFilters(StructuredQuery.Filter proto) { } @VisibleForTesting - StructuredQuery.Filter encodeRelationFilter(RelationFilter filter) { - FieldFilter.Builder proto = FieldFilter.newBuilder(); + StructuredQuery.Filter encodeUnaryOrFieldFilter(FieldFilter filter) { + if (filter.getOperator() == Filter.Operator.EQUAL) { + UnaryFilter.Builder unaryProto = UnaryFilter.newBuilder(); + unaryProto.setField(encodeFieldPath(filter.getField())); + if (filter.getValue().equals(DoubleValue.NaN)) { + unaryProto.setOp(UnaryFilter.Operator.IS_NAN); + return StructuredQuery.Filter.newBuilder().setUnaryFilter(unaryProto).build(); + } else if (filter.getValue().equals(NullValue.nullValue())) { + unaryProto.setOp(UnaryFilter.Operator.IS_NULL); + return StructuredQuery.Filter.newBuilder().setUnaryFilter(unaryProto).build(); + } + } + StructuredQuery.FieldFilter.Builder proto = StructuredQuery.FieldFilter.newBuilder(); proto.setField(encodeFieldPath(filter.getField())); - proto.setOp(encodeRelationFilterOperator(filter.getOperator())); + proto.setOp(encodeFieldFilterOperator(filter.getOperator())); proto.setValue(encodeValue(filter.getValue())); return StructuredQuery.Filter.newBuilder().setFieldFilter(proto).build(); } - private Filter decodeRelationFilter(StructuredQuery.FieldFilter proto) { + @VisibleForTesting + FieldFilter decodeFieldFilter(StructuredQuery.FieldFilter proto) { FieldPath fieldPath = FieldPath.fromServerFormat(proto.getField().getFieldPath()); - RelationFilter.Operator filterOperator = decodeRelationFilterOperator(proto.getOp()); + FieldFilter.Operator filterOperator = decodeFieldFilterOperator(proto.getOp()); FieldValue value = decodeValue(proto.getValue()); - return Filter.create(fieldPath, filterOperator, value); - } - - private StructuredQuery.Filter encodeUnaryFilter(Filter filter) { - UnaryFilter.Builder proto = UnaryFilter.newBuilder(); - proto.setField(encodeFieldPath(filter.getField())); - if (filter instanceof NaNFilter) { - proto.setOp(UnaryFilter.Operator.IS_NAN); - } else if (filter instanceof NullFilter) { - proto.setOp(UnaryFilter.Operator.IS_NULL); - } else { - throw fail("Unrecognized filter: %s", filter.getCanonicalId()); - } - return StructuredQuery.Filter.newBuilder().setUnaryFilter(proto).build(); + return FieldFilter.create(fieldPath, filterOperator, value); } private Filter decodeUnaryFilter(StructuredQuery.UnaryFilter proto) { FieldPath fieldPath = FieldPath.fromServerFormat(proto.getField().getFieldPath()); switch (proto.getOp()) { case IS_NAN: - return new NaNFilter(fieldPath); + return FieldFilter.create(fieldPath, Filter.Operator.EQUAL, DoubleValue.NaN); case IS_NULL: - return new NullFilter(fieldPath); + return FieldFilter.create(fieldPath, Filter.Operator.EQUAL, NullValue.nullValue()); default: throw fail("Unrecognized UnaryFilter.operator %d", proto.getOp()); @@ -915,47 +909,49 @@ private FieldReference encodeFieldPath(FieldPath field) { return FieldReference.newBuilder().setFieldPath(field.canonicalString()).build(); } - private FieldFilter.Operator encodeRelationFilterOperator(RelationFilter.Operator operator) { + private StructuredQuery.FieldFilter.Operator encodeFieldFilterOperator( + FieldFilter.Operator operator) { switch (operator) { case LESS_THAN: - return FieldFilter.Operator.LESS_THAN; + return StructuredQuery.FieldFilter.Operator.LESS_THAN; case LESS_THAN_OR_EQUAL: - return FieldFilter.Operator.LESS_THAN_OR_EQUAL; + return StructuredQuery.FieldFilter.Operator.LESS_THAN_OR_EQUAL; case EQUAL: - return FieldFilter.Operator.EQUAL; + return StructuredQuery.FieldFilter.Operator.EQUAL; case GREATER_THAN: - return FieldFilter.Operator.GREATER_THAN; + return StructuredQuery.FieldFilter.Operator.GREATER_THAN; case GREATER_THAN_OR_EQUAL: - return FieldFilter.Operator.GREATER_THAN_OR_EQUAL; + return StructuredQuery.FieldFilter.Operator.GREATER_THAN_OR_EQUAL; case ARRAY_CONTAINS: - return FieldFilter.Operator.ARRAY_CONTAINS; + return StructuredQuery.FieldFilter.Operator.ARRAY_CONTAINS; case IN: - return FieldFilter.Operator.IN; + return StructuredQuery.FieldFilter.Operator.IN; case ARRAY_CONTAINS_ANY: - return FieldFilter.Operator.ARRAY_CONTAINS_ANY; + return StructuredQuery.FieldFilter.Operator.ARRAY_CONTAINS_ANY; default: throw fail("Unknown operator %d", operator); } } - private RelationFilter.Operator decodeRelationFilterOperator(FieldFilter.Operator operator) { + private FieldFilter.Operator decodeFieldFilterOperator( + StructuredQuery.FieldFilter.Operator operator) { switch (operator) { case LESS_THAN: - return RelationFilter.Operator.LESS_THAN; + return FieldFilter.Operator.LESS_THAN; case LESS_THAN_OR_EQUAL: - return RelationFilter.Operator.LESS_THAN_OR_EQUAL; + return FieldFilter.Operator.LESS_THAN_OR_EQUAL; case EQUAL: - return RelationFilter.Operator.EQUAL; + return FieldFilter.Operator.EQUAL; case GREATER_THAN_OR_EQUAL: - return RelationFilter.Operator.GREATER_THAN_OR_EQUAL; + return FieldFilter.Operator.GREATER_THAN_OR_EQUAL; case GREATER_THAN: - return RelationFilter.Operator.GREATER_THAN; + return FieldFilter.Operator.GREATER_THAN; case ARRAY_CONTAINS: - return RelationFilter.Operator.ARRAY_CONTAINS; + return FieldFilter.Operator.ARRAY_CONTAINS; case IN: - return RelationFilter.Operator.IN; + return FieldFilter.Operator.IN; case ARRAY_CONTAINS_ANY: - return RelationFilter.Operator.ARRAY_CONTAINS_ANY; + return FieldFilter.Operator.ARRAY_CONTAINS_ANY; default: throw fail("Unhandled FieldFilter.operator %d", operator); } diff --git a/firebase-firestore/src/test/java/com/google/firebase/firestore/remote/RemoteSerializerTest.java b/firebase-firestore/src/test/java/com/google/firebase/firestore/remote/RemoteSerializerTest.java index bb2f072dec7..66a5f45e20b 100644 --- a/firebase-firestore/src/test/java/com/google/firebase/firestore/remote/RemoteSerializerTest.java +++ b/firebase-firestore/src/test/java/com/google/firebase/firestore/remote/RemoteSerializerTest.java @@ -31,12 +31,16 @@ import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import com.google.firebase.firestore.DocumentReference; import com.google.firebase.firestore.GeoPoint; +import com.google.firebase.firestore.core.ArrayContainsAnyFilter; import com.google.firebase.firestore.core.Bound; +import com.google.firebase.firestore.core.FieldFilter; +import com.google.firebase.firestore.core.InFilter; +import com.google.firebase.firestore.core.KeyFieldFilter; import com.google.firebase.firestore.core.Query; -import com.google.firebase.firestore.core.RelationFilter; import com.google.firebase.firestore.local.QueryData; import com.google.firebase.firestore.local.QueryPurpose; import com.google.firebase.firestore.model.DatabaseId; @@ -65,7 +69,6 @@ import com.google.firestore.v1.StructuredQuery.CollectionSelector; import com.google.firestore.v1.StructuredQuery.CompositeFilter; import com.google.firestore.v1.StructuredQuery.Direction; -import com.google.firestore.v1.StructuredQuery.FieldFilter; import com.google.firestore.v1.StructuredQuery.FieldFilter.Operator; import com.google.firestore.v1.StructuredQuery.FieldReference; import com.google.firestore.v1.StructuredQuery.Filter; @@ -548,7 +551,7 @@ public void testEncodesSingleFilterAtFirstLevelCollections() { .setWhere( Filter.newBuilder() .setFieldFilter( - FieldFilter.newBuilder() + StructuredQuery.FieldFilter.newBuilder() .setField(FieldReference.newBuilder().setFieldPath("prop")) .setOp(Operator.LESS_THAN) .setValue(valueBuilder().setIntegerValue(42)))) @@ -592,7 +595,7 @@ public void testEncodesMultipleFiltersOnDeeperCollections() { .addFilters( Filter.newBuilder() .setFieldFilter( - FieldFilter.newBuilder() + StructuredQuery.FieldFilter.newBuilder() .setField( FieldReference.newBuilder().setFieldPath("prop")) .setOp(Operator.LESS_THAN) @@ -600,7 +603,7 @@ public void testEncodesMultipleFiltersOnDeeperCollections() { .addFilters( Filter.newBuilder() .setFieldFilter( - FieldFilter.newBuilder() + StructuredQuery.FieldFilter.newBuilder() .setField( FieldReference.newBuilder().setFieldPath("author")) .setOp(Operator.EQUAL) @@ -608,7 +611,7 @@ public void testEncodesMultipleFiltersOnDeeperCollections() { .addFilters( Filter.newBuilder() .setFieldFilter( - FieldFilter.newBuilder() + StructuredQuery.FieldFilter.newBuilder() .setField( FieldReference.newBuilder().setFieldPath("tags")) .setOp(Operator.ARRAY_CONTAINS) @@ -635,43 +638,72 @@ public void testEncodesMultipleFiltersOnDeeperCollections() { @Test public void testInSerialization() { - StructuredQuery.Filter filter = - serializer.encodeRelationFilter(((RelationFilter) filter("field", "in", asList(42)))); + FieldFilter inputFilter = filter("field", "in", asList(42)); + StructuredQuery.Filter apiFilter = serializer.encodeUnaryOrFieldFilter(inputFilter); ArrayValue.Builder inFilterValue = ArrayValue.newBuilder().addValues(valueBuilder().setIntegerValue(42)); StructuredQuery.Filter expectedFilter = Filter.newBuilder() .setFieldFilter( - FieldFilter.newBuilder() + StructuredQuery.FieldFilter.newBuilder() .setField(FieldReference.newBuilder().setFieldPath("field")) .setOp(Operator.IN) .setValue(valueBuilder().setArrayValue(inFilterValue)) .build()) .build(); - assertEquals(filter, expectedFilter); + assertEquals(apiFilter, expectedFilter); + FieldFilter roundTripped = serializer.decodeFieldFilter(apiFilter.getFieldFilter()); + assertEquals(roundTripped, inputFilter); + assertTrue(roundTripped instanceof InFilter); } @Test public void testArrayContainsAnySerialization() { - StructuredQuery.Filter filter = - serializer.encodeRelationFilter( - ((RelationFilter) filter("field", "array-contains-any", asList(42)))); + FieldFilter inputFilter = filter("field", "array-contains-any", asList(42)); + StructuredQuery.Filter apiFilter = serializer.encodeUnaryOrFieldFilter(inputFilter); ArrayValue.Builder arrayContainsAnyFilterValue = ArrayValue.newBuilder().addValues(valueBuilder().setIntegerValue(42)); StructuredQuery.Filter expectedFilter = Filter.newBuilder() .setFieldFilter( - FieldFilter.newBuilder() + StructuredQuery.FieldFilter.newBuilder() .setField(FieldReference.newBuilder().setFieldPath("field")) .setOp(Operator.ARRAY_CONTAINS_ANY) .setValue(valueBuilder().setArrayValue(arrayContainsAnyFilterValue)) .build()) .build(); - assertEquals(filter, expectedFilter); + assertEquals(apiFilter, expectedFilter); + FieldFilter roundTripped = serializer.decodeFieldFilter(apiFilter.getFieldFilter()); + assertEquals(roundTripped, inputFilter); + assertTrue(roundTripped instanceof ArrayContainsAnyFilter); + } + + @Test + public void testKeyFieldSerializationEncoding() { + FieldFilter inputFilter = filter("__name__", "==", ref("project/database")); + StructuredQuery.Filter apiFilter = serializer.encodeUnaryOrFieldFilter(inputFilter); + + StructuredQuery.Filter expectedFilter = + Filter.newBuilder() + .setFieldFilter( + StructuredQuery.FieldFilter.newBuilder() + .setField(FieldReference.newBuilder().setFieldPath("__name__")) + .setOp(Operator.EQUAL) + .setValue( + valueBuilder() + .setReferenceValue( + "projects/project/databases/(default)/documents/project/database")) + .build()) + .build(); + + assertEquals(apiFilter, expectedFilter); + FieldFilter roundTripped = serializer.decodeFieldFilter(apiFilter.getFieldFilter()); + assertEquals(roundTripped, inputFilter); + assertTrue(roundTripped instanceof KeyFieldFilter); } // TODO(PORTING NOTE): Android currently tests most filter serialization (for equals, greater diff --git a/firebase-firestore/src/testUtil/java/com/google/firebase/firestore/testutil/TestUtil.java b/firebase-firestore/src/testUtil/java/com/google/firebase/firestore/testutil/TestUtil.java index a7205249dc3..3dea69b8c81 100644 --- a/firebase-firestore/src/testUtil/java/com/google/firebase/firestore/testutil/TestUtil.java +++ b/firebase-firestore/src/testUtil/java/com/google/firebase/firestore/testutil/TestUtil.java @@ -33,6 +33,7 @@ import com.google.firebase.firestore.DocumentReference; import com.google.firebase.firestore.TestAccessHelper; import com.google.firebase.firestore.UserDataConverter; +import com.google.firebase.firestore.core.FieldFilter; import com.google.firebase.firestore.core.Filter; import com.google.firebase.firestore.core.Filter.Operator; import com.google.firebase.firestore.core.OrderBy; @@ -220,8 +221,13 @@ public static ImmutableSortedSet keySet(DocumentKey... keys) { return keySet; } - public static Filter filter(String key, String operator, Object value) { - return Filter.create(field(key), operatorFromString(operator), wrap(value)); + public static FieldFilter filter(String key, String operator, Object value) { + Filter filter = FieldFilter.create(field(key), operatorFromString(operator), wrap(value)); + if (filter instanceof FieldFilter) { + return (FieldFilter) filter; + } else { + throw new IllegalArgumentException("Unrecognized filter: " + filter.toString()); + } } public static Operator operatorFromString(String s) {