Skip to content

Commit f3c7db7

Browse files
authored
Multiple inequality support (#11626)
1 parent 0c9fe27 commit f3c7db7

File tree

17 files changed

+802
-250
lines changed

17 files changed

+802
-250
lines changed

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

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

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

Lines changed: 28 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -626,111 +626,72 @@ - (void)testQueriesUsingInAndDocumentIdMustHaveProperDocumentReferencesInArray {
626626

627627
- (void)testInvalidQueryFilters {
628628
FIRCollectionReference *collection = [self collectionRef];
629-
630-
// Multiple inequalities, one of which is inside a nested composite filter.
631-
NSString *reason = @"Invalid Query. All where filters with an inequality (notEqual, lessThan, "
632-
"lessThanOrEqual, greaterThan, or greaterThanOrEqual) must be on the same "
633-
"field. But you have inequality filters on 'c' and 'r'";
634-
629+
NSString *reason = @"Invalid Query. You cannot use 'notIn' filters with 'in' filters.";
635630
NSArray<FIRFilter *> *array1 = @[
636631
[FIRFilter andFilterWithFilters:@[
637632
[FIRFilter filterWhereField:@"a" isEqualTo:@"b"], [FIRFilter filterWhereField:@"c"
638-
isGreaterThan:@"d"]
633+
in:@[ @"d", @"e" ]]
639634
]],
640635
[FIRFilter andFilterWithFilters:@[
641636
[FIRFilter filterWhereField:@"e" isEqualTo:@"f"], [FIRFilter filterWhereField:@"g"
642-
isEqualTo:@"h"]
637+
notIn:@[ @"h", @"i" ]]
643638
]]
644639
];
640+
FSTAssertThrows([collection queryWhereFilter:[FIRFilter orFilterWithFilters:array1]], reason);
645641

646-
FSTAssertThrows(
647-
[[collection queryWhereFilter:[FIRFilter orFilterWithFilters:array1]] queryWhereField:@"r"
648-
isGreaterThan:@"s"],
649-
reason);
650-
651-
// OrderBy and inequality on different fields. Inequality inside a nested composite filter.
652-
reason = @"Invalid query. You have a where filter with an inequality (notEqual, lessThan, "
653-
"lessThanOrEqual, greaterThan, or greaterThanOrEqual) on field 'c' and so you must "
654-
"also use 'c' as your first queryOrderedBy field, but your first queryOrderedBy is "
655-
"currently on field 'r' instead.";
656-
657-
FSTAssertThrows([[collection queryWhereFilter:[FIRFilter orFilterWithFilters:array1]]
658-
queryOrderedByField:@"r"],
659-
reason);
660-
661-
// Conflicting operations within a composite filter.
662-
reason = @"Invalid Query. You cannot use 'notIn' filters with 'in' filters.";
663-
642+
reason = @"Invalid Query. You cannot use 'notIn' filters with 'notEqual' filters.";
664643
NSArray<FIRFilter *> *array2 = @[
665644
[FIRFilter andFilterWithFilters:@[
666645
[FIRFilter filterWhereField:@"a" isEqualTo:@"b"], [FIRFilter filterWhereField:@"c"
667-
in:@[ @"d", @"e" ]]
646+
isNotEqualTo:@"d"]
668647
]],
669648
[FIRFilter andFilterWithFilters:@[
670-
[FIRFilter filterWhereField:@"e" isEqualTo:@"f"], [FIRFilter filterWhereField:@"c"
671-
notIn:@[ @"f", @"g" ]]
649+
[FIRFilter filterWhereField:@"e" isEqualTo:@"f"], [FIRFilter filterWhereField:@"g"
650+
notIn:@[ @"h", @"i" ]]
672651
]]
673652
];
674-
675653
FSTAssertThrows([collection queryWhereFilter:[FIRFilter orFilterWithFilters:array2]], reason);
676654

677-
// Conflicting operations between a field filter and a composite filter.
655+
reason = @"Invalid Query. You cannot use more than one 'notIn' filter.";
678656
NSArray<FIRFilter *> *array3 = @[
679657
[FIRFilter andFilterWithFilters:@[
680658
[FIRFilter filterWhereField:@"a" isEqualTo:@"b"], [FIRFilter filterWhereField:@"c"
681-
in:@[ @"d", @"e" ]]
659+
notIn:@[ @"d", @"e" ]]
682660
]],
683661
[FIRFilter andFilterWithFilters:@[
684662
[FIRFilter filterWhereField:@"e" isEqualTo:@"f"], [FIRFilter filterWhereField:@"g"
685-
isEqualTo:@"h"]
663+
notIn:@[ @"h", @"i" ]]
686664
]]
687665
];
666+
FSTAssertThrows([collection queryWhereFilter:[FIRFilter orFilterWithFilters:array3]], reason);
688667

689-
NSArray<NSString *> *array4 = @[ @"j", @"k" ];
690-
691-
FSTAssertThrows(
692-
[[collection queryWhereFilter:[FIRFilter orFilterWithFilters:array3]] queryWhereField:@"i"
693-
notIn:array4],
694-
reason);
695-
696-
// Conflicting operations between two composite filters.
697-
NSArray<FIRFilter *> *array5 = @[
668+
reason = @"Invalid Query. You cannot use more than one 'notEqual' filter.";
669+
NSArray<FIRFilter *> *array4 = @[
698670
[FIRFilter andFilterWithFilters:@[
699-
[FIRFilter filterWhereField:@"i" isEqualTo:@"j"], [FIRFilter filterWhereField:@"l"
700-
notIn:@[ @"m", @"n" ]]
671+
[FIRFilter filterWhereField:@"a" isEqualTo:@"b"], [FIRFilter filterWhereField:@"c"
672+
isNotEqualTo:@"d"]
701673
]],
702674
[FIRFilter andFilterWithFilters:@[
703-
[FIRFilter filterWhereField:@"o" isEqualTo:@"p"], [FIRFilter filterWhereField:@"q"
704-
isEqualTo:@"r"]
675+
[FIRFilter filterWhereField:@"e" isEqualTo:@"f"], [FIRFilter filterWhereField:@"g"
676+
isNotEqualTo:@"h"]
705677
]]
706678
];
707-
708-
FSTAssertThrows([[collection queryWhereFilter:[FIRFilter orFilterWithFilters:array3]]
709-
queryWhereFilter:[FIRFilter orFilterWithFilters:array5]],
710-
reason);
679+
FSTAssertThrows([collection queryWhereFilter:[FIRFilter orFilterWithFilters:array4]], reason);
711680
}
712681

713-
- (void)testQueryInequalityFieldMustMatchFirstOrderByField {
682+
- (void)testQueryInequalityFieldWithMultipleOrderByFields {
714683
FIRCollectionReference *coll = [self.db collectionWithPath:@"collection"];
715684
FIRQuery *base = [coll queryWhereField:@"x" isGreaterThanOrEqualTo:@32];
716685

717-
FSTAssertThrows([base queryWhereField:@"y" isLessThan:@"cat"],
718-
@"Invalid Query. All where filters with an inequality (notEqual, lessThan, "
719-
"lessThanOrEqual, greaterThan, or greaterThanOrEqual) must be on the same "
720-
"field. But you have inequality filters on 'x' and 'y'");
686+
XCTAssertNoThrow([base queryWhereField:@"y" isLessThan:@"cat"]);
721687

722-
NSString *reason =
723-
@"Invalid query. You have a where filter with "
724-
"an inequality (notEqual, lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) "
725-
"on field 'x' and so you must also use 'x' as your first queryOrderedBy field, "
726-
"but your first queryOrderedBy is currently on field 'y' instead.";
727-
FSTAssertThrows([base queryOrderedByField:@"y"], reason);
728-
FSTAssertThrows([[coll queryOrderedByField:@"y"] queryWhereField:@"x" isGreaterThan:@32], reason);
729-
FSTAssertThrows([[base queryOrderedByField:@"y"] queryOrderedByField:@"x"], reason);
730-
FSTAssertThrows([[[coll queryOrderedByField:@"y"] queryOrderedByField:@"x"] queryWhereField:@"x"
731-
isGreaterThan:@32],
732-
reason);
733-
FSTAssertThrows([[coll queryOrderedByField:@"y"] queryWhereField:@"x" isNotEqualTo:@32], reason);
688+
XCTAssertNoThrow([base queryOrderedByField:@"y"]);
689+
XCTAssertNoThrow([[coll queryOrderedByField:@"y"] queryWhereField:@"x" isGreaterThan:@32]);
690+
XCTAssertNoThrow([[base queryOrderedByField:@"y"] queryOrderedByField:@"x"]);
691+
XCTAssertNoThrow([[[coll queryOrderedByField:@"y"] queryOrderedByField:@"x"]
692+
queryWhereField:@"x"
693+
isGreaterThan:@32]);
694+
XCTAssertNoThrow([[coll queryOrderedByField:@"y"] queryWhereField:@"x" isNotEqualTo:@32]);
734695

735696
XCTAssertNoThrow([base queryWhereField:@"x" isLessThanOrEqualTo:@"cat"],
736697
@"Same inequality fields work");
@@ -758,20 +719,6 @@ - (void)testQueryInequalityFieldMustMatchFirstOrderByField {
758719
@"array_contains different than orderBy works.");
759720
}
760721

761-
- (void)testQueriesWithMultipleNotEqualAndInequalitiesFail {
762-
FIRCollectionReference *coll = [self.db collectionWithPath:@"collection"];
763-
764-
FSTAssertThrows([[coll queryWhereField:@"x" isNotEqualTo:@1] queryWhereField:@"x"
765-
isNotEqualTo:@2],
766-
@"Invalid Query. You cannot use more than one 'notEqual' filter.");
767-
768-
FSTAssertThrows([[coll queryWhereField:@"x" isNotEqualTo:@1] queryWhereField:@"y"
769-
isNotEqualTo:@2],
770-
@"Invalid Query. All where filters with an inequality (notEqual, lessThan, "
771-
"lessThanOrEqual, greaterThan, or greaterThanOrEqual) must be on "
772-
"the same field. But you have inequality filters on 'x' and 'y'");
773-
}
774-
775722
- (void)testQueriesWithNotEqualAndNotInFiltersFail {
776723
FIRCollectionReference *coll = [self.db collectionWithPath:@"collection"];
777724

Firestore/core/src/api/query_core.cc

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,6 @@ Query Query::OrderBy(FieldPath field_path, bool descending) const {
277277
}
278278

279279
Query Query::OrderBy(FieldPath field_path, Direction direction) const {
280-
ValidateNewOrderByPath(field_path);
281280
if (query_.start_at()) {
282281
ThrowInvalidArgument(
283282
"Invalid query. You must not specify a starting point "
@@ -320,26 +319,6 @@ Query Query::EndAt(Bound bound) const {
320319

321320
void Query::ValidateNewFieldFilter(const core::Query& query,
322321
const FieldFilter& field_filter) const {
323-
if (field_filter.IsInequality()) {
324-
const FieldPath* existing_inequality = query.InequalityFilterField();
325-
const FieldPath& new_inequality = field_filter.field();
326-
327-
if (existing_inequality && *existing_inequality != new_inequality) {
328-
ThrowInvalidArgument(
329-
"Invalid Query. All where filters with an inequality (notEqual, "
330-
"lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) "
331-
"must be on the same field. But you have inequality filters on "
332-
"'%s' and '%s'",
333-
existing_inequality->CanonicalString(),
334-
new_inequality.CanonicalString());
335-
}
336-
337-
const FieldPath* first_order_by_field = query.FirstOrderByField();
338-
if (first_order_by_field) {
339-
ValidateOrderByField(*first_order_by_field, field_filter.field());
340-
}
341-
}
342-
343322
Operator filter_op = field_filter.op();
344323
absl::optional<Operator> conflicting_op =
345324
query.FindOpInsideFilters(ConflictingOps(filter_op));
@@ -367,30 +346,6 @@ void Query::ValidateNewFilter(const Filter& filter) const {
367346
}
368347
}
369348

370-
void Query::ValidateNewOrderByPath(const FieldPath& field_path) const {
371-
if (!query_.FirstOrderByField()) {
372-
// This is the first order by. It must match any inequality.
373-
const FieldPath* inequality_field = query_.InequalityFilterField();
374-
if (inequality_field) {
375-
ValidateOrderByField(field_path, *inequality_field);
376-
}
377-
}
378-
}
379-
380-
void Query::ValidateOrderByField(const FieldPath& order_by_field,
381-
const FieldPath& inequality_field) const {
382-
if (order_by_field != inequality_field) {
383-
ThrowInvalidArgument(
384-
"Invalid query. You have a where filter with an inequality "
385-
"(notEqual, lessThan, lessThanOrEqual, greaterThan, or "
386-
"greaterThanOrEqual) on field '%s' and so you must also use '%s' as "
387-
"your first queryOrderedBy field, but your first queryOrderedBy is "
388-
"currently on field '%s' instead.",
389-
inequality_field.CanonicalString(), inequality_field.CanonicalString(),
390-
order_by_field.CanonicalString());
391-
}
392-
}
393-
394349
void Query::ValidateHasExplicitOrderByForLimitToLast() const {
395350
if (query_.has_limit_to_last() && query_.explicit_order_bys().empty()) {
396351
ThrowInvalidArgument(

Firestore/core/src/api/query_core.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,6 @@ class Query {
215215
void ValidateNewFilter(const core::Filter& filter) const;
216216
void ValidateNewFieldFilter(const core::Query& query,
217217
const core::FieldFilter& filter) const;
218-
void ValidateNewOrderByPath(const model::FieldPath& field_path) const;
219-
void ValidateOrderByField(const model::FieldPath& order_by_field,
220-
const model::FieldPath& inequality_field) const;
221218
void ValidateHasExplicitOrderByForLimitToLast() const;
222219
/**
223220
* Validates that the value passed into a disjunctive filter satisfies all

Firestore/core/src/core/composite_filter.cc

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -141,17 +141,6 @@ const FieldFilter* CompositeFilter::Rep::FindFirstMatchingFilter(
141141
return nullptr;
142142
}
143143

144-
const model::FieldPath* CompositeFilter::Rep::GetFirstInequalityField() const {
145-
CheckFunction condition = [](const FieldFilter& field_filter) {
146-
return field_filter.IsInequality();
147-
};
148-
const FieldFilter* found = FindFirstMatchingFilter(condition);
149-
if (found) {
150-
return &(found->field());
151-
}
152-
return nullptr;
153-
}
154-
155144
const std::vector<FieldFilter>& CompositeFilter::Rep::GetFlattenedFilters()
156145
const {
157146
return memoized_flattened_filters_->memoize([&]() {

Firestore/core/src/core/composite_filter.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,6 @@ class CompositeFilter : public Filter {
140140

141141
const std::vector<FieldFilter>& GetFlattenedFilters() const override;
142142

143-
const model::FieldPath* GetFirstInequalityField() const override;
144-
145143
std::vector<Filter> GetFilters() const override {
146144
return filters();
147145
}

Firestore/core/src/core/field_filter.cc

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,6 @@ bool FieldFilter::Rep::Equals(const Filter::Rep& other) const {
202202
*value_rhs_ == *other_rep.value_rhs_;
203203
}
204204

205-
const model::FieldPath* FieldFilter::Rep::GetFirstInequalityField() const {
206-
if (IsInequality()) {
207-
return &field();
208-
}
209-
return nullptr;
210-
}
211-
212205
} // namespace core
213206
} // namespace firestore
214207
} // namespace firebase

Firestore/core/src/core/field_filter.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,6 @@ class FieldFilter : public Filter {
117117
return false;
118118
}
119119

120-
const model::FieldPath* GetFirstInequalityField() const override;
121-
122120
const std::vector<FieldFilter>& GetFlattenedFilters() const override;
123121

124122
std::vector<Filter> GetFilters() const override;

Firestore/core/src/core/filter.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,6 @@ class Filter {
9595
return rep_->IsEmpty();
9696
}
9797

98-
/**
99-
* Returns the first inequality filter contained within this filter.
100-
* Returns nullptr if it does not contain any inequalities.
101-
*/
102-
const model::FieldPath* GetFirstInequalityField() const {
103-
return rep_->GetFirstInequalityField();
104-
}
105-
10698
/**
10799
* Returns a list of all field filters that are contained within this filter.
108100
*/
@@ -155,8 +147,6 @@ class Filter {
155147

156148
virtual bool IsEmpty() const = 0;
157149

158-
virtual const model::FieldPath* GetFirstInequalityField() const = 0;
159-
160150
virtual const std::vector<FieldFilter>& GetFlattenedFilters() const = 0;
161151

162152
virtual std::vector<Filter> GetFilters() const = 0;

0 commit comments

Comments
 (0)