diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 9c2b16d7b47..c72eb03528d 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [feature] Enable queries with range & inequality filters on multiple fields. (#12416) + # 10.23.0 - [feature] Enable snapshot listener option to retrieve data from local cache only. (#12370) - [fixed] Update gRPC dependency to 1.62.* (#12098, #12021) diff --git a/Firestore/Example/Tests/Integration/API/FIRCompositeIndexQueryTests.mm b/Firestore/Example/Tests/Integration/API/FIRCompositeIndexQueryTests.mm index ece28eb23bf..68766f66dbe 100644 --- a/Firestore/Example/Tests/Integration/API/FIRCompositeIndexQueryTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRCompositeIndexQueryTests.mm @@ -60,7 +60,7 @@ - (FIRCollectionReference *)testCollectionRef { } // Runs a test with specified documents in the COMPOSITE_INDEX_TEST_COLLECTION. -- (FIRCollectionReference *)withTestDocs: +- (FIRCollectionReference *)collectionRefwithTestDocs: (NSDictionary *> *)docs { FIRCollectionReference *writer = [self testCollectionRef]; // Use a different instance to write the documents @@ -124,6 +124,13 @@ - (void)assertOnlineAndOfflineResultsMatch:(FIRQuery *)query [self checkOnlineAndOfflineQuery:query matchesResult:[self toHashedIds:expectedDocs]]; } +// Asserts that the IDs in the query snapshot matches the expected Ids. The expected document +// IDs are hashed to match the actual document IDs created by the test helper. +- (void)assertSnapshotResultIdsMatch:(FIRQuerySnapshot *)snapshot + expectedIds:(NSArray *)expectedIds { + XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), [self toHashedIds:expectedIds]); +} + // Adds a filter on test id for a query. - (FIRQuery *)compositeIndexQuery:(FIRQuery *)query_ { return [query_ queryWhereField:TEST_ID_FIELD isEqualTo:self.testId]; @@ -196,7 +203,7 @@ - (void)deleteDoc:(FIRDocumentReference *)document { */ - (void)testOrQueriesWithCompositeIndexes { - FIRCollectionReference *collRef = [self withTestDocs:@{ + FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{ @"doc1" : @{@"a" : @1, @"b" : @0}, @"doc2" : @{@"a" : @2, @"b" : @1}, @"doc3" : @{@"a" : @3, @"b" : @2}, @@ -290,7 +297,7 @@ - (void)testCanRunAggregateCollectionGroupQuery { } - (void)testCanPerformMaxAggregations { - FIRCollectionReference *testCollection = [self withTestDocs:@{ + FIRCollectionReference *testCollection = [self collectionRefwithTestDocs:@{ @"a" : @{ @"author" : @"authorA", @"title" : @"titleA", @@ -336,7 +343,7 @@ - (void)testCanPerformMaxAggregations { } - (void)testPerformsAggregationsWhenNaNExistsForSomeFieldValues { - FIRCollectionReference *testCollection = [self withTestDocs:@{ + FIRCollectionReference *testCollection = [self collectionRefwithTestDocs:@{ @"a" : @{ @"author" : @"authorA", @"title" : @"titleA", @@ -395,7 +402,7 @@ - (void)testPerformsAggregationsWhenNaNExistsForSomeFieldValues { } - (void)testPerformsAggregationWhenUsingArrayContainsAnyOperator { - FIRCollectionReference *testCollection = [self withTestDocs:@{ + FIRCollectionReference *testCollection = [self collectionRefwithTestDocs:@{ @"a" : @{ @"author" : @"authorA", @"title" : @"titleA", @@ -463,6 +470,491 @@ - (void)testPerformsAggregationWhenUsingArrayContainsAnyOperator { 100.0); } +// Multiple Inequality +- (void)testMultipleInequalityOnDifferentFields { + FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{ + @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @0}, + @"doc2" : @{@"key" : @"b", @"sort" : @3, @"v" : @1}, + @"doc3" : @{@"key" : @"c", @"sort" : @1, @"v" : @3}, + @"doc4" : @{@"key" : @"d", @"sort" : @2, @"v" : @2} + }]; + + // Multiple inequality fields + FIRQuery *query = [[[collRef queryWhereField:@"key" + isNotEqualTo:@"a"] queryWhereField:@"sort" + isLessThanOrEqualTo:@2] queryWhereField:@"v" + isGreaterThan:@2]; + FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc3" ])]; + + // Duplicate inequality fields + query = [[[collRef queryWhereField:@"key" + isNotEqualTo:@"a"] queryWhereField:@"sort" + isLessThanOrEqualTo:@2] queryWhereField:@"sort" + isGreaterThan:@1]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc4" ])]; + + // With multiple IN + query = [[[[collRef queryWhereField:@"key" isGreaterThanOrEqualTo:@"a"] queryWhereField:@"sort" + isLessThanOrEqualTo:@2] + queryWhereField:@"v" + in:@[ @2, @3, @4 ]] queryWhereField:@"sort" in:@[ @2, @3 ]]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc4" ])]; + + // With NOT-IN + query = [[[collRef queryWhereField:@"key" + isGreaterThanOrEqualTo:@"a"] queryWhereField:@"sort" + isLessThanOrEqualTo:@2] queryWhereField:@"v" + notIn:@[ @2, @4, @5 ]]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc1", @"doc3" ])]; + + // With orderby + query = [[[collRef queryWhereField:@"key" + isGreaterThanOrEqualTo:@"a"] queryWhereField:@"sort" + isLessThanOrEqualTo:@2] queryOrderedByField:@"v" + descending:YES]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc3", @"doc4", @"doc1" ])]; + + // With limit + query = [[[[collRef queryWhereField:@"key" isGreaterThanOrEqualTo:@"a"] + queryWhereField:@"sort" + isLessThanOrEqualTo:@2] queryOrderedByField:@"v" descending:YES] queryLimitedTo:2]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc3", @"doc4" ])]; + + // With limitedToLast + query = [[[[collRef queryWhereField:@"key" isGreaterThanOrEqualTo:@"a"] + queryWhereField:@"sort" + isLessThanOrEqualTo:@2] queryOrderedByField:@"v" descending:YES] queryLimitedToLast:2]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc4", @"doc1" ])]; +} + +- (void)testMultipleInequalityOnSpecialValues { + FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{ + @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @0}, + @"doc2" : @{@"key" : @"b", @"sort" : @(NAN), @"v" : @1}, + @"doc3" : @{@"key" : @"c", @"sort" : [NSNull null], @"v" : @3}, + @"doc4" : @{@"key" : @"d", @"v" : @0}, + @"doc5" : @{@"key" : @"e", @"sort" : @1}, + @"doc6" : @{@"key" : @"f", @"sort" : @1, @"v" : @1} + }]; + + FIRQuery *query = [[collRef queryWhereField:@"key" isNotEqualTo:@"a"] queryWhereField:@"sort" + isLessThanOrEqualTo:@2]; + FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc5", @"doc6" ])]; + + query = [[[collRef queryWhereField:@"key" + isNotEqualTo:@"a"] queryWhereField:@"sort" + isLessThanOrEqualTo:@2] queryWhereField:@"v" + isLessThanOrEqualTo:@1]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc6" ])]; +} + +- (void)testMultipleInequalityWithArrayMembership { + FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{ + @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @[ @0 ]}, + @"doc2" : @{@"key" : @"b", @"sort" : @1, @"v" : @[ @0, @1, @3 ]}, + @"doc3" : @{@"key" : @"c", @"sort" : @1, @"v" : @[]}, + @"doc4" : @{@"key" : @"d", @"sort" : @2, @"v" : @[ @1 ]}, + @"doc5" : @{@"key" : @"e", @"sort" : @3, @"v" : @[ @2, @4 ]}, + @"doc6" : @{@"key" : @"f", @"sort" : @4, @"v" : @[ @(NAN) ]}, + @"doc7" : @{@"key" : @"g", @"sort" : @4, @"v" : @[ [NSNull null] ]} + + }]; + + FIRQuery *query = [[[collRef queryWhereField:@"key" + isNotEqualTo:@"a"] queryWhereField:@"sort" + isGreaterThanOrEqualTo:@1] queryWhereField:@"v" + arrayContains:@0]; + FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc2" ])]; + + query = [[[collRef queryWhereField:@"key" + isNotEqualTo:@"a"] queryWhereField:@"sort" + isGreaterThanOrEqualTo:@1] queryWhereField:@"v" + arrayContainsAny:@[ @0, @1 ]]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc2", @"doc4" ])]; +} + +- (NSDictionary *)nestedData:(int)number { + return @{ + @"name" : [NSString stringWithFormat:@"room %d", number], + @"metadata" : @{@"createdAt" : @(number)}, + @"field" : [NSString stringWithFormat:@"field %d", number], + @"field.dot" : @(number), + @"field\\slash" : @(number) + }; +} + +- (void)testMultipleInequalityWithNestedField { + FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{ + @"doc1" : [self nestedData:400], + @"doc2" : [self nestedData:200], + @"doc3" : [self nestedData:100], + @"doc4" : [self nestedData:300] + }]; + + FIRQuery *query = [[[[collRef queryWhereField:@"metadata.createdAt" isLessThanOrEqualTo:@500] + queryWhereField:@"metadata.createdAt" + isGreaterThan:@100] queryWhereField:@"name" + isNotEqualTo:@"room 200"] queryOrderedByField:@"name" descending:NO]; + FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc4", @"doc1" ])]; + + query = [[[[collRef queryWhereField:@"field" isGreaterThanOrEqualTo:@"field 100"] + queryWhereFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"field.dot" ]] + isNotEqualTo:@300] queryWhereField:@"field\\slash" + isLessThan:@400] queryOrderedByField:@"name" descending:YES]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc2", @"doc3" ])]; +} + +- (void)testMultipleInequalityWithCompositeFilters { + FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{ + @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @5}, + @"doc2" : @{@"key" : @"aa", @"sort" : @4, @"v" : @4}, + @"doc3" : @{@"key" : @"c", @"sort" : @3, @"v" : @3}, + @"doc4" : @{@"key" : @"b", @"sort" : @2, @"v" : @2}, + @"doc5" : @{@"key" : @"b", @"sort" : @2, @"v" : @1}, + @"doc6" : @{@"key" : @"b", @"sort" : @0, @"v" : @0} + }]; + + FIRQuery *query = [collRef + queryWhereFilter:[FIRFilter orFilterWithFilters:@[ + [FIRFilter andFilterWithFilters:@[ + [FIRFilter filterWhereField:@"key" isEqualTo:@"b"], [FIRFilter filterWhereField:@"sort" + isLessThanOrEqualTo:@2] + ]], + [FIRFilter andFilterWithFilters:@[ + [FIRFilter filterWhereField:@"key" isNotEqualTo:@"b"], [FIRFilter filterWhereField:@"v" + isGreaterThan:@4] + ]] + ]]]; + FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + // Implicitly ordered by: 'key' asc, 'sort' asc, 'v' asc, __name__ asc + [self assertSnapshotResultIdsMatch:snapshot + expectedIds:(@[ @"doc1", @"doc6", @"doc5", @"doc4" ])]; + + query = [[[collRef + queryWhereFilter:[FIRFilter orFilterWithFilters:@[ + [FIRFilter andFilterWithFilters:@[ + [FIRFilter filterWhereField:@"key" isEqualTo:@"b"], [FIRFilter filterWhereField:@"sort" + isLessThanOrEqualTo:@2] + ]], + [FIRFilter andFilterWithFilters:@[ + [FIRFilter filterWhereField:@"key" isNotEqualTo:@"b"], [FIRFilter filterWhereField:@"v" + isGreaterThan:@4] + ]] + ]]] queryOrderedByField:@"sort" descending:YES] queryOrderedByField:@"key"]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + // Ordered by: 'sort' desc, 'key' asc, 'v' asc, __name__ asc + [self assertSnapshotResultIdsMatch:snapshot + expectedIds:(@[ @"doc5", @"doc4", @"doc1", @"doc6" ])]; + + query = [collRef + queryWhereFilter:[FIRFilter andFilterWithFilters:@[ + + [FIRFilter orFilterWithFilters:@[ + [FIRFilter andFilterWithFilters:@[ + [FIRFilter filterWhereField:@"key" isEqualTo:@"b"], [FIRFilter filterWhereField:@"sort" + isLessThanOrEqualTo:@4] + ]], + [FIRFilter andFilterWithFilters:@[ + [FIRFilter filterWhereField:@"key" isNotEqualTo:@"b"], [FIRFilter filterWhereField:@"v" + isGreaterThanOrEqualTo:@4] + ]] + ]], + [FIRFilter orFilterWithFilters:@[ + [FIRFilter andFilterWithFilters:@[ + [FIRFilter filterWhereField:@"key" isGreaterThan:@"b"], + [FIRFilter filterWhereField:@"sort" isGreaterThanOrEqualTo:@1] + ]], + [FIRFilter andFilterWithFilters:@[ + [FIRFilter filterWhereField:@"key" isLessThan:@"b"], [FIRFilter filterWhereField:@"v" + isGreaterThan:@0] + ]] + ]] + + ]]]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + // Implicitly ordered by: 'key' asc, 'sort' asc, 'v' asc, __name__ asc + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc1", @"doc2" ])]; +} + +- (void)testMultipleInequalityFieldsWillBeImplicitlyOrderedLexicographically { + FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{ + @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @5}, + @"doc2" : @{@"key" : @"aa", @"sort" : @4, @"v" : @4}, + @"doc3" : @{@"key" : @"b", @"sort" : @3, @"v" : @3}, + @"doc4" : @{@"key" : @"b", @"sort" : @2, @"v" : @2}, + @"doc5" : @{@"key" : @"b", @"sort" : @2, @"v" : @1}, + @"doc6" : @{@"key" : @"b", @"sort" : @0, @"v" : @0} + }]; + + FIRQuery *query = [[[collRef queryWhereField:@"key" isNotEqualTo:@"a"] + queryWhereField:@"sort" + isGreaterThan:@1] queryWhereField:@"v" in:@[ @1, @2, @3, @4 ]]; + FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc + [self assertSnapshotResultIdsMatch:snapshot + expectedIds:(@[ @"doc2", @"doc4", @"doc5", @"doc3" ])]; + + query = [[[collRef queryWhereField:@"sort" + isGreaterThan:@1] queryWhereField:@"key" + isNotEqualTo:@"a"] queryWhereField:@"v" + in:@[ @1, @2, @3, @4 ]]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc + [self assertSnapshotResultIdsMatch:snapshot + expectedIds:(@[ @"doc2", @"doc4", @"doc5", @"doc3" ])]; +} + +- (void)testMultipleInequalityWithMultipleExplicitOrderBy { + FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{ + @"doc1" : @{@"key" : @"a", @"sort" : @5, @"v" : @0}, + @"doc2" : @{@"key" : @"aa", @"sort" : @4, @"v" : @0}, + @"doc3" : @{@"key" : @"b", @"sort" : @3, @"v" : @1}, + @"doc4" : @{@"key" : @"b", @"sort" : @2, @"v" : @1}, + @"doc5" : @{@"key" : @"bb", @"sort" : @1, @"v" : @1}, + @"doc6" : @{@"key" : @"c", @"sort" : @0, @"v" : @2} + }]; + + FIRQuery *query = [[[collRef queryWhereField:@"key" + isGreaterThan:@"a"] queryWhereField:@"sort" + isGreaterThanOrEqualTo:@1] queryOrderedByField:@"v" + descending:NO]; + FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + // Ordered by: 'v' asc, 'key' asc, 'sort' asc, __name__ asc + [self assertSnapshotResultIdsMatch:snapshot + expectedIds:(@[ @"doc2", @"doc4", @"doc3", @"doc5" ])]; + + query = [[[[collRef queryWhereField:@"key" isGreaterThan:@"a"] queryWhereField:@"sort" + isGreaterThanOrEqualTo:@1] + queryOrderedByField:@"v" + descending:NO] queryOrderedByField:@"sort" descending:NO]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + // Ordered by: 'v asc, 'sort' asc, 'key' asc, __name__ asc + [self assertSnapshotResultIdsMatch:snapshot + expectedIds:(@[ @"doc2", @"doc5", @"doc4", @"doc3" ])]; + + query = [[[collRef queryWhereField:@"key" + isGreaterThan:@"a"] queryWhereField:@"sort" + isGreaterThanOrEqualTo:@1] queryOrderedByField:@"v" + descending:YES]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + // Implicit order by matches the direction of last explicit order by. + // Ordered by: 'v' desc, 'key' desc, 'sort' desc, __name__ desc + [self assertSnapshotResultIdsMatch:snapshot + expectedIds:(@[ @"doc5", @"doc3", @"doc4", @"doc2" ])]; + + query = [[[[collRef queryWhereField:@"key" isGreaterThan:@"a"] queryWhereField:@"sort" + isGreaterThanOrEqualTo:@1] + queryOrderedByField:@"v" + descending:YES] queryOrderedByField:@"sort" descending:NO]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + // Ordered by: 'v desc, 'sort' asc, 'key' asc, __name__ asc + [self assertSnapshotResultIdsMatch:snapshot + expectedIds:(@[ @"doc5", @"doc4", @"doc3", @"doc2" ])]; +} + +- (void)testMultipleInequalityInAggregateQuery { + FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{ + @"doc1" : @{@"key" : @"a", @"sort" : @5, @"v" : @0}, + @"doc2" : @{@"key" : @"aa", @"sort" : @4, @"v" : @0}, + @"doc3" : @{@"key" : @"b", @"sort" : @3, @"v" : @1}, + @"doc4" : @{@"key" : @"b", @"sort" : @2, @"v" : @1}, + @"doc5" : @{@"key" : @"bb", @"sort" : @1, @"v" : @1}, + }]; + + FIRAggregateQuerySnapshot *snapshot = + [self readSnapshotForAggregate:[[self compositeIndexQuery:[[[collRef queryWhereField:@"key" + isGreaterThan:@"a"] + queryWhereField:@"sort" + isGreaterThanOrEqualTo:@1] + queryOrderedByField:@"v" + descending:NO]] + aggregate:@[ + [FIRAggregateField aggregateFieldForCount], + [FIRAggregateField aggregateFieldForSumOfField:@"sort"], + [FIRAggregateField aggregateFieldForAverageOfField:@"v"] + ]]]; + XCTAssertEqual([snapshot count], [NSNumber numberWithLong:4L]); + + snapshot = + [self readSnapshotForAggregate:[[self compositeIndexQuery:[[[collRef queryWhereField:@"key" + isGreaterThan:@"a"] + queryWhereField:@"sort" + isGreaterThanOrEqualTo:@1] + queryWhereField:@"v" + isNotEqualTo:@0]] + aggregate:@[ + [FIRAggregateField aggregateFieldForCount], + [FIRAggregateField aggregateFieldForSumOfField:@"sort"], + [FIRAggregateField aggregateFieldForAverageOfField:@"v"], + ]]]; + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]], + [NSNumber numberWithLong:3L]); + XCTAssertEqual( + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"sort"]] + longValue], + 6L); + XCTAssertEqual( + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"v"]], + [NSNumber numberWithDouble:1.0]); +} + +- (void)testMultipleInequalityFieldsWithDocumentKey { + FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{ + @"doc1" : @{@"key" : @"a", @"sort" : @5}, + @"doc2" : @{@"key" : @"aa", @"sort" : @4}, + @"doc3" : @{@"key" : @"b", @"sort" : @3}, + @"doc4" : @{@"key" : @"b", @"sort" : @2}, + @"doc5" : @{@"key" : @"bb", @"sort" : @1} + }]; + + FIRQuery *query = [[[collRef queryWhereField:@"sort" isGreaterThan:@1] + queryWhereField:@"key" + isNotEqualTo:@"a"] queryWhereFieldPath:[FIRFieldPath documentID] isLessThan:@"doc5"]; + FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + // Document Key in inequality field will implicitly ordered to the last. + // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc2", @"doc4", @"doc3" ])]; + + query = [[[collRef queryWhereFieldPath:[FIRFieldPath documentID] + isLessThan:@"doc5"] queryWhereField:@"sort" + isGreaterThan:@1] queryWhereField:@"key" + isNotEqualTo:@"a"]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + // Changing filters order will not effect implicit order. + // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc2", @"doc4", @"doc3" ])]; + + query = [[[[collRef queryWhereFieldPath:[FIRFieldPath documentID] + isLessThan:@"doc5"] queryWhereField:@"sort" isGreaterThan:@1] + queryWhereField:@"key" + isNotEqualTo:@"a"] queryOrderedByField:@"sort" descending:YES]; + snapshot = [self readDocumentSetForRef:[self compositeIndexQuery:query]]; + // Ordered by: 'sort' desc,'key' desc, __name__ desc + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc2", @"doc3", @"doc4" ])]; +} + +- (void)testMultipleInequalityReadFromCacheWhenOffline { + FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{ + @"doc1" : @{@"key" : @"a", @"sort" : @1}, + @"doc2" : @{@"key" : @"aa", @"sort" : @4}, + @"doc3" : @{@"key" : @"b", @"sort" : @3}, + @"doc4" : @{@"key" : @"b", @"sort" : @2}, + }]; + + FIRQuery *query = [self compositeIndexQuery:[[collRef queryWhereField:@"key" isNotEqualTo:@"a"] + queryWhereField:@"sort" + isLessThanOrEqualTo:@3]]; + // populate the cache. + FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:query]; + XCTAssertEqual(snapshot.count, 2L); + XCTAssertEqual(snapshot.metadata.isFromCache, NO); + + [self disableNetwork]; + + snapshot = [self readDocumentSetForRef:query]; + XCTAssertEqual(snapshot.count, 2L); + XCTAssertEqual(snapshot.metadata.isFromCache, YES); + // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc + [self assertSnapshotResultIdsMatch:snapshot expectedIds:(@[ @"doc4", @"doc3" ])]; +} + +- (void)testMultipleInequalityFromCacheAndFromServer { + FIRCollectionReference *collRef = [self collectionRefwithTestDocs:@{ + @"doc1" : @{@"a" : @1, @"b" : @0}, + @"doc2" : @{@"a" : @2, @"b" : @1}, + @"doc3" : @{@"a" : @3, @"b" : @2}, + @"doc4" : @{@"a" : @1, @"b" : @3}, + @"doc5" : @{@"a" : @1, @"b" : @1}, + }]; + + // implicit AND: a != 1 && b < 2 + FIRQuery *query = [[collRef queryWhereField:@"a" isNotEqualTo:@1] queryWhereField:@"b" + isLessThan:@2]; + [self assertOnlineAndOfflineResultsMatch:[self compositeIndexQuery:query] + expectedDocs:@[ @"doc2" ]]; + + // explicit AND: a != 1 && b < 2 + query = + [collRef queryWhereFilter:[FIRFilter andFilterWithFilters:@[ + [FIRFilter filterWhereField:@"a" isNotEqualTo:@1], [FIRFilter filterWhereField:@"b" + isLessThan:@2] + ]]]; + [self assertOnlineAndOfflineResultsMatch:[self compositeIndexQuery:query] + expectedDocs:@[ @"doc2" ]]; + + // explicit AND: a < 3 && b not-in [2, 3] + // Implicitly ordered by: a asc, b asc, __name__ asc + query = [collRef + queryWhereFilter:[FIRFilter andFilterWithFilters:@[ + [FIRFilter filterWhereField:@"a" isLessThan:@3], [FIRFilter filterWhereField:@"b" + notIn:@[ @2, @3 ]] + ]]]; + [self assertOnlineAndOfflineResultsMatch:[self compositeIndexQuery:query] + expectedDocs:@[ @"doc1", @"doc5", @"doc2" ]]; + + // a <3 && b != 0, ordered by: b desc, a desc, __name__ desc + query = [[[[collRef queryWhereField:@"a" isLessThan:@3] queryWhereField:@"b" isNotEqualTo:@0] + queryOrderedByField:@"b" + descending:YES] queryLimitedTo:2]; + [self assertOnlineAndOfflineResultsMatch:[self compositeIndexQuery:query] + expectedDocs:@[ @"doc4", @"doc2" ]]; + + // explicit OR: a>2 || b<1. + query = [collRef + queryWhereFilter:[FIRFilter orFilterWithFilters:@[ + [FIRFilter filterWhereField:@"a" isGreaterThan:@2], [FIRFilter filterWhereField:@"b" + isLessThan:@1] + ]]]; + [self assertOnlineAndOfflineResultsMatch:[self compositeIndexQuery:query] + expectedDocs:@[ @"doc1", @"doc3" ]]; +} + +- (void)testMultipleInequalityRejectsIfDocumentKeyIsNotTheLastOrderByField { + FIRCollectionReference *collRef = [self collectionRef]; + + FIRQuery *query = [[collRef queryWhereField:@"key" isNotEqualTo:@42] + queryOrderedByFieldPath:[FIRFieldPath documentID]]; + + XCTestExpectation *queryCompletion = [self expectationWithDescription:@"query"]; + [query getDocumentsWithCompletion:^(FIRQuerySnapshot *results, NSError *error) { + XCTAssertNil(results); + XCTAssertNotNil(error); + XCTAssertEqual(error.code, FIRFirestoreErrorCodeInvalidArgument); + [queryCompletion fulfill]; + }]; + [self awaitExpectations]; +} + +- (void)testMultipleInequalityRejectsIfDocumentKeyAppearsOnlyInEqualityFilter { + FIRCollectionReference *collRef = [self collectionRef]; + + FIRQuery *query = [[collRef queryWhereField:@"key" + isNotEqualTo:@42] queryWhereFieldPath:[FIRFieldPath documentID] + isEqualTo:@"doc1"]; + + XCTestExpectation *queryCompletion = [self expectationWithDescription:@"query"]; + [query getDocumentsWithCompletion:^(FIRQuerySnapshot *results, NSError *error) { + XCTAssertNil(results); + XCTAssertNotNil(error); + XCTAssertEqual(error.code, FIRFirestoreErrorCodeInvalidArgument); + [queryCompletion fulfill]; + }]; + [self awaitExpectations]; +} + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm b/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm index 7be42ae8d99..9afd1b469f1 100644 --- a/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm @@ -1061,556 +1061,6 @@ - (void)testOrderByEquality { matchesResult:@[ @"doc6", @"doc3" ]]; } -// Multiple Inequality -- (void)testMultipleInequalityOnDifferentFields { - // TODO(MIEQ): Enable this test against production when possible. - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because multiple inequality is " - "not supported yet."); - - FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{ - @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @0}, - @"doc2" : @{@"key" : @"b", @"sort" : @3, @"v" : @1}, - @"doc3" : @{@"key" : @"c", @"sort" : @1, @"v" : @3}, - @"doc4" : @{@"key" : @"d", @"sort" : @2, @"v" : @2} - }]; - - // Multiple inequality fields - FIRQuerySnapshot *snapshot = [self - readDocumentSetForRef:[[[collRef queryWhereField:@"key" isNotEqualTo:@"a"] - queryWhereField:@"sort" - isLessThanOrEqualTo:@2] queryWhereField:@"v" isGreaterThan:@2]]; - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc3" ])); - - // Duplicate inequality fields - snapshot = [self readDocumentSetForRef:[[[collRef queryWhereField:@"key" isNotEqualTo:@"a"] - queryWhereField:@"sort" - isLessThanOrEqualTo:@2] queryWhereField:@"sort" - isGreaterThan:@1]]; - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc4" ])); - - // With multiple IN - snapshot = [self - readDocumentSetForRef:[[[[collRef queryWhereField:@"key" - isGreaterThanOrEqualTo:@"a"] queryWhereField:@"sort" - isLessThanOrEqualTo:@2] - queryWhereField:@"v" - in:@[ @2, @3, @4 ]] queryWhereField:@"sort" - in:@[ @2, @3 ]]]; - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc4" ])); - - // With NOT-IN - snapshot = [self readDocumentSetForRef:[[[collRef queryWhereField:@"key" - isGreaterThanOrEqualTo:@"a"] queryWhereField:@"sort" - isLessThanOrEqualTo:@2] - queryWhereField:@"v" - notIn:@[ @2, @4, @5 ]]]; - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc1", @"doc3" ])); - - // With orderby - snapshot = [self readDocumentSetForRef:[[[collRef queryWhereField:@"key" - isGreaterThanOrEqualTo:@"a"] queryWhereField:@"sort" - isLessThanOrEqualTo:@2] - queryOrderedByField:@"v" - descending:YES]]; - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc3", @"doc4", @"doc1" ])); - - // With limit - snapshot = [self readDocumentSetForRef:[[[[collRef queryWhereField:@"key" - isGreaterThanOrEqualTo:@"a"] queryWhereField:@"sort" - isLessThanOrEqualTo:@2] - queryOrderedByField:@"v" - descending:YES] queryLimitedTo:2]]; - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc3", @"doc4" ])); - - // With limitedToLast - snapshot = [self readDocumentSetForRef:[[[[collRef queryWhereField:@"key" - isGreaterThanOrEqualTo:@"a"] queryWhereField:@"sort" - isLessThanOrEqualTo:@2] - queryOrderedByField:@"v" - descending:YES] queryLimitedToLast:2]]; - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc4", @"doc1" ])); -} - -- (void)testMultipleInequalityOnSpecialValues { - // TODO(MIEQ): Enable this test against production when possible. - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because multiple inequality is " - "not supported yet."); - - FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{ - @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @0}, - @"doc2" : @{@"key" : @"b", @"sort" : @(NAN), @"v" : @1}, - @"doc3" : @{@"key" : @"c", @"sort" : [NSNull null], @"v" : @3}, - @"doc4" : @{@"key" : @"d", @"v" : @0}, - @"doc5" : @{@"key" : @"e", @"sort" : @1}, - @"doc6" : @{@"key" : @"f", @"sort" : @1, @"v" : @1} - }]; - - FIRQuerySnapshot *snapshot = - [self readDocumentSetForRef:[[collRef queryWhereField:@"key" - isNotEqualTo:@"a"] queryWhereField:@"sort" - isLessThanOrEqualTo:@2]]; - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc5", @"doc6" ])); - - snapshot = [self readDocumentSetForRef:[[[collRef queryWhereField:@"key" isNotEqualTo:@"a"] - queryWhereField:@"sort" - isLessThanOrEqualTo:@2] queryWhereField:@"v" - isLessThanOrEqualTo:@1]]; - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc6" ])); -} - -- (void)testMultipleInequalityWithArrayMembership { - // TODO(MIEQ): Enable this test against production when possible. - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because multiple inequality is " - "not supported yet."); - - FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{ - @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @[ @0 ]}, - @"doc2" : @{@"key" : @"b", @"sort" : @1, @"v" : @[ @0, @1, @3 ]}, - @"doc3" : @{@"key" : @"c", @"sort" : @1, @"v" : @[]}, - @"doc4" : @{@"key" : @"d", @"sort" : @2, @"v" : @[ @1 ]}, - @"doc5" : @{@"key" : @"e", @"sort" : @3, @"v" : @[ @2, @4 ]}, - @"doc6" : @{@"key" : @"f", @"sort" : @4, @"v" : @[ @(NAN) ]}, - @"doc7" : @{@"key" : @"g", @"sort" : @4, @"v" : @[ [NSNull null] ]} - - }]; - - FIRQuerySnapshot *snapshot = [self - readDocumentSetForRef:[[[collRef queryWhereField:@"key" isNotEqualTo:@"a"] - queryWhereField:@"sort" - isGreaterThanOrEqualTo:@1] queryWhereField:@"v" arrayContains:@0]]; - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc2" ])); - - snapshot = [self readDocumentSetForRef:[[[collRef queryWhereField:@"key" - isNotEqualTo:@"a"] queryWhereField:@"sort" - isGreaterThanOrEqualTo:@1] - queryWhereField:@"v" - arrayContainsAny:@[ @0, @1 ]]]; - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc2", @"doc4" ])); -} - -- (NSDictionary *)nestedData:(int)number { - return @{ - @"name" : [NSString stringWithFormat:@"room %d", number], - @"metadata" : @{@"createdAt" : @(number)}, - @"field" : [NSString stringWithFormat:@"field %d", number], - @"field.dot" : @(number), - @"field\\slash" : @(number) - }; -} - -- (void)testMultipleInequalityWithNestedField { - // TODO(MIEQ): Enable this test against production when possible. - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because multiple inequality is " - "not supported yet."); - - FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{ - @"doc1" : [self nestedData:400], - @"doc2" : [self nestedData:200], - @"doc3" : [self nestedData:100], - @"doc4" : [self nestedData:300] - }]; - - FIRQuerySnapshot *snapshot = [self - readDocumentSetForRef:[[[[collRef queryWhereField:@"metadata.createdAt" - isLessThanOrEqualTo:@500] queryWhereField:@"metadata.createdAt" - isGreaterThan:@100] - queryWhereField:@"name" - isNotEqualTo:@"room 200"] queryOrderedByField:@"name" - descending:NO]]; - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc4", @"doc1" ])); - - snapshot = - [self readDocumentSetForRef:[[[[collRef queryWhereField:@"field" - isGreaterThanOrEqualTo:@"field 100"] - queryWhereFieldPath:[[FIRFieldPath alloc] - initWithFields:@[ @"field.dot" ]] - isNotEqualTo:@300] queryWhereField:@"field\\slash" - isLessThan:@400] - queryOrderedByField:@"name" - descending:YES]]; - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc2", @"doc3" ])); -} - -- (void)testMultipleInequalityWithCompositeFilters { - // TODO(MIEQ): Enable this test against production when possible. - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because multiple inequality is " - "not supported yet."); - - FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{ - @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @5}, - @"doc2" : @{@"key" : @"aa", @"sort" : @4, @"v" : @4}, - @"doc3" : @{@"key" : @"c", @"sort" : @3, @"v" : @3}, - @"doc4" : @{@"key" : @"b", @"sort" : @2, @"v" : @2}, - @"doc5" : @{@"key" : @"b", @"sort" : @2, @"v" : @1}, - @"doc6" : @{@"key" : @"b", @"sort" : @0, @"v" : @0} - }]; - - FIRQuerySnapshot *snapshot = [self - readDocumentSetForRef:[collRef queryWhereFilter:[FIRFilter orFilterWithFilters:@[ - [FIRFilter andFilterWithFilters:@[ - [FIRFilter filterWhereField:@"key" isEqualTo:@"b"], - [FIRFilter filterWhereField:@"sort" isLessThanOrEqualTo:@2] - ]], - [FIRFilter andFilterWithFilters:@[ - [FIRFilter filterWhereField:@"key" isNotEqualTo:@"b"], - [FIRFilter filterWhereField:@"v" isGreaterThan:@4] - ]] - ]]]]; - // Implicitly ordered by: 'key' asc, 'sort' asc, 'v' asc, __name__ asc - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), - (@[ @"doc1", @"doc6", @"doc5", @"doc4" ])); - - snapshot = [self - readDocumentSetForRef:[[[collRef queryWhereFilter:[FIRFilter orFilterWithFilters:@[ - [FIRFilter andFilterWithFilters:@[ - [FIRFilter filterWhereField:@"key" isEqualTo:@"b"], - [FIRFilter filterWhereField:@"sort" - isLessThanOrEqualTo:@2] - ]], - [FIRFilter andFilterWithFilters:@[ - [FIRFilter filterWhereField:@"key" isNotEqualTo:@"b"], - [FIRFilter filterWhereField:@"v" isGreaterThan:@4] - ]] - ]]] queryOrderedByField:@"sort" - descending:YES] queryOrderedByField:@"key"]]; - // Ordered by: 'sort' desc, 'key' asc, 'v' asc, __name__ asc - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), - (@[ @"doc5", @"doc4", @"doc1", @"doc6" ])); - - snapshot = [self - readDocumentSetForRef:[collRef - queryWhereFilter:[FIRFilter andFilterWithFilters:@[ - - [FIRFilter orFilterWithFilters:@[ - [FIRFilter andFilterWithFilters:@[ - [FIRFilter filterWhereField:@"key" isEqualTo:@"b"], - [FIRFilter filterWhereField:@"sort" isLessThanOrEqualTo:@4] - ]], - [FIRFilter andFilterWithFilters:@[ - [FIRFilter filterWhereField:@"key" isNotEqualTo:@"b"], - [FIRFilter filterWhereField:@"v" isGreaterThanOrEqualTo:@4] - ]] - ]], - - [FIRFilter orFilterWithFilters:@[ - [FIRFilter andFilterWithFilters:@[ - [FIRFilter filterWhereField:@"key" isGreaterThan:@"b"], - [FIRFilter filterWhereField:@"sort" isGreaterThanOrEqualTo:@1] - ]], - [FIRFilter andFilterWithFilters:@[ - [FIRFilter filterWhereField:@"key" isLessThan:@"b"], - [FIRFilter filterWhereField:@"v" isGreaterThan:@0] - ]] - ]] - - ]]]]; - // Implicitly ordered by: 'key' asc, 'sort' asc, 'v' asc, __name__ asc - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc1", @"doc2" ])); -} - -- (void)testMultipleInequalityFieldsWillBeImplicitlyOrderedLexicographically { - // TODO(MIEQ): Enable this test against production when possible. - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because multiple inequality is " - "not supported yet."); - - FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{ - @"doc1" : @{@"key" : @"a", @"sort" : @0, @"v" : @5}, - @"doc2" : @{@"key" : @"aa", @"sort" : @4, @"v" : @4}, - @"doc3" : @{@"key" : @"b", @"sort" : @3, @"v" : @3}, - @"doc4" : @{@"key" : @"b", @"sort" : @2, @"v" : @2}, - @"doc5" : @{@"key" : @"b", @"sort" : @2, @"v" : @1}, - @"doc6" : @{@"key" : @"b", @"sort" : @0, @"v" : @0} - }]; - - FIRQuerySnapshot *snapshot = [self - readDocumentSetForRef:[[[collRef queryWhereField:@"key" isNotEqualTo:@"a"] - queryWhereField:@"sort" - isGreaterThan:@1] queryWhereField:@"v" in:@[ @1, @2, @3, @4 ]]]; - // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), - (@[ @"doc2", @"doc4", @"doc5", @"doc3" ])); - - snapshot = [self readDocumentSetForRef:[[[collRef queryWhereField:@"sort" - isGreaterThan:@1] queryWhereField:@"key" - isNotEqualTo:@"a"] - queryWhereField:@"v" - in:@[ @1, @2, @3, @4 ]]]; - // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), - (@[ @"doc2", @"doc4", @"doc5", @"doc3" ])); -} - -- (void)testMultipleInequalityWithMultipleExplicitOrderBy { - // TODO(MIEQ): Enable this test against production when possible. - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because multiple inequality is " - "not supported yet."); - - FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{ - @"doc1" : @{@"key" : @"a", @"sort" : @5, @"v" : @0}, - @"doc2" : @{@"key" : @"aa", @"sort" : @4, @"v" : @0}, - @"doc3" : @{@"key" : @"b", @"sort" : @3, @"v" : @1}, - @"doc4" : @{@"key" : @"b", @"sort" : @2, @"v" : @1}, - @"doc5" : @{@"key" : @"bb", @"sort" : @1, @"v" : @1}, - @"doc6" : @{@"key" : @"c", @"sort" : @0, @"v" : @2} - }]; - - FIRQuerySnapshot *snapshot = [self - readDocumentSetForRef:[[[collRef queryWhereField:@"key" isGreaterThan:@"a"] - queryWhereField:@"sort" - isGreaterThanOrEqualTo:@1] queryOrderedByField:@"v" descending:NO]]; - // Ordered by: 'v' asc, 'key' asc, 'sort' asc, __name__ asc - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), - (@[ @"doc2", @"doc4", @"doc3", @"doc5" ])); - - snapshot = [self readDocumentSetForRef:[[[[collRef queryWhereField:@"key" - isGreaterThan:@"a"] queryWhereField:@"sort" - isGreaterThanOrEqualTo:@1] - queryOrderedByField:@"v" - descending:NO] queryOrderedByField:@"sort" - descending:NO]]; - // Ordered by: 'v asc, 'sort' asc, 'key' asc, __name__ asc - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), - (@[ @"doc2", @"doc5", @"doc4", @"doc3" ])); - - snapshot = [self readDocumentSetForRef:[[[collRef queryWhereField:@"key" isGreaterThan:@"a"] - queryWhereField:@"sort" - isGreaterThanOrEqualTo:@1] queryOrderedByField:@"v" - descending:YES]]; - // Implicit order by matches the direction of last explicit order by. - // Ordered by: 'v' desc, 'key' desc, 'sort' desc, __name__ desc - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), - (@[ @"doc5", @"doc3", @"doc4", @"doc2" ])); - - snapshot = [self readDocumentSetForRef:[[[[collRef queryWhereField:@"key" - isGreaterThan:@"a"] queryWhereField:@"sort" - isGreaterThanOrEqualTo:@1] - queryOrderedByField:@"v" - descending:YES] queryOrderedByField:@"sort" - descending:NO]]; - // Ordered by: 'v desc, 'sort' asc, 'key' asc, __name__ asc - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), - (@[ @"doc5", @"doc4", @"doc3", @"doc2" ])); -} - -- (void)testMultipleInequalityInAggregateQuery { - // TODO(MIEQ): Enable this test against production when possible. - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because multiple inequality is " - "not supported yet."); - - FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{ - @"doc1" : @{@"key" : @"a", @"sort" : @5, @"v" : @0}, - @"doc2" : @{@"key" : @"aa", @"sort" : @4, @"v" : @0}, - @"doc3" : @{@"key" : @"b", @"sort" : @3, @"v" : @1}, - @"doc4" : @{@"key" : @"b", @"sort" : @2, @"v" : @1}, - @"doc5" : @{@"key" : @"bb", @"sort" : @1, @"v" : @1}, - }]; - - FIRAggregateQuerySnapshot *snapshot = - [self readSnapshotForAggregate:[[[[collRef queryWhereField:@"key" isGreaterThan:@"a"] - queryWhereField:@"sort" - isGreaterThanOrEqualTo:@1] queryOrderedByField:@"v" - descending:NO] - aggregate:@[ - [FIRAggregateField aggregateFieldForCount], - [FIRAggregateField aggregateFieldForSumOfField:@"sort"], - [FIRAggregateField aggregateFieldForAverageOfField:@"v"] - ]]]; - XCTAssertEqual([snapshot count], [NSNumber numberWithLong:4L]); - - snapshot = [self - readSnapshotForAggregate:[[[[collRef queryWhereField:@"key" isGreaterThan:@"a"] - queryWhereField:@"sort" - isGreaterThanOrEqualTo:@1] queryWhereField:@"v" isNotEqualTo:@0] - aggregate:@[ - [FIRAggregateField aggregateFieldForCount], - [FIRAggregateField aggregateFieldForSumOfField:@"sort"], - [FIRAggregateField aggregateFieldForAverageOfField:@"v"], - ]]]; - XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]], - [NSNumber numberWithLong:3L]); - XCTAssertEqual( - [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"sort"]] - longValue], - 6L); - XCTAssertEqual( - [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"v"]], - [NSNumber numberWithDouble:1.0]); -} - -- (void)testMultipleInequalityFieldsWithDocumentKey { - // TODO(MIEQ): Enable this test against production when possible. - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because multiple inequality is " - "not supported yet."); - - FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{ - @"doc1" : @{@"key" : @"a", @"sort" : @5}, - @"doc2" : @{@"key" : @"aa", @"sort" : @4}, - @"doc3" : @{@"key" : @"b", @"sort" : @3}, - @"doc4" : @{@"key" : @"b", @"sort" : @2}, - @"doc5" : @{@"key" : @"bb", @"sort" : @1} - }]; - - FIRQuerySnapshot *snapshot = [self - readDocumentSetForRef:[[[collRef queryWhereField:@"sort" - isGreaterThan:@1] queryWhereField:@"key" isNotEqualTo:@"a"] - queryWhereFieldPath:[FIRFieldPath documentID] - isLessThan:@"doc5"]]; - // Document Key in inequality field will implicitly ordered to the last. - // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc2", @"doc4", @"doc3" ])); - - snapshot = [self readDocumentSetForRef:[[[collRef queryWhereFieldPath:[FIRFieldPath documentID] - isLessThan:@"doc5"] - queryWhereField:@"sort" - isGreaterThan:@1] queryWhereField:@"key" - isNotEqualTo:@"a"]]; - // Changing filters order will not effect implicit order. - // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc2", @"doc4", @"doc3" ])); - - snapshot = [self - readDocumentSetForRef:[[[[collRef queryWhereFieldPath:[FIRFieldPath documentID] - isLessThan:@"doc5"] queryWhereField:@"sort" - isGreaterThan:@1] - queryWhereField:@"key" - isNotEqualTo:@"a"] queryOrderedByField:@"sort" descending:YES]]; - // Ordered by: 'sort' desc,'key' desc, __name__ desc - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc2", @"doc3", @"doc4" ])); -} - -- (void)testMultipleInequalityReadFromCacheWhenOffline { - // TODO(MIEQ): Enable this test against production when possible. - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because multiple inequality is " - "not supported yet."); - - FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{ - @"doc1" : @{@"key" : @"a", @"sort" : @1}, - @"doc2" : @{@"key" : @"aa", @"sort" : @4}, - @"doc3" : @{@"key" : @"b", @"sort" : @3}, - @"doc4" : @{@"key" : @"b", @"sort" : @2}, - }]; - - FIRQuery *query = [[collRef queryWhereField:@"key" isNotEqualTo:@"a"] queryWhereField:@"sort" - isLessThanOrEqualTo:@3]; - - // populate the cache. - FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:query]; - XCTAssertEqual(snapshot.count, 2L); - XCTAssertEqual(snapshot.metadata.isFromCache, NO); - - [self disableNetwork]; - - snapshot = [self readDocumentSetForRef:query]; - XCTAssertEqual(snapshot.count, 2L); - XCTAssertEqual(snapshot.metadata.isFromCache, YES); - // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc - XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"doc4", @"doc3" ])); -} - -- (void)testMultipleInequalityFromCacheAndFromServer { - // TODO(MIEQ): Enable this test against production when possible. - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because multiple inequality is " - "not supported yet."); - - FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{ - @"doc1" : @{@"a" : @1, @"b" : @0}, - @"doc2" : @{@"a" : @2, @"b" : @1}, - @"doc3" : @{@"a" : @3, @"b" : @2}, - @"doc4" : @{@"a" : @1, @"b" : @3}, - @"doc5" : @{@"a" : @1, @"b" : @1}, - - }]; - - // implicit AND: a != 1 && b < 2 - FIRQuery *query = [[collRef queryWhereField:@"a" isNotEqualTo:@1] queryWhereField:@"b" - isLessThan:@2]; - [self checkOnlineAndOfflineQuery:query matchesResult:@[ @"doc2" ]]; - - // explicit AND: a != 1 && b < 2 - FIRFilter *filter = [FIRFilter andFilterWithFilters:@[ - [FIRFilter filterWhereField:@"a" isNotEqualTo:@1], [FIRFilter filterWhereField:@"b" - isLessThan:@2] - ]]; - [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter] matchesResult:@[ @"doc2" ]]; - - // explicit AND: a < 3 && b not-in [2, 3] - // Implicitly ordered by: a asc, b asc, __name__ asc - filter = [FIRFilter andFilterWithFilters:@[ - [FIRFilter filterWhereField:@"a" isLessThan:@3], [FIRFilter filterWhereField:@"b" - notIn:@[ @2, @3 ]] - ]]; - [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter] - matchesResult:@[ @"doc1", @"doc5", @"doc2" ]]; - - // a <3 && b != 0, ordered by: b desc, a desc, __name__ desc - query = [[[[collRef queryWhereField:@"a" isLessThan:@3] queryWhereField:@"b" isNotEqualTo:@0] - queryOrderedByField:@"b" - descending:YES] queryLimitedTo:2]; - [self checkOnlineAndOfflineQuery:query matchesResult:@[ @"doc4", @"doc2" ]]; - - // explicit OR: a>2 || b<1. - filter = [FIRFilter orFilterWithFilters:@[ - [FIRFilter filterWhereField:@"a" isGreaterThan:@2], [FIRFilter filterWhereField:@"b" - isLessThan:@1] - ]]; - [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter] - matchesResult:@[ @"doc1", @"doc3" ]]; -} - -- (void)testMultipleInequalityRejectsIfDocumentKeyIsNotTheLastOrderByField { - // TODO(MIEQ): Enable this test against production when possible. - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because multiple inequality is " - "not supported yet."); - - FIRCollectionReference *collRef = [self collectionRef]; - - FIRQuery *query = [[collRef queryWhereField:@"key" isNotEqualTo:@42] - queryOrderedByFieldPath:[FIRFieldPath documentID]]; - - XCTestExpectation *queryCompletion = [self expectationWithDescription:@"query"]; - [query getDocumentsWithCompletion:^(FIRQuerySnapshot *results, NSError *error) { - XCTAssertNil(results); - XCTAssertNotNil(error); - XCTAssertEqual(error.code, FIRFirestoreErrorCodeInvalidArgument); - [queryCompletion fulfill]; - }]; - [self awaitExpectations]; -} - -- (void)testMultipleInequalityRejectsIfDocumentKeyAppearsOnlyInEqualityFilter { - // TODO(MIEQ): Enable this test against production when possible. - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because multiple inequality is " - "not supported yet."); - - FIRCollectionReference *collRef = [self collectionRef]; - - FIRQuery *query = [[collRef queryWhereField:@"key" - isNotEqualTo:@42] queryWhereFieldPath:[FIRFieldPath documentID] - isEqualTo:@"doc1"]; - - XCTestExpectation *queryCompletion = [self expectationWithDescription:@"query"]; - [query getDocumentsWithCompletion:^(FIRQuerySnapshot *results, NSError *error) { - XCTAssertNil(results); - XCTAssertNotNil(error); - XCTAssertEqual(error.code, FIRFirestoreErrorCodeInvalidArgument); - [queryCompletion fulfill]; - }]; - [self awaitExpectations]; -} - - (void)testResumingAQueryShouldUseBloomFilterToAvoidFullRequery { // TODO(b/291365820): Stop skipping this test when running against the Firestore emulator once // the emulator is improved to include a bloom filter in the existence filter messages that it diff --git a/Firestore/firestore_collection_group_index_config.tf b/Firestore/firestore_collection_group_index_config.tf index 937096951e0..2d8b98a312e 100644 --- a/Firestore/firestore_collection_group_index_config.tf +++ b/Firestore/firestore_collection_group_index_config.tf @@ -1,3 +1,17 @@ +# Copyright 2024 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. + locals { collection_group_indexes = { index1 = [ diff --git a/Firestore/firestore_composite_index_config.tf b/Firestore/firestore_composite_index_config.tf index ad14134d716..ad541ac61ea 100644 --- a/Firestore/firestore_composite_index_config.tf +++ b/Firestore/firestore_composite_index_config.tf @@ -1,10 +1,20 @@ +# Copyright 2024 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. + locals { indexes = { index1 = [ - { - field_path = "b" - order = "ASCENDING" - }, { field_path = "testId" order = "ASCENDING" @@ -16,9 +26,15 @@ locals { ] index2 = [ { - field_path = "a" + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "b" order = "ASCENDING" }, + ] + index3 = [ { field_path = "testId" order = "ASCENDING" @@ -28,7 +44,7 @@ locals { order = "DESCENDING" }, ] - index3 = [ + index4 = [ { field_path = "a" order = "ASCENDING" @@ -42,7 +58,7 @@ locals { order = "ASCENDING" }, ] - index4 = [ + index5 = [ { field_path = "a" order = "ASCENDING" @@ -52,55 +68,83 @@ locals { order = "ASCENDING" }, { - field_path = "a" + field_path = "b" order = "DESCENDING" }, ] - index5 = [ + index6 = [ + { + field_path = "a" + order = "ASCENDING" + }, { field_path = "testId" order = "ASCENDING" }, { field_path = "a" - order = "ASCENDING" + order = "DESCENDING" }, ] - index6 = [ + index7 = [ + { + field_path = "b" + order = "ASCENDING" + }, { field_path = "testId" order = "ASCENDING" }, { - field_path = "b" + field_path = "a" order = "ASCENDING" }, ] - index7 = [ + index8 = [ + { + field_path = "b" + order = "ASCENDING" + }, { field_path = "testId" order = "ASCENDING" }, { - field_path = "b" + field_path = "a" order = "DESCENDING" }, ] - index8 = [ + index9 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "a" + order = "ASCENDING" + }, + { field_path = "b" order = "ASCENDING" }, + ] + index10 = [ { field_path = "testId" order = "ASCENDING" }, + { + field_path = "b" + order = "DESCENDING" + }, + { field_path = "a" order = "DESCENDING" }, ] - index9 = [ + index11 = [ { field_path = "testId" order = "ASCENDING" @@ -114,7 +158,7 @@ locals { order = "ASCENDING" }, ] - index10 = [ + index12 = [ { field_path = "testId" order = "ASCENDING" @@ -132,7 +176,7 @@ locals { order = "ASCENDING" }, ] - index11 = [ + index13 = [ { field_path = "rating" array_config = "CONTAINS" @@ -150,5 +194,218 @@ locals { order = "ASCENDING" }, ] + index14 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "key" + order = "ASCENDING" + }, + { + field_path = "sort" + order = "ASCENDING" + } + ] + index15 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "key" + order = "ASCENDING" + }, + { + field_path = "sort" + order = "ASCENDING" + }, + { + field_path = "v" + order = "ASCENDING" + } + ] + index16 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "v" + order = "DESCENDING" + }, + { + field_path = "key" + order = "DESCENDING" + }, + { + field_path = "sort" + order = "DESCENDING" + }, + ] + index17 = [ + { + field_path = "v" + array_config = "CONTAINS" + }, + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "key" + order = "ASCENDING" + }, + { + field_path = "sort" + order = "ASCENDING" + }, + ] + index18 = [ + { + field_path = "key" + order = "ASCENDING" + }, + { + field_path = "testId" + order = "ASCENDING" + }, + + { + field_path = "sort" + order = "DESCENDING" + }, + { + field_path = "v" + order = "ASCENDING" + }, + ] + index19 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + + { + field_path = "sort" + order = "DESCENDING" + }, + { + field_path = "key" + order = "ASCENDING" + }, + { + field_path = "v" + order = "ASCENDING" + }, + ] + index20 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "v" + order = "ASCENDING" + }, + + { + field_path = "sort" + order = "ASCENDING" + }, + { + field_path = "key" + order = "ASCENDING" + }, + + ] + index21 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "sort" + order = "DESCENDING" + }, + { + field_path = "key" + order = "DESCENDING" + }, + + ] + index22 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "v" + order = "DESCENDING" + }, + { + field_path = "sort" + order = "ASCENDING" + }, + { + field_path = "key" + order = "ASCENDING" + }, + ] + index23 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "name" + order = "ASCENDING" + }, + { + field_path = "metadata.createdAt" + order = "ASCENDING" + }, + ] + index24 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "name" + order = "DESCENDING" + }, + { + field_path = "field" + order = "DESCENDING" + }, + { + field_path = "`field.dot`" + order = "DESCENDING" + }, + { + field_path = "`field\\\\slash`" + order = "DESCENDING" + }, + ], + index25 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "v" + order = "ASCENDING" + }, + { + field_path = "key" + order = "ASCENDING" + }, + { + field_path = "sort" + order = "ASCENDING" + }, + ] } -} +} \ No newline at end of file