diff --git a/Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm b/Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm index c1e45c0f49d..055772760be 100644 --- a/Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm @@ -469,61 +469,6 @@ - (void)testTerminateDoesNotCrashWithFlyingAggregateQuery { [NSNumber numberWithLong:150L], ); } -- (void)testCanPerformMaxAggregations { - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because it requires a composite index."); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ - @"a" : @{ - @"author" : @"authorA", - @"title" : @"titleA", - @"pages" : @100, - @"height" : @24.5, - @"weight" : @24.1, - @"foo" : @1, - @"bar" : @2, - @"baz" : @3 - }, - @"b" : @{ - @"author" : @"authorB", - @"title" : @"titleB", - @"pages" : @50, - @"height" : @25.5, - @"weight" : @75.5, - @"foo" : @1, - @"bar" : @2, - @"baz" : @3 - } - }]; - - // Max is 5, do not exceed - FIRAggregateQuerySnapshot* snapshot = - [self readSnapshotForAggregate:[testCollection aggregate:@[ - [FIRAggregateField aggregateFieldForCount], - [FIRAggregateField aggregateFieldForSumOfField:@"pages"], - [FIRAggregateField aggregateFieldForSumOfField:@"weight"], - [FIRAggregateField aggregateFieldForAverageOfField:@"pages"], - [FIRAggregateField aggregateFieldForAverageOfField:@"weight"] - ]]]; - - // Assert - XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]], - [NSNumber numberWithLong:2L]); - XCTAssertEqual( - [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]], - [NSNumber numberWithLong:150L], ); - XCTAssertEqual( - [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"weight"]] - doubleValue], - 99.6); - XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField - aggregateFieldForAverageOfField:@"pages"]], - [NSNumber numberWithDouble:75.0]); - XCTAssertEqual([[snapshot valueForAggregateField:[FIRAggregateField - aggregateFieldForAverageOfField:@"weight"]] - doubleValue], - 49.8); -} - - (void)testCannotPerformMoreThanMaxAggregations { FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @@ -574,105 +519,6 @@ - (void)testCannotPerformMoreThanMaxAggregations { XCTAssertTrue([[result localizedDescription] containsString:@"maximum number of aggregations"]); } -- (void)testCanRunAggregateCollectionGroupQuery { - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because it requires a composite index."); - - NSString* collectionGroup = - [NSString stringWithFormat:@"%@%@", @"b", - [self.db collectionWithPath:@"foo"].documentWithAutoID.documentID]; - NSArray* docPathFormats = @[ - @"abc/123/%@/cg-doc1", @"abc/123/%@/cg-doc2", @"%@/cg-doc3", @"%@/cg-doc4", - @"def/456/%@/cg-doc5", @"%@/virtual-doc/nested-coll/not-cg-doc", @"x%@/not-cg-doc", - @"%@x/not-cg-doc", @"abc/123/%@x/not-cg-doc", @"abc/123/x%@/not-cg-doc", @"abc/%@" - ]; - - FIRWriteBatch* batch = self.db.batch; - for (NSString* format in docPathFormats) { - NSString* path = [NSString stringWithFormat:format, collectionGroup]; - [batch setData:@{@"x" : @2} forDocument:[self.db documentWithPath:path]]; - } - [self commitWriteBatch:batch]; - - FIRAggregateQuerySnapshot* snapshot = - [self readSnapshotForAggregate:[[self.db collectionGroupWithID:collectionGroup] aggregate:@[ - [FIRAggregateField aggregateFieldForCount], - [FIRAggregateField aggregateFieldForSumOfField:@"x"], - [FIRAggregateField aggregateFieldForAverageOfField:@"x"] - ]]]; - // "cg-doc1", "cg-doc2", "cg-doc3", "cg-doc4", "cg-doc5", - XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]], - [NSNumber numberWithLong:5L]); - XCTAssertEqual( - [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"x"]], - [NSNumber numberWithLong:10L]); - XCTAssertEqual( - [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"x"]], - [NSNumber numberWithDouble:2.0]); -} - -- (void)testPerformsAggregationsWhenNaNExistsForSomeFieldValues { - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because it requires a composite index."); - - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ - @"a" : @{ - @"author" : @"authorA", - @"title" : @"titleA", - @"pages" : @100, - @"year" : @1980, - @"rating" : @5 - }, - @"b" : @{ - @"author" : @"authorB", - @"title" : @"titleB", - @"pages" : @50, - @"year" : @2020, - @"rating" : @4 - }, - @"c" : @{ - @"author" : @"authorC", - @"title" : @"titleC", - @"pages" : @100, - @"year" : @1980, - @"rating" : [NSNumber numberWithFloat:NAN] - }, - @"d" : @{ - @"author" : @"authorD", - @"title" : @"titleD", - @"pages" : @50, - @"year" : @2020, - @"rating" : @0 - } - }]; - - FIRAggregateQuerySnapshot* snapshot = - [self readSnapshotForAggregate:[testCollection aggregate:@[ - [FIRAggregateField aggregateFieldForSumOfField:@"rating"], - [FIRAggregateField aggregateFieldForSumOfField:@"pages"], - [FIRAggregateField aggregateFieldForAverageOfField:@"rating"], - [FIRAggregateField aggregateFieldForAverageOfField:@"year"] - ]]]; - - // Sum - XCTAssertEqual( - [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]], - [NSNumber numberWithDouble:NAN]); - XCTAssertEqual( - [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]] - longValue], - 300L); - - // Average - XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField - aggregateFieldForAverageOfField:@"rating"]], - [NSNumber numberWithDouble:NAN]); - XCTAssertEqual( - [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"year"]] - doubleValue], - 2000.0); -} - - (void)testThrowsAnErrorWhenGettingTheResultOfAnUnrequestedAggregation { FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @@ -789,77 +635,6 @@ - (void)testPerformsAggregationWhenUsingInOperator { 4.0); } -- (void)testPerformsAggregationWhenUsingArrayContainsAnyOperator { - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], - "Skip this test if running against production because it requires a composite index."); - - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ - @"a" : @{ - @"author" : @"authorA", - @"title" : @"titleA", - @"pages" : @100, - @"year" : @1980, - @"rating" : @[ @5, @1000 ] - }, - @"b" : @{ - @"author" : @"authorB", - @"title" : @"titleB", - @"pages" : @50, - @"year" : @2020, - @"rating" : @[ @4 ] - }, - @"c" : @{ - @"author" : @"authorC", - @"title" : @"titleC", - @"pages" : @100, - @"year" : @1980, - @"rating" : @[ @2222, @3 ] - }, - @"d" : @{ - @"author" : @"authorD", - @"title" : @"titleD", - @"pages" : @50, - @"year" : @2020, - @"rating" : @[ @0 ] - } - }]; - - FIRAggregateQuerySnapshot* snapshot = [self - readSnapshotForAggregate:[[testCollection queryWhereField:@"rating" - arrayContainsAny:@[ @5, @3 ]] - aggregate:@[ - [FIRAggregateField aggregateFieldForSumOfField:@"rating"], - [FIRAggregateField aggregateFieldForSumOfField:@"pages"], - [FIRAggregateField aggregateFieldForAverageOfField:@"rating"], - [FIRAggregateField aggregateFieldForAverageOfField:@"pages"], - [FIRAggregateField aggregateFieldForCount] - ]]]; - - // Count - XCTAssertEqual( - [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]] longValue], 2L); - - // Sum - XCTAssertEqual( - [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] - longValue], - 0L); - XCTAssertEqual( - [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]] - longValue], - 200L); - - // Average - XCTAssertEqualObjects( - [snapshot - valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]], - [NSNull null]); - XCTAssertEqual( - [[snapshot valueForAggregateField:[FIRAggregateField - aggregateFieldForAverageOfField:@"pages"]] doubleValue], - 100.0); -} - - (void)testPerformsAggregationsOnNestedMapValues { FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ diff --git a/Firestore/Example/Tests/Integration/API/FIRCompositeIndexQueryTests.mm b/Firestore/Example/Tests/Integration/API/FIRCompositeIndexQueryTests.mm index 63a88c159d6..ece28eb23bf 100644 --- a/Firestore/Example/Tests/Integration/API/FIRCompositeIndexQueryTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRCompositeIndexQueryTests.mm @@ -189,6 +189,10 @@ - (void)deleteDoc:(FIRDocumentReference *)document { * To get started, please refer to the instructions provided in the README file. This will * guide you through setting up your local testing environment and updating the Terraform * configuration with any new composite indexes required for your testing scenarios. + * + * Note: Whenever feasible, make use of the current document fields (such as 'a,' 'b,' 'author,' + * 'title') to avoid introducing new composite indexes and surpassing the limit. Refer to the + * guidelines at https://firebase.google.com/docs/firestore/quotas#indexes for further information. */ - (void)testOrQueriesWithCompositeIndexes { @@ -249,6 +253,216 @@ - (void)testOrQueriesWithCompositeIndexes { expectedDocs:@[ @"doc2" ]]; } +- (void)testCanRunAggregateCollectionGroupQuery { + NSString *collectionGroup = [[self testCollectionRef] collectionID]; + NSArray *docPathFormats = @[ + @"abc/123/%@/cg-doc1", @"abc/123/%@/cg-doc2", @"%@/cg-doc3", @"%@/cg-doc4", + @"def/456/%@/cg-doc5", @"%@/virtual-doc/nested-coll/not-cg-doc", @"x%@/not-cg-doc", + @"%@x/not-cg-doc", @"abc/123/%@x/not-cg-doc", @"abc/123/x%@/not-cg-doc", @"abc/%@" + ]; + + FIRWriteBatch *batch = self.db.batch; + for (NSString *format in docPathFormats) { + NSString *path = [NSString stringWithFormat:format, collectionGroup]; + [batch setData:[self addTestSpecificFieldsToDoc:@{@"a" : @2}] + forDocument:[self.db documentWithPath:path]]; + } + [self commitWriteBatch:batch]; + + FIRAggregateQuerySnapshot *snapshot = [self + readSnapshotForAggregate:[[self + compositeIndexQuery:[self.db + collectionGroupWithID:collectionGroup]] + aggregate:@[ + [FIRAggregateField aggregateFieldForCount], + [FIRAggregateField aggregateFieldForSumOfField:@"a"], + [FIRAggregateField aggregateFieldForAverageOfField:@"a"] + ]]]; + // "cg-doc1", "cg-doc2", "cg-doc3", "cg-doc4", "cg-doc5", + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]], + [NSNumber numberWithLong:5L]); + XCTAssertEqual( + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"a"]], + [NSNumber numberWithLong:10L]); + XCTAssertEqual( + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"a"]], + [NSNumber numberWithDouble:2.0]); +} + +- (void)testCanPerformMaxAggregations { + FIRCollectionReference *testCollection = [self withTestDocs:@{ + @"a" : @{ + @"author" : @"authorA", + @"title" : @"titleA", + @"pages" : @100, + @"year" : @1980, + @"rating" : @5.0, + }, + @"b" : @{ + @"author" : @"authorB", + @"title" : @"titleB", + @"pages" : @50, + @"year" : @2020, + @"rating" : @4.0, + } + }]; + + // Max is 5, do not exceed + FIRAggregateQuerySnapshot *snapshot = + [self readSnapshotForAggregate:[[self compositeIndexQuery:testCollection] aggregate:@[ + [FIRAggregateField aggregateFieldForCount], + [FIRAggregateField aggregateFieldForSumOfField:@"pages"], + [FIRAggregateField aggregateFieldForSumOfField:@"year"], + [FIRAggregateField aggregateFieldForAverageOfField:@"pages"], + [FIRAggregateField aggregateFieldForAverageOfField:@"rating"] + ]]]; + + // Assert + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]], + [NSNumber numberWithLong:2L]); + XCTAssertEqual( + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]], + [NSNumber numberWithLong:150L]); + XCTAssertEqual( + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"year"]], + [NSNumber numberWithLong:4000L]); + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"pages"]], + [NSNumber numberWithDouble:75.0]); + XCTAssertEqual([[snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"rating"]] + doubleValue], + 4.5); +} + +- (void)testPerformsAggregationsWhenNaNExistsForSomeFieldValues { + FIRCollectionReference *testCollection = [self withTestDocs:@{ + @"a" : @{ + @"author" : @"authorA", + @"title" : @"titleA", + @"pages" : @100, + @"year" : @1980, + @"rating" : @5 + }, + @"b" : @{ + @"author" : @"authorB", + @"title" : @"titleB", + @"pages" : @50, + @"year" : @2020, + @"rating" : @4 + }, + @"c" : @{ + @"author" : @"authorC", + @"title" : @"titleC", + @"pages" : @100, + @"year" : @1980, + @"rating" : [NSNumber numberWithFloat:NAN] + }, + @"d" : @{ + @"author" : @"authorD", + @"title" : @"titleD", + @"pages" : @50, + @"year" : @2020, + @"rating" : @0 + } + }]; + + FIRAggregateQuerySnapshot *snapshot = + [self readSnapshotForAggregate:[[self compositeIndexQuery:testCollection] aggregate:@[ + [FIRAggregateField aggregateFieldForSumOfField:@"rating"], + [FIRAggregateField aggregateFieldForSumOfField:@"pages"], + [FIRAggregateField aggregateFieldForAverageOfField:@"rating"], + [FIRAggregateField aggregateFieldForAverageOfField:@"year"] + ]]]; + + // Sum + XCTAssertEqual( + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]], + [NSNumber numberWithDouble:NAN]); + XCTAssertEqual( + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]] + longValue], + 300L); + + // Average + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"rating"]], + [NSNumber numberWithDouble:NAN]); + XCTAssertEqual( + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"year"]] + doubleValue], + 2000.0); +} + +- (void)testPerformsAggregationWhenUsingArrayContainsAnyOperator { + FIRCollectionReference *testCollection = [self withTestDocs:@{ + @"a" : @{ + @"author" : @"authorA", + @"title" : @"titleA", + @"pages" : @100, + @"year" : @1980, + @"rating" : @[ @5, @1000 ] + }, + @"b" : @{ + @"author" : @"authorB", + @"title" : @"titleB", + @"pages" : @50, + @"year" : @2020, + @"rating" : @[ @4 ] + }, + @"c" : @{ + @"author" : @"authorC", + @"title" : @"titleC", + @"pages" : @100, + @"year" : @1980, + @"rating" : @[ @2222, @3 ] + }, + @"d" : @{ + @"author" : @"authorD", + @"title" : @"titleD", + @"pages" : @50, + @"year" : @2020, + @"rating" : @[ @0 ] + } + }]; + + FIRAggregateQuerySnapshot *snapshot = [self + readSnapshotForAggregate:[[self + compositeIndexQuery:[testCollection queryWhereField:@"rating" + arrayContainsAny:@[ @5, @3 ]]] + aggregate:@[ + [FIRAggregateField aggregateFieldForSumOfField:@"rating"], + [FIRAggregateField aggregateFieldForSumOfField:@"pages"], + [FIRAggregateField aggregateFieldForAverageOfField:@"rating"], + [FIRAggregateField aggregateFieldForAverageOfField:@"pages"], + [FIRAggregateField aggregateFieldForCount] + ]]]; + + // Count + XCTAssertEqual( + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]] longValue], 2L); + + // Sum + XCTAssertEqual( + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] + longValue], + 0L); + XCTAssertEqual( + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]] + longValue], + 200L); + + // Average + XCTAssertEqualObjects( + [snapshot + valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]], + [NSNull null]); + XCTAssertEqual( + [[snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"pages"]] doubleValue], + 100.0); +} + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/firestore_collection_group_index_config.tf b/Firestore/firestore_collection_group_index_config.tf new file mode 100644 index 00000000000..937096951e0 --- /dev/null +++ b/Firestore/firestore_collection_group_index_config.tf @@ -0,0 +1,14 @@ +locals { + collection_group_indexes = { + index1 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "a" + order = "ASCENDING" + }, + ] + } +} \ No newline at end of file diff --git a/Firestore/firestore_index_config.tf b/Firestore/firestore_composite_index_config.tf similarity index 65% rename from Firestore/firestore_index_config.tf rename to Firestore/firestore_composite_index_config.tf index 98ed132d62f..ad14134d716 100644 --- a/Firestore/firestore_index_config.tf +++ b/Firestore/firestore_composite_index_config.tf @@ -100,5 +100,55 @@ locals { order = "DESCENDING" }, ] + index9 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "pages" + order = "ASCENDING" + }, + { + field_path = "year" + order = "ASCENDING" + }, + ] + index10 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "pages" + order = "ASCENDING" + }, + { + field_path = "rating" + order = "ASCENDING" + }, + { + field_path = "year" + order = "ASCENDING" + }, + ] + index11 = [ + { + field_path = "rating" + array_config = "CONTAINS" + }, + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "pages" + order = "ASCENDING" + }, + { + field_path = "rating" + order = "ASCENDING" + }, + ] } } diff --git a/Firestore/main.tf b/Firestore/main.tf index fcb5b3378bb..9b3ce4e2b0c 100644 --- a/Firestore/main.tf +++ b/Firestore/main.tf @@ -4,24 +4,46 @@ provider "google" { project = var.project_id } -resource "google_firestore_index" "default-db-index" { +resource "google_firestore_index" "default_db_index" { collection = "composite-index-test-collection" for_each = local.indexes dynamic "fields" { for_each = distinct(flatten([for k, v in local.indexes : [ for i in each.value : { - field_path = i.field_path - order = i.order + field_path = i.field_path + order = can(i.order) ? i.order : null + array_config = can(i.array_config) ? i.array_config : null }]])) content { - field_path = lookup(fields.value, "field_path", null) - order = lookup(fields.value, "order", null) + field_path = fields.value.field_path + order = fields.value.order + array_config = fields.value.array_config } } } -resource "google_firestore_index" "named-db-index" { +resource "google_firestore_index" "default_db_collection_group_index" { + collection = "composite-index-test-collection" + query_scope = "COLLECTION_GROUP" + + for_each = local.collection_group_indexes + dynamic "fields" { + for_each = distinct(flatten([for k, v in local.indexes : [ + for i in each.value : { + field_path = i.field_path + order = can(i.order) ? i.order : null + array_config = can(i.array_config) ? i.array_config : null + }]])) + content { + field_path = fields.value.field_path + order = fields.value.order + array_config = fields.value.array_config + } + } +} + +resource "google_firestore_index" "named_db_index" { collection = "composite-index-test-collection" database = "test-db" @@ -29,12 +51,35 @@ resource "google_firestore_index" "named-db-index" { dynamic "fields" { for_each = distinct(flatten([for k, v in local.indexes : [ for i in each.value : { - field_path = i.field_path - order = i.order + field_path = i.field_path + order = can(i.order) ? i.order : null + array_config = can(i.array_config) ? i.array_config : null + }]])) + content { + field_path = fields.value.field_path + order = fields.value.order + array_config = fields.value.array_config + } + } +} + +resource "google_firestore_index" "named_db_collection_group_index" { + collection = "composite-index-test-collection" + database = "test-db" + query_scope = "COLLECTION_GROUP" + + for_each = local.collection_group_indexes + dynamic "fields" { + for_each = distinct(flatten([for k, v in local.indexes : [ + for i in each.value : { + field_path = i.field_path + order = can(i.order) ? i.order : null + array_config = can(i.array_config) ? i.array_config : null }]])) content { - field_path = lookup(fields.value, "field_path", null) - order = lookup(fields.value, "order", null) + field_path = fields.value.field_path + order = fields.value.order + array_config = fields.value.array_config } } }