Skip to content

Commit 54030f5

Browse files
committed
Port FieldFilter refactor from the JS SDK
firebase/firebase-js-sdk#1894
1 parent 5c29499 commit 54030f5

File tree

18 files changed

+114
-320
lines changed

18 files changed

+114
-320
lines changed

Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ - (void)testEncodesListenRequestLabels {
530530

531531
- (void)testEncodesFieldFilter {
532532
auto input = std::static_pointer_cast<FieldFilter>(Filter("item.part.top", "==", "food"));
533-
GCFSStructuredQuery_Filter *actual = [self.serializer encodedFieldFilter:*input];
533+
GCFSStructuredQuery_Filter *actual = [self.serializer encodedUnaryOrFieldFilter:*input];
534534

535535
GCFSStructuredQuery_Filter *expected = [GCFSStructuredQuery_Filter message];
536536
GCFSStructuredQuery_FieldFilter *prop = expected.fieldFilter;
@@ -542,7 +542,7 @@ - (void)testEncodesFieldFilter {
542542

543543
- (void)testEncodesArrayContainsFilter {
544544
auto input = std::static_pointer_cast<FieldFilter>(Filter("item.tags", "array_contains", "food"));
545-
GCFSStructuredQuery_Filter *actual = [self.serializer encodedFieldFilter:*input];
545+
GCFSStructuredQuery_Filter *actual = [self.serializer encodedUnaryOrFieldFilter:*input];
546546

547547
GCFSStructuredQuery_Filter *expected = [GCFSStructuredQuery_Filter message];
548548
GCFSStructuredQuery_FieldFilter *prop = expected.fieldFilter;

Firestore/Source/Core/FSTQuery.mm

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
#include "Firestore/core/src/firebase/firestore/api/input_validation.h"
3030
#include "Firestore/core/src/firebase/firestore/core/field_filter.h"
3131
#include "Firestore/core/src/firebase/firestore/core/filter.h"
32-
#include "Firestore/core/src/firebase/firestore/core/nan_filter.h"
33-
#include "Firestore/core/src/firebase/firestore/core/null_filter.h"
3432
#include "Firestore/core/src/firebase/firestore/core/query.h"
3533
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
3634
#include "Firestore/core/src/firebase/firestore/model/field_path.h"

Firestore/Source/Remote/FSTSerializerBeta.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ NS_ASSUME_NONNULL_BEGIN
120120
- (GCFSTarget_QueryTarget *)encodedQueryTarget:(FSTQuery *)query;
121121
- (FSTQuery *)decodedQueryFromQueryTarget:(GCFSTarget_QueryTarget *)target;
122122

123-
- (GCFSStructuredQuery_Filter *)encodedFieldFilter:(const core::FieldFilter &)filter;
123+
- (GCFSStructuredQuery_Filter *)encodedUnaryOrFieldFilter:(const core::FieldFilter &)filter;
124124

125125
- (std::unique_ptr<remote::WatchChange>)decodedWatchChange:(GCFSListenResponse *)watchChange;
126126
- (model::SnapshotVersion)versionFromListenResponse:(GCFSListenResponse *)watchChange;

Firestore/Source/Remote/FSTSerializerBeta.mm

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@
4343
#include "Firestore/core/include/firebase/firestore/geo_point.h"
4444
#include "Firestore/core/src/firebase/firestore/core/field_filter.h"
4545
#include "Firestore/core/src/firebase/firestore/core/filter.h"
46-
#include "Firestore/core/src/firebase/firestore/core/nan_filter.h"
47-
#include "Firestore/core/src/firebase/firestore/core/null_filter.h"
4846
#include "Firestore/core/src/firebase/firestore/core/query.h"
4947
#include "Firestore/core/src/firebase/firestore/model/database_id.h"
5048
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
@@ -98,6 +96,8 @@
9896
using firebase::firestore::remote::WatchTargetChange;
9997
using firebase::firestore::remote::WatchTargetChangeState;
10098

99+
using Operator = Filter::Operator;
100+
101101
NS_ASSUME_NONNULL_BEGIN
102102

103103
@implementation FSTSerializerBeta {
@@ -888,10 +888,8 @@ - (GCFSStructuredQuery_Filter *_Nullable)encodedFilters:(const Query::FilterList
888888
}
889889
NSMutableArray<GCFSStructuredQuery_Filter *> *protos = [NSMutableArray array];
890890
for (const auto &filter : filters) {
891-
if (filter->type() == Filter::Type::kFieldFilter) {
892-
[protos addObject:[self encodedFieldFilter:static_cast<const FieldFilter &>(*filter)]];
893-
} else {
894-
[protos addObject:[self encodedUnaryFilter:*filter]];
891+
if (filter->IsFieldFilter()) {
892+
[protos addObject:[self encodedUnaryOrFieldFilter:static_cast<const FieldFilter &>(*filter)]];
895893
}
896894
}
897895
if (protos.count == 1) {
@@ -938,8 +936,23 @@ - (GCFSStructuredQuery_Filter *_Nullable)encodedFilters:(const Query::FilterList
938936
return result;
939937
}
940938

941-
- (GCFSStructuredQuery_Filter *)encodedFieldFilter:(const FieldFilter &)filter {
939+
- (GCFSStructuredQuery_Filter *)encodedUnaryOrFieldFilter:(const FieldFilter &)filter {
942940
GCFSStructuredQuery_Filter *proto = [GCFSStructuredQuery_Filter message];
941+
942+
if (filter.op() == Operator::Equal) {
943+
if (filter.value().is_null()) {
944+
proto.unaryFilter.field = [self encodedFieldPath:filter.field()];
945+
proto.unaryFilter.op = GCFSStructuredQuery_UnaryFilter_Operator_IsNull;
946+
return proto;
947+
}
948+
949+
if (filter.value().is_nan()) {
950+
proto.unaryFilter.field = [self encodedFieldPath:filter.field()];
951+
proto.unaryFilter.op = GCFSStructuredQuery_UnaryFilter_Operator_IsNan;
952+
return proto;
953+
}
954+
}
955+
943956
GCFSStructuredQuery_FieldFilter *fieldFilter = proto.fieldFilter;
944957
fieldFilter.field = [self encodedFieldPath:filter.field()];
945958
fieldFilter.op = [self encodedFieldFilterOperator:filter.op()];
@@ -954,27 +967,14 @@ - (GCFSStructuredQuery_Filter *)encodedFieldFilter:(const FieldFilter &)filter {
954967
return FieldFilter::Create(std::move(fieldPath), op, std::move(value));
955968
}
956969

957-
- (GCFSStructuredQuery_Filter *)encodedUnaryFilter:(const Filter &)filter {
958-
GCFSStructuredQuery_Filter *proto = [GCFSStructuredQuery_Filter message];
959-
proto.unaryFilter.field = [self encodedFieldPath:filter.field()];
960-
if (filter.type() == Filter::Type::kNanFilter) {
961-
proto.unaryFilter.op = GCFSStructuredQuery_UnaryFilter_Operator_IsNan;
962-
} else if (filter.type() == Filter::Type::kNullFilter) {
963-
proto.unaryFilter.op = GCFSStructuredQuery_UnaryFilter_Operator_IsNull;
964-
} else {
965-
HARD_FAIL("Unrecognized filter: %s", filter.ToString());
966-
}
967-
return proto;
968-
}
969-
970970
- (std::shared_ptr<Filter>)decodedUnaryFilter:(GCFSStructuredQuery_UnaryFilter *)proto {
971971
FieldPath field = FieldPath::FromServerFormat(util::MakeString(proto.field.fieldPath));
972972
switch (proto.op) {
973973
case GCFSStructuredQuery_UnaryFilter_Operator_IsNan:
974-
return std::make_shared<core::NanFilter>(std::move(field));
974+
return FieldFilter::Create(std::move(field), Operator::Equal, FieldValue::Nan());
975975

976976
case GCFSStructuredQuery_UnaryFilter_Operator_IsNull:
977-
return std::make_shared<core::NullFilter>(std::move(field));
977+
return FieldFilter::Create(std::move(field), Operator::Equal, FieldValue::Null());
978978

979979
default:
980980
HARD_FAIL("Unrecognized UnaryFilter.operator %s", proto.op);

Firestore/core/src/firebase/firestore/api/query_core.mm

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -267,32 +267,49 @@ QuerySnapshot result(firestore_, query_, std::move(snapshot),
267267
return Wrap([query() queryByAddingEndAt:bound]);
268268
}
269269

270-
void Query::ValidateNewFilter(const Filter& filter) const {
271-
if (filter->type() == Filter::Type::kRelationFilter) {
272-
const auto& field_filter = static_cast<const FieldFilter&>(*filter);
273-
Operator filter_op = field_filter.op();
270+
namespace {
271+
272+
constexpr Operator kArrayOps[] = {
273+
Operator::ArrayContains
274+
};
275+
276+
}
277+
278+
void Query::ValidateNewFilter(const class Filter& filter) const {
279+
if (filter.IsFieldFilter()) {
280+
const auto& field_filter = static_cast<const FieldFilter&>(filter);
281+
274282
if (field_filter.IsInequality()) {
275283
const FieldPath* existing_inequality = [query_ inequalityFilterField];
276284
const FieldPath* new_inequality = &filter.field();
277285

278286
if (existing_inequality && *existing_inequality != *new_inequality) {
279287
ThrowInvalidArgument(
280288
"Invalid Query. All where filters with an inequality (lessThan, "
281-
"lessThanOrEqual, greaterThan, or greaterThanOrEqual) must be on the "
289+
"lessThanOrEqual, greaterThan, or greaterThanOrEqual) must be on "
290+
"the "
282291
"same field. But you have inequality filters on '%s' and '%s'",
283-
existing_inequality->CanonicalString(), new_inequality->CanonicalString());
292+
existing_inequality->CanonicalString(),
293+
new_inequality->CanonicalString());
284294
}
285295

286296
const FieldPath* first_order_by_field = [query_ firstSortOrderField];
287297
if (first_order_by_field) {
288298
ValidateOrderByField(*first_order_by_field, filter.field());
289299
}
290-
} else if (filter_op == Operator::ArrayContains) {
291-
if ([query_ hasArrayContainsFilter]) {
292-
ThrowInvalidArgument(
293-
"Invalid Query. Queries only support a single arrayContains filter.");
300+
301+
} else {
302+
// You can have at most 1 disjunctive filter and 1 array filter. Check if
303+
// the new filter conflicts with an existing one.
304+
Operator filter_op = field_filter.op();
305+
bool is_array_op = absl::c_linear_search(kArrayOps, filter_op);
306+
307+
if (is_array_op && [query_ hasArrayContainsFilter]) {
308+
ThrowInvalidArgument("Invalid Query. Queries only support a single "
309+
"arrayContains filter.");
294310
}
295311
}
312+
}
296313
}
297314

298315
void Query::ValidateNewOrderByPath(const FieldPath& fieldPath) const {

Firestore/core/src/firebase/firestore/core/CMakeLists.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@ cc_library(
2828
key_field_filter.cc
2929
key_field_filter.h
3030
listen_options.h
31-
nan_filter.cc
32-
nan_filter.h
33-
null_filter.cc
34-
null_filter.h
3531
query.cc
3632
query.h
3733
target_id_generator.cc

Firestore/core/src/firebase/firestore/core/array_contains_filter.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ class ArrayContainsFilter : public FieldFilter {
3535

3636
ArrayContainsFilter(model::FieldPath field, model::FieldValue value);
3737

38+
Type type() const override {
39+
return Type::kArrayContainsFilter;
40+
}
41+
3842
bool Matches(const model::Document& doc) const override;
3943
};
4044

Firestore/core/src/firebase/firestore/core/field_filter.cc

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
#include <vector>
2121

2222
#include "Firestore/core/src/firebase/firestore/api/input_validation.h"
23-
#include "Firestore/core/src/firebase/firestore/core/nan_filter.h"
24-
#include "Firestore/core/src/firebase/firestore/core/null_filter.h"
23+
#include "Firestore/core/src/firebase/firestore/core/array_contains_filter.h"
24+
#include "Firestore/core/src/firebase/firestore/core/key_field_filter.h"
2525
#include "Firestore/core/src/firebase/firestore/util/hashing.h"
2626
#include "absl/algorithm/container.h"
2727
#include "absl/strings/str_cat.h"
@@ -62,21 +62,37 @@ const char* Describe(Filter::Operator op) {
6262
std::shared_ptr<FieldFilter> FieldFilter::Create(FieldPath path,
6363
Operator op,
6464
FieldValue value_rhs) {
65-
if (value_rhs.type() == FieldValue::Type::Null) {
65+
if (path.IsKeyFieldPath()) {
66+
HARD_ASSERT(value_rhs.type() == FieldValue::Type::Reference,
67+
"Comparing on key, but filter value not a Reference.");
68+
HARD_ASSERT(op != Filter::Operator::ArrayContains,
69+
"arrayContains queries don't make sense on document keys.");
70+
return std::make_shared<KeyFieldFilter>(std::move(path), op,
71+
std::move(value_rhs));
72+
73+
} else if (value_rhs.type() == FieldValue::Type::Null) {
6674
if (op != Filter::Operator::Equal) {
6775
ThrowInvalidArgument(
6876
"Invalid Query. Null supports only equality comparisons.");
6977
}
70-
return std::make_shared<NullFilter>(std::move(path));
78+
FieldFilter filter(std::move(path), op, std::move(value_rhs));
79+
return std::make_shared<FieldFilter>(std::move(filter));
80+
7181
} else if (value_rhs.is_nan()) {
7282
if (op != Filter::Operator::Equal) {
7383
ThrowInvalidArgument(
7484
"Invalid Query. NaN supports only equality comparisons.");
7585
}
76-
return std::make_shared<NanFilter>(std::move(path));
86+
FieldFilter filter(std::move(path), op, std::move(value_rhs));
87+
return std::make_shared<FieldFilter>(std::move(filter));
88+
89+
} else if (op == Operator::ArrayContains) {
90+
return std::make_shared<ArrayContainsFilter>(std::move(path),
91+
std::move(value_rhs));
92+
7793
} else {
78-
return std::make_shared<FieldFilter>(std::move(path), op,
79-
std::move(value_rhs));
94+
FieldFilter filter(std::move(path), op, std::move(value_rhs));
95+
return std::make_shared<FieldFilter>(std::move(filter));
8096
}
8197
}
8298

@@ -89,31 +105,14 @@ const FieldPath& FieldFilter::field() const {
89105
}
90106

91107
bool FieldFilter::Matches(const model::Document& doc) const {
92-
if (field_.IsKeyFieldPath()) {
93-
HARD_ASSERT(value_rhs_.type() == FieldValue::Type::Reference,
94-
"Comparing on key, but filter value not a Reference.");
95-
HARD_ASSERT(op_ != Filter::Operator::ArrayContains,
96-
"arrayContains queries don't make sense on document keys.");
97-
const auto& ref = value_rhs_.reference_value();
98-
ComparisonResult comparison = doc.key().CompareTo(ref.key());
99-
return MatchesComparison(comparison);
100-
} else {
101-
absl::optional<FieldValue> doc_field_value = doc.field(field_);
102-
return doc_field_value && MatchesValue(doc_field_value.value());
103-
}
104-
}
108+
absl::optional<FieldValue> maybe_lhs = doc.field(field_);
109+
if (!maybe_lhs) return false;
105110

106-
bool FieldFilter::MatchesValue(const FieldValue& lhs) const {
107-
if (op_ == Filter::Operator::ArrayContains) {
108-
if (lhs.type() != FieldValue::Type::Array) return false;
111+
const FieldValue& lhs = *maybe_lhs;
109112

110-
const auto& contents = lhs.array_value();
111-
return absl::c_linear_search(contents, value_rhs_);
112-
} else {
113-
// Only compare types with matching backend order (such as double and int).
114-
return FieldValue::Comparable(lhs.type(), value_rhs_.type()) &&
115-
MatchesComparison(lhs.CompareTo(value_rhs_));
116-
}
113+
// Only compare types with matching backend order (such as double and int).
114+
return FieldValue::Comparable(lhs.type(), value_rhs_.type()) &&
115+
MatchesComparison(lhs.CompareTo(value_rhs_));
117116
}
118117

119118
bool FieldFilter::MatchesComparison(ComparisonResult comparison) const {
@@ -130,10 +129,9 @@ bool FieldFilter::MatchesComparison(ComparisonResult comparison) const {
130129
comparison == ComparisonResult::Same;
131130
case Operator::GreaterThan:
132131
return comparison == ComparisonResult::Descending;
133-
case Operator::ArrayContains:
134-
HARD_FAIL("Should have been handled in MatchesValue()");
132+
default:
133+
HARD_FAIL("Operator %s unsuitable for comparison", op_);
135134
}
136-
UNREACHABLE();
137135
}
138136

139137
std::string FieldFilter::CanonicalId() const {
@@ -155,7 +153,7 @@ bool FieldFilter::IsInequality() const {
155153
}
156154

157155
bool FieldFilter::Equals(const Filter& other) const {
158-
if (other.type() != Type::kFieldFilter) return false;
156+
if (type() != other.type()) return false;
159157

160158
const auto& other_filter = static_cast<const FieldFilter&>(other);
161159
return op_ == other_filter.op_ && field_ == other_filter.field_ &&

Firestore/core/src/firebase/firestore/core/field_filter.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ class FieldFilter : public Filter {
4848
return Type::kFieldFilter;
4949
}
5050

51+
bool IsFieldFilter() const override {
52+
return true;
53+
}
54+
5155
const model::FieldPath& field() const override;
5256

5357
Operator op() const {

Firestore/core/src/firebase/firestore/core/filter.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,26 @@ class Filter {
4848
// For lack of RTTI, all subclasses must identify themselves so that
4949
// comparisons properly take type into account.
5050
enum class Type {
51+
kArrayContainsFilter,
5152
kFieldFilter,
52-
kNanFilter,
53-
kNullFilter,
53+
kKeyFieldFilter,
5454
};
5555

5656
virtual ~Filter() = default;
5757

5858
virtual Type type() const = 0;
5959

60+
/**
61+
* Returns true if this instance is FieldFilter or any derived class.
62+
* Equivalent to `instanceof FieldFilter` on other platforms.
63+
*
64+
* Note this is different than checking `type() == Type::kFieldFilter` which
65+
* is only true if the type is exactly FieldFilter.
66+
*/
67+
virtual bool IsFieldFilter() const {
68+
return false;
69+
}
70+
6071
/** Returns the field the Filter operates over. */
6172
virtual const model::FieldPath& field() const = 0;
6273

Firestore/core/src/firebase/firestore/core/key_field_filter.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ class KeyFieldFilter : public FieldFilter {
3636
core::Filter::Operator op,
3737
model::FieldValue value);
3838

39+
Type type() const override {
40+
return Type::kKeyFieldFilter;
41+
}
42+
3943
bool Matches(const model::Document& doc) const override;
4044
};
4145

0 commit comments

Comments
 (0)