Skip to content

Commit 3512851

Browse files
authored
Implement the Composite Filter class (#9734)
* Implement the Composite Filter class * Polish code * add tests for composite filter * fix broken test * Improve the implementation of flattern filters * change the flatten_filters implementation with some other things * Change the implementation of Filter in Composite Filter * Address feedback * Match Android Tests
1 parent cc9c626 commit 3512851

File tree

21 files changed

+851
-223
lines changed

21 files changed

+851
-223
lines changed

Firestore/Example/Firestore.xcodeproj/project.pbxproj

Lines changed: 14 additions & 14 deletions
Large diffs are not rendered by default.

Firestore/Example/Tests/Integration/API/FIRValidationTests.mm

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
2828
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
2929

30+
#import "Firestore/Source/API/FIRFilter+Internal.h"
3031
#include "Firestore/core/test/unit/testutil/app_testing.h"
3132

3233
using firebase::firestore::testutil::AppForUnitTesting;
@@ -602,6 +603,92 @@ - (void)testQueriesUsingInAndDocumentIdMustHaveProperDocumentReferencesInArray {
602603
reason);
603604
}
604605

606+
- (void)testInvalidQueryFilters {
607+
FIRCollectionReference *collection = [self collectionRef];
608+
609+
// Multiple inequalities, one of which is inside a nested composite filter.
610+
NSString *reason = @"Invalid Query. All where filters with an inequality (notEqual, lessThan, "
611+
"lessThanOrEqual, greaterThan, or greaterThanOrEqual) must be on the same "
612+
"field. But you have inequality filters on 'c' and 'r'";
613+
614+
NSArray<FIRFilter *> *array1 = @[
615+
[FIRFilter andFilterWithFilters:@[
616+
[FIRFilter filterWhereField:@"a" isEqualTo:@"b"], [FIRFilter filterWhereField:@"c"
617+
isGreaterThan:@"d"]
618+
]],
619+
[FIRFilter andFilterWithFilters:@[
620+
[FIRFilter filterWhereField:@"e" isEqualTo:@"f"], [FIRFilter filterWhereField:@"g"
621+
isEqualTo:@"h"]
622+
]]
623+
];
624+
625+
FSTAssertThrows(
626+
[[collection queryWhereFilter:[FIRFilter orFilterWithFilters:array1]] queryWhereField:@"r"
627+
isGreaterThan:@"s"],
628+
reason);
629+
630+
// OrderBy and inequality on different fields. Inequality inside a nested composite filter.
631+
reason = @"Invalid query. You have a where filter with an inequality (notEqual, lessThan, "
632+
"lessThanOrEqual, greaterThan, or greaterThanOrEqual) on field 'c' and so you must "
633+
"also use 'c' as your first queryOrderedBy field, but your first queryOrderedBy is "
634+
"currently on field 'r' instead.";
635+
636+
FSTAssertThrows([[collection queryWhereFilter:[FIRFilter orFilterWithFilters:array1]]
637+
queryOrderedByField:@"r"],
638+
reason);
639+
640+
// Conflicting operations within a composite filter.
641+
reason = @"Invalid Query. You cannot use 'notIn' filters with 'in' filters.";
642+
643+
NSArray<FIRFilter *> *array2 = @[
644+
[FIRFilter andFilterWithFilters:@[
645+
[FIRFilter filterWhereField:@"a" isEqualTo:@"b"], [FIRFilter filterWhereField:@"c"
646+
in:@[ @"d", @"e" ]]
647+
]],
648+
[FIRFilter andFilterWithFilters:@[
649+
[FIRFilter filterWhereField:@"e" isEqualTo:@"f"], [FIRFilter filterWhereField:@"c"
650+
notIn:@[ @"f", @"g" ]]
651+
]]
652+
];
653+
654+
FSTAssertThrows([collection queryWhereFilter:[FIRFilter orFilterWithFilters:array2]], reason);
655+
656+
// Conflicting operations between a field filter and a composite filter.
657+
NSArray<FIRFilter *> *array3 = @[
658+
[FIRFilter andFilterWithFilters:@[
659+
[FIRFilter filterWhereField:@"a" isEqualTo:@"b"], [FIRFilter filterWhereField:@"c"
660+
in:@[ @"d", @"e" ]]
661+
]],
662+
[FIRFilter andFilterWithFilters:@[
663+
[FIRFilter filterWhereField:@"e" isEqualTo:@"f"], [FIRFilter filterWhereField:@"g"
664+
isEqualTo:@"h"]
665+
]]
666+
];
667+
668+
NSArray<NSString *> *array4 = @[ @"j", @"k" ];
669+
670+
FSTAssertThrows(
671+
[[collection queryWhereFilter:[FIRFilter orFilterWithFilters:array3]] queryWhereField:@"i"
672+
notIn:array4],
673+
reason);
674+
675+
// Conflicting operations between two composite filters.
676+
NSArray<FIRFilter *> *array5 = @[
677+
[FIRFilter andFilterWithFilters:@[
678+
[FIRFilter filterWhereField:@"i" isEqualTo:@"j"], [FIRFilter filterWhereField:@"l"
679+
notIn:@[ @"m", @"n" ]]
680+
]],
681+
[FIRFilter andFilterWithFilters:@[
682+
[FIRFilter filterWhereField:@"o" isEqualTo:@"p"], [FIRFilter filterWhereField:@"q"
683+
isEqualTo:@"r"]
684+
]]
685+
];
686+
687+
FSTAssertThrows([[collection queryWhereFilter:[FIRFilter orFilterWithFilters:array3]]
688+
queryWhereFilter:[FIRFilter orFilterWithFilters:array5]],
689+
reason);
690+
}
691+
605692
- (void)testQueryInequalityFieldMustMatchFirstOrderByField {
606693
FIRCollectionReference *coll = [self.db collectionWithPath:@"collection"];
607694
FIRQuery *base = [coll queryWhereField:@"x" isGreaterThanOrEqualTo:@32];

Firestore/Source/API/FIRFilter+Internal.h

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
*/
1616

1717
#import <Foundation/Foundation.h>
18-
#include "Firestore/core/src/core/field_filter.h"
18+
#include "Firestore/Protos/nanopb/google/firestore/v1/query.nanopb.h"
19+
#import "Firestore/core/src/core/composite_filter.h"
20+
#import "Firestore/core/src/core/field_filter.h"
1921

2022
// TODO(orquery): This class will become public API. Change visibility and add documentation.
2123

@@ -93,15 +95,26 @@ NS_SWIFT_NAME(Filter)
9395
notIn:(nonnull NSArray<id> *)values
9496
NS_SWIFT_NAME(whereField(_:notIn:));
9597

98+
+ (FIRFilter *)orFilterWithFilters:(NSArray<FIRFilter *> *)filters NS_SWIFT_NAME(orFilter(_:));
99+
100+
+ (FIRFilter *)andFilterWithFilters:(NSArray<FIRFilter *> *)filters NS_SWIFT_NAME(andFilter(_:));
101+
96102
@end
97103

98-
/** Internal FIRFilter properties we don't want exposed in our public header files. */
99-
@interface FIRFilter (Internal)
104+
/** Exposed internally */
105+
@interface FSTUnaryFilter : FIRFilter
100106

101107
@property(nonatomic, strong, readonly) FIRFieldPath *fieldPath;
102-
@property(nonatomic, readonly) firebase::firestore::core::FieldFilter::Operator op;
108+
@property(nonatomic, readonly) firebase::firestore::core::FieldFilter::Operator unaryOp;
103109
@property(nonatomic, strong, readonly) id value;
104110

105111
@end
106112

113+
@interface FSTCompositeFilter : FIRFilter
114+
115+
@property(nonatomic, strong, readonly) NSArray<FIRFilter *> *filters;
116+
@property(nonatomic, readonly) firebase::firestore::core::CompositeFilter::Operator compOp;
117+
118+
@end
119+
107120
NS_ASSUME_NONNULL_END

Firestore/Source/API/FIRFilter.mm

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using firebase::firestore::model::FieldPath;
2121
using firebase::firestore::util::MakeString;
2222
using firebase::firestore::core::FieldFilter;
23+
using firebase::firestore::core::CompositeFilter;
2324

2425
NS_ASSUME_NONNULL_BEGIN
2526

@@ -31,55 +32,81 @@
3132

3233
} // namespace
3334

34-
@interface FIRFilter ()
35+
@interface FSTUnaryFilter ()
3536

3637
@property(nonatomic, strong, readwrite) FIRFieldPath *fieldPath;
37-
@property(nonatomic, readwrite) FieldFilter::Operator op;
38+
@property(nonatomic, readwrite) FieldFilter::Operator unaryOp;
3839
@property(nonatomic, strong, readwrite) id value;
3940

4041
@end
4142

42-
@implementation FIRFilter
43-
44-
#pragma mark - Constructor Methods
43+
@implementation FSTUnaryFilter
4544

4645
- (instancetype)initWithFIRFieldPath:(nonnull FIRFieldPath *)fieldPath
4746
op:(FieldFilter::Operator)op
4847
value:(nonnull id)value {
4948
if (self = [super init]) {
5049
self.fieldPath = fieldPath;
51-
self.op = op;
50+
self.unaryOp = op;
5251
self.value = value;
5352
}
5453
return self;
5554
}
5655

56+
@end
57+
58+
@interface FSTCompositeFilter ()
59+
60+
@property(nonatomic, strong, readwrite) NSArray<FIRFilter *> *filters;
61+
@property(nonatomic, readwrite) CompositeFilter::Operator compOp;
62+
63+
@end
64+
65+
@implementation FSTCompositeFilter
66+
67+
- (instancetype)initWithFilters:(nonnull NSArray<FIRFilter *> *)filters
68+
op:(CompositeFilter::Operator)op {
69+
if (self = [super init]) {
70+
self.filters = filters;
71+
self.compOp = op;
72+
}
73+
return self;
74+
}
75+
76+
@end
77+
78+
@implementation FIRFilter
79+
80+
#pragma mark - Constructor Methods
81+
5782
+ (FIRFilter *)filterWhereField:(nonnull NSString *)field isEqualTo:(nonnull id)value {
5883
return [self filterWhereFieldPath:MakeFIRFieldPath(field) isEqualTo:value];
5984
}
6085

6186
+ (FIRFilter *)filterWhereFieldPath:(nonnull FIRFieldPath *)field isEqualTo:(nonnull id)value {
62-
return [[FIRFilter alloc] initWithFIRFieldPath:field op:FieldFilter::Operator::Equal value:value];
87+
return [[FSTUnaryFilter alloc] initWithFIRFieldPath:field
88+
op:FieldFilter::Operator::Equal
89+
value:value];
6390
}
6491

6592
+ (FIRFilter *)filterWhereField:(nonnull NSString *)field isNotEqualTo:(nonnull id)value {
6693
return [self filterWhereFieldPath:MakeFIRFieldPath(field) isNotEqualTo:value];
6794
}
6895

6996
+ (FIRFilter *)filterWhereFieldPath:(nonnull FIRFieldPath *)field isNotEqualTo:(nonnull id)value {
70-
return [[FIRFilter alloc] initWithFIRFieldPath:field
71-
op:FieldFilter::Operator::NotEqual
72-
value:value];
97+
return [[FSTUnaryFilter alloc] initWithFIRFieldPath:field
98+
op:FieldFilter::Operator::NotEqual
99+
value:value];
73100
}
74101

75102
+ (FIRFilter *)filterWhereField:(nonnull NSString *)field isGreaterThan:(nonnull id)value {
76103
return [self filterWhereFieldPath:MakeFIRFieldPath(field) isGreaterThan:value];
77104
}
78105

79106
+ (FIRFilter *)filterWhereFieldPath:(nonnull FIRFieldPath *)field isGreaterThan:(nonnull id)value {
80-
return [[FIRFilter alloc] initWithFIRFieldPath:field
81-
op:FieldFilter::Operator::GreaterThan
82-
value:value];
107+
return [[FSTUnaryFilter alloc] initWithFIRFieldPath:field
108+
op:FieldFilter::Operator::GreaterThan
109+
value:value];
83110
}
84111

85112
+ (FIRFilter *)filterWhereField:(nonnull NSString *)field isGreaterThanOrEqualTo:(nonnull id)value {
@@ -88,19 +115,19 @@ + (FIRFilter *)filterWhereField:(nonnull NSString *)field isGreaterThanOrEqualTo
88115

89116
+ (FIRFilter *)filterWhereFieldPath:(nonnull FIRFieldPath *)field
90117
isGreaterThanOrEqualTo:(nonnull id)value {
91-
return [[FIRFilter alloc] initWithFIRFieldPath:field
92-
op:FieldFilter::Operator::GreaterThanOrEqual
93-
value:value];
118+
return [[FSTUnaryFilter alloc] initWithFIRFieldPath:field
119+
op:FieldFilter::Operator::GreaterThanOrEqual
120+
value:value];
94121
}
95122

96123
+ (FIRFilter *)filterWhereField:(nonnull NSString *)field isLessThan:(nonnull id)value {
97124
return [self filterWhereFieldPath:MakeFIRFieldPath(field) isLessThan:value];
98125
}
99126

100127
+ (FIRFilter *)filterWhereFieldPath:(nonnull FIRFieldPath *)field isLessThan:(nonnull id)value {
101-
return [[FIRFilter alloc] initWithFIRFieldPath:field
102-
op:FieldFilter::Operator::LessThan
103-
value:value];
128+
return [[FSTUnaryFilter alloc] initWithFIRFieldPath:field
129+
op:FieldFilter::Operator::LessThan
130+
value:value];
104131
}
105132

106133
+ (FIRFilter *)filterWhereField:(nonnull NSString *)field isLessThanOrEqualTo:(nonnull id)value {
@@ -109,19 +136,19 @@ + (FIRFilter *)filterWhereField:(nonnull NSString *)field isLessThanOrEqualTo:(n
109136

110137
+ (FIRFilter *)filterWhereFieldPath:(nonnull FIRFieldPath *)field
111138
isLessThanOrEqualTo:(nonnull id)value {
112-
return [[FIRFilter alloc] initWithFIRFieldPath:field
113-
op:FieldFilter::Operator::LessThanOrEqual
114-
value:value];
139+
return [[FSTUnaryFilter alloc] initWithFIRFieldPath:field
140+
op:FieldFilter::Operator::LessThanOrEqual
141+
value:value];
115142
}
116143

117144
+ (FIRFilter *)filterWhereField:(nonnull NSString *)field arrayContains:(nonnull id)value {
118145
return [self filterWhereFieldPath:MakeFIRFieldPath(field) arrayContains:value];
119146
}
120147

121148
+ (FIRFilter *)filterWhereFieldPath:(nonnull FIRFieldPath *)field arrayContains:(nonnull id)value {
122-
return [[FIRFilter alloc] initWithFIRFieldPath:field
123-
op:FieldFilter::Operator::ArrayContains
124-
value:value];
149+
return [[FSTUnaryFilter alloc] initWithFIRFieldPath:field
150+
op:FieldFilter::Operator::ArrayContains
151+
value:value];
125152
}
126153

127154
+ (FIRFilter *)filterWhereField:(nonnull NSString *)field
@@ -131,17 +158,19 @@ + (FIRFilter *)filterWhereField:(nonnull NSString *)field
131158

132159
+ (FIRFilter *)filterWhereFieldPath:(nonnull FIRFieldPath *)field
133160
arrayContainsAny:(nonnull NSArray<id> *)values {
134-
return [[FIRFilter alloc] initWithFIRFieldPath:field
135-
op:FieldFilter::Operator::ArrayContainsAny
136-
value:values];
161+
return [[FSTUnaryFilter alloc] initWithFIRFieldPath:field
162+
op:FieldFilter::Operator::ArrayContainsAny
163+
value:values];
137164
}
138165

139166
+ (FIRFilter *)filterWhereField:(nonnull NSString *)field in:(nonnull NSArray<id> *)values {
140167
return [self filterWhereFieldPath:MakeFIRFieldPath(field) in:values];
141168
}
142169

143170
+ (FIRFilter *)filterWhereFieldPath:(nonnull FIRFieldPath *)field in:(nonnull NSArray<id> *)values {
144-
return [[FIRFilter alloc] initWithFIRFieldPath:field op:FieldFilter::Operator::In value:values];
171+
return [[FSTUnaryFilter alloc] initWithFIRFieldPath:field
172+
op:FieldFilter::Operator::In
173+
value:values];
145174
}
146175

147176
+ (FIRFilter *)filterWhereField:(nonnull NSString *)field notIn:(nonnull NSArray<id> *)values {
@@ -150,11 +179,18 @@ + (FIRFilter *)filterWhereField:(nonnull NSString *)field notIn:(nonnull NSArray
150179

151180
+ (FIRFilter *)filterWhereFieldPath:(nonnull FIRFieldPath *)field
152181
notIn:(nonnull NSArray<id> *)values {
153-
return [[FIRFilter alloc] initWithFIRFieldPath:field
154-
op:FieldFilter::Operator::NotIn
155-
value:values];
182+
return [[FSTUnaryFilter alloc] initWithFIRFieldPath:field
183+
op:FieldFilter::Operator::NotIn
184+
value:values];
156185
}
157186

187+
+ (FIRFilter *)orFilterWithFilters:(NSArray<FIRFilter *> *)filters {
188+
return [[FSTCompositeFilter alloc] initWithFilters:filters op:CompositeFilter::Operator::Or];
189+
}
190+
191+
+ (FIRFilter *)andFilterWithFilters:(NSArray<FIRFilter *> *)filters {
192+
return [[FSTCompositeFilter alloc] initWithFilters:filters op:CompositeFilter::Operator::And];
193+
}
158194
@end
159195

160196
NS_ASSUME_NONNULL_END

Firestore/Source/API/FIRQuery+Internal.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include "Firestore/core/src/api/api_fwd.h"
2222
#include "Firestore/core/src/core/core_fwd.h"
2323

24+
@class FIRFilter;
25+
2426
namespace api = firebase::firestore::api;
2527
namespace core = firebase::firestore::core;
2628

@@ -42,6 +44,9 @@ NS_ASSUME_NONNULL_BEGIN
4244

4345
- (const api::Query &)apiQuery;
4446

47+
// TODO(orquery): This method will become public API. Change visibility and add documentation.
48+
- (FIRQuery *)queryWhereFilter:(FIRFilter *)filter;
49+
4550
@end
4651

4752
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)