From 54d4d703b6caba1f6f8857aa766fb187e44897e4 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Mon, 16 Oct 2023 21:10:30 -0600 Subject: [PATCH] sum avg (#11650) --- Firestore/CHANGELOG.md | 3 + .../Integration/API/FIRAggregateTests.mm | 355 ++++------ .../Tests/Integration/API/FIRQueryTests.mm | 6 +- Firestore/Source/API/FIRAggregateField.mm | 4 +- .../Source/API/FIRAggregateQuery+Internal.h | 4 +- Firestore/Source/API/FIRAggregateQuery.mm | 6 +- .../API/FIRAggregateQuerySnapshot+Internal.h | 22 - .../Source/API/FIRAggregateQuerySnapshot.mm | 14 +- Firestore/Source/API/FIRQuery+Internal.h | 23 - Firestore/Source/API/FIRQuery.mm | 6 +- .../FirebaseFirestore}/FIRAggregateField.h | 6 +- .../FIRAggregateQuerySnapshot.h | 17 + .../Public/FirebaseFirestore/FIRQuery.h | 22 +- .../FirebaseFirestore/FirebaseFirestore.h | 1 + .../AggregationIntegrationTests.swift | 628 ++++++++---------- Firestore/core/src/api/query_core.cc | 4 +- Firestore/core/src/api/query_core.h | 4 +- .../core/src/remote/remote_objc_bridge.cc | 2 +- 18 files changed, 462 insertions(+), 665 deletions(-) rename Firestore/Source/{API => Public/FirebaseFirestore}/FIRAggregateField.h (94%) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 44dafdecb09..1832eabef6f 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,3 +1,6 @@ +# 10.17.0 +- [feature] Add support for sum and average aggregate queries. + # 10.16.0 - [fixed] Fixed an issue where Firestore's binary SwiftPM distribution would not link properly when building a target for testing. This issue affected diff --git a/Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm b/Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm index 99e73e1f6b1..c1e45c0f49d 100644 --- a/Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm @@ -14,19 +14,14 @@ * limitations under the License. */ +#import #import #import #import -// TODO(sum/avg) update these imports with public imports when sum/avg is public -#import "Firestore/Source/API/FIRAggregateField.h" -#import "Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h" -#import "Firestore/Source/API/FIRQuery+Internal.h" - -#include "Firestore/core/src/util/sanitizers.h" - #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h" +#import "Firestore/core/src/util/exception.h" @interface FIRAggregateTests : FSTIntegrationTestCase @end @@ -204,9 +199,6 @@ - (void)testAggregateFieldQueryNotEquals { } - (void)testCanRunAggregateQuery { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -234,39 +226,26 @@ - (void)testCanRunAggregateQuery { [self readSnapshotForAggregate:[testCollection aggregate:@[ [FIRAggregateField aggregateFieldForCount], [FIRAggregateField aggregateFieldForSumOfField:@"pages"], - [FIRAggregateField aggregateFieldForSumOfField:@"weight"], - [FIRAggregateField aggregateFieldForAverageOfField:@"pages"], - [FIRAggregateField aggregateFieldForAverageOfField:@"weight"] + [FIRAggregateField aggregateFieldForAverageOfField:@"pages"] ]]]; // Count - XCTAssertEqual([snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]], + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]], [NSNumber numberWithLong:2L]); XCTAssertEqual([snapshot count], [NSNumber numberWithLong:2L]); // Sum XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]], + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]], [NSNumber numberWithLong:150L], ); - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"weight"]] - doubleValue], - 99.6); // Average - XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"pages"]], - [NSNumber numberWithDouble:75.0]); - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"weight"]] - doubleValue], - 49.8); + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"pages"]], + [NSNumber numberWithDouble:75.0]); } - (void)testCanRunEmptyAggregateQuery { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -311,8 +290,6 @@ - (void)testCanRunEmptyAggregateQuery { // (TODO:b/283101111): Try thread sanitizer to see if timeout on Github Actions is gone. #if !defined(THREAD_SANITIZER) - (void)testAggregateFieldQuerySnapshotEquality { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -340,40 +317,33 @@ - (void)testAggregateFieldQuerySnapshotEquality { [self readSnapshotForAggregate:[testCollection aggregate:@[ [FIRAggregateField aggregateFieldForCount], [FIRAggregateField aggregateFieldForSumOfField:@"pages"], - [FIRAggregateField aggregateFieldForSumOfField:@"weight"], - [FIRAggregateField aggregateFieldForAverageOfField:@"pages"], - [FIRAggregateField aggregateFieldForAverageOfField:@"weight"] + [FIRAggregateField aggregateFieldForAverageOfField:@"pages"] ]]]; FIRAggregateQuerySnapshot* snapshot2 = [self readSnapshotForAggregate:[testCollection aggregate:@[ [FIRAggregateField aggregateFieldForCount], [FIRAggregateField aggregateFieldForSumOfField:@"pages"], - [FIRAggregateField aggregateFieldForSumOfField:@"weight"], - [FIRAggregateField aggregateFieldForAverageOfField:@"pages"], - [FIRAggregateField aggregateFieldForAverageOfField:@"weight"] + [FIRAggregateField aggregateFieldForAverageOfField:@"pages"] ]]]; // different aggregates FIRAggregateQuerySnapshot* snapshot3 = [self readSnapshotForAggregate:[testCollection aggregate:@[ [FIRAggregateField aggregateFieldForCount], - [FIRAggregateField aggregateFieldForSumOfField:@"pages"], [FIRAggregateField aggregateFieldForSumOfField:@"weight"], - [FIRAggregateField aggregateFieldForAverageOfField:@"pages"] + [FIRAggregateField aggregateFieldForAverageOfField:@"weight"] ]]]; // different data set - FIRAggregateQuerySnapshot* snapshot4 = [self - readSnapshotForAggregate:[[testCollection queryWhereField:@"pages" isGreaterThan:@50] - aggregate:@[ - [FIRAggregateField aggregateFieldForCount], - [FIRAggregateField aggregateFieldForSumOfField:@"pages"], - [FIRAggregateField aggregateFieldForSumOfField:@"weight"], - [FIRAggregateField aggregateFieldForAverageOfField:@"pages"], - [FIRAggregateField - aggregateFieldForAverageOfField:@"weight"] - ]]]; + FIRAggregateQuerySnapshot* snapshot4 = + [self readSnapshotForAggregate:[[testCollection queryWhereField:@"pages" isGreaterThan:@50] + aggregate:@[ + [FIRAggregateField aggregateFieldForCount], + [FIRAggregateField aggregateFieldForSumOfField:@"pages"], + [FIRAggregateField + aggregateFieldForAverageOfField:@"pages"] + ]]]; XCTAssertEqualObjects(snapshot1, snapshot2); XCTAssertNotEqualObjects(snapshot1, snapshot3); @@ -387,15 +357,8 @@ - (void)testAggregateFieldQuerySnapshotEquality { } #endif // #if !defined(THREAD_SANITIZER) -- (void)testAllowsAliasesLongerThan1500Bytes { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - - // The longest field name allowed is 1500. The alias chosen by the client is _. - // If the field name is - // 1500 bytes, the alias will be longer than 1500, which is the limit for aliases. This is to - // make sure the client - // can handle this corner case correctly. +- (void)testAggregateOnFieldNameWithMaxLength { + // The longest field name and alias allowed is 1500 bytes or 1499 characters. NSString* longField = [@"" stringByPaddingToLength:1499 withString:@"0123456789" startingAtIndex:0]; @@ -410,14 +373,11 @@ - (void)testAllowsAliasesLongerThan1500Bytes { // Sum XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:longField]], + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:longField]], [NSNumber numberWithLong:3], ); } - (void)testCanGetDuplicateAggregations { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -449,19 +409,16 @@ - (void)testCanGetDuplicateAggregations { ]]]; // Count - XCTAssertEqual([snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]], + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]], [NSNumber numberWithLong:2L]); // Sum XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]], + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]], [NSNumber numberWithLong:150L], ); } - (void)testTerminateDoesNotCrashWithFlyingAggregateQuery { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -503,19 +460,18 @@ - (void)testTerminateDoesNotCrashWithFlyingAggregateQuery { [self awaitExpectation:expectation]; // Count - XCTAssertEqual([result valueForAggregation:[FIRAggregateField aggregateFieldForCount]], + XCTAssertEqual([result valueForAggregateField:[FIRAggregateField aggregateFieldForCount]], [NSNumber numberWithLong:2L]); // Sum XCTAssertEqual( - [result valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]], + [result valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]], [NSNumber numberWithLong:150L], ); } - (void)testCanPerformMaxAggregations { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - + XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], + "Skip this test if running against production because it requires a composite index."); FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -550,28 +506,25 @@ - (void)testCanPerformMaxAggregations { ]]]; // Assert - XCTAssertEqual([snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]], + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]], [NSNumber numberWithLong:2L]); XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]], + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]], [NSNumber numberWithLong:150L], ); XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"weight"]] + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"weight"]] doubleValue], 99.6); - XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"pages"]], - [NSNumber numberWithDouble:75.0]); - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"weight"]] - doubleValue], - 49.8); + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"pages"]], + [NSNumber numberWithDouble:75.0]); + XCTAssertEqual([[snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"weight"]] + doubleValue], + 49.8); } - (void)testCannotPerformMoreThanMaxAggregations { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -622,8 +575,8 @@ - (void)testCannotPerformMoreThanMaxAggregations { } - (void)testCanRunAggregateCollectionGroupQuery { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); + XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], + "Skip this test if running against production because it requires a composite index."); NSString* collectionGroup = [NSString stringWithFormat:@"%@%@", @"b", @@ -648,19 +601,19 @@ - (void)testCanRunAggregateCollectionGroupQuery { [FIRAggregateField aggregateFieldForAverageOfField:@"x"] ]]]; // "cg-doc1", "cg-doc2", "cg-doc3", "cg-doc4", "cg-doc5", - XCTAssertEqual([snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]], + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]], [NSNumber numberWithLong:5L]); XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"x"]], + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"x"]], [NSNumber numberWithLong:10L]); XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"x"]], + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"x"]], [NSNumber numberWithDouble:2.0]); } - (void)testPerformsAggregationsWhenNaNExistsForSomeFieldValues { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); + XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], + "Skip this test if running against production because it requires a composite index."); FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @@ -703,27 +656,24 @@ - (void)testPerformsAggregationsWhenNaNExistsForSomeFieldValues { // Sum XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]], + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]], [NSNumber numberWithDouble:NAN]); XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]] + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]] longValue], 300L); // Average + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"rating"]], + [NSNumber numberWithDouble:NAN]); XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]], - [NSNumber numberWithDouble:NAN]); - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"year"]] + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"year"]] doubleValue], 2000.0); } - (void)testThrowsAnErrorWhenGettingTheResultOfAnUnrequestedAggregation { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -763,7 +713,7 @@ - (void)testThrowsAnErrorWhenGettingTheResultOfAnUnrequestedAggregation { @ try { - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"foo"]]; + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"foo"]]; XCTAssertTrue(false, "Exception expected"); } @catch (NSException* exception) { XCTAssertEqualObjects(exception.name, @"FIRInvalidArgumentException"); @@ -773,7 +723,7 @@ - (void)testThrowsAnErrorWhenGettingTheResultOfAnUnrequestedAggregation { @ try { - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"pages"]]; + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"pages"]]; XCTAssertTrue(false, "Exception expected"); } @catch (NSException* exception) { XCTAssertEqualObjects(exception.name, @"FIRInvalidArgumentException"); @@ -783,9 +733,6 @@ - (void)testThrowsAnErrorWhenGettingTheResultOfAnUnrequestedAggregation { } - (void)testPerformsAggregationWhenUsingInOperator { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -821,40 +768,30 @@ - (void)testPerformsAggregationWhenUsingInOperator { readSnapshotForAggregate:[[testCollection queryWhereField:@"rating" in:@[ @5, @3 ]] aggregate:@[ [FIRAggregateField aggregateFieldForSumOfField:@"rating"], - [FIRAggregateField aggregateFieldForSumOfField:@"pages"], [FIRAggregateField aggregateFieldForAverageOfField:@"rating"], - [FIRAggregateField aggregateFieldForAverageOfField:@"pages"], [FIRAggregateField aggregateFieldForCount] ]]]; // Count XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]] longValue], 2L); + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]] longValue], 2L); // Sum XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] longValue], 8L); - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]] - longValue], - 200L); // Average - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]] - doubleValue], - 4.0); - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"pages"]] - doubleValue], - 100.0); + XCTAssertEqual([[snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"rating"]] + doubleValue], + 4.0); } - (void)testPerformsAggregationWhenUsingArrayContainsAnyOperator { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); + XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], + "Skip this test if running against production because it requires a composite index."); FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @@ -900,32 +837,30 @@ - (void)testPerformsAggregationWhenUsingArrayContainsAnyOperator { // Count XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]] longValue], 2L); + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]] longValue], 2L); // Sum XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] longValue], 0L); XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]] + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]] longValue], 200L); // Average XCTAssertEqualObjects( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]], + [snapshot + valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]], [NSNull null]); XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"pages"]] - doubleValue], + [[snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"pages"]] doubleValue], 100.0); } - (void)testPerformsAggregationsOnNestedMapValues { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -942,44 +877,30 @@ - (void)testPerformsAggregationsOnNestedMapValues { FIRAggregateQuerySnapshot* snapshot = [self readSnapshotForAggregate:[testCollection aggregate:@[ [FIRAggregateField aggregateFieldForSumOfField:@"metadata.pages"], - [FIRAggregateField aggregateFieldForSumOfField:@"metadata.rating.user"], [FIRAggregateField aggregateFieldForAverageOfField:@"metadata.pages"], - [FIRAggregateField aggregateFieldForAverageOfField:@"metadata.rating.critic"], [FIRAggregateField aggregateFieldForCount] ]]]; // Count XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]] longValue], 2L); + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]] longValue], 2L); // Sum XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField - aggregateFieldForSumOfField:@"metadata.pages"]] longValue], - 150L); - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField - aggregateFieldForSumOfField:@"metadata.rating.user"]] + [[snapshot + valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"metadata.pages"]] longValue], - 9); + 150L); // Average XCTAssertEqual( - [[snapshot - valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"metadata.pages"]] + [[snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"metadata.pages"]] doubleValue], 75.0); - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField - aggregateFieldForAverageOfField:@"metadata.rating.critic"]] - doubleValue], - 3.0); } - (void)testPerformsSumThatOverflowsMaxLong { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -1000,16 +921,13 @@ - (void)testPerformsSumThatOverflowsMaxLong { // Sum XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] doubleValue], [[NSNumber numberWithLong:LLONG_MAX] doubleValue] + [[NSNumber numberWithLong:LLONG_MAX] doubleValue]); } - (void)testPerformsSumThatCanOverflowLongValuesDuringAccumulation { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -1028,15 +946,12 @@ - (void)testPerformsSumThatCanOverflowLongValuesDuringAccumulation { // Sum XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] longLongValue], [[NSNumber numberWithLong:LLONG_MAX - 100] longLongValue]); } - (void)testPerformsSumThatIsNegative { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -1064,15 +979,12 @@ - (void)testPerformsSumThatIsNegative { // Sum XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] longLongValue], [[NSNumber numberWithLong:-10101] longLongValue]); } - (void)testPerformsSumThatIsPositiveInfinity { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -1093,14 +1005,11 @@ - (void)testPerformsSumThatIsPositiveInfinity { // Sum XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]], + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]], [NSNumber numberWithDouble:INFINITY]); } - (void)testPerformsSumThatIsNegativeInfinity { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -1121,23 +1030,16 @@ - (void)testPerformsSumThatIsNegativeInfinity { // Sum XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]], + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]], [NSNumber numberWithDouble:-INFINITY]); } - (void)testPerformsSumThatIsValidButCouldOverflowDuringAggregation { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{@"rating" : [NSNumber numberWithDouble:DBL_MAX]}, @"b" : @{@"rating" : [NSNumber numberWithDouble:DBL_MAX]}, @"c" : @{@"rating" : [NSNumber numberWithDouble:-DBL_MAX]}, - @"d" : @{@"rating" : [NSNumber numberWithDouble:-DBL_MAX]}, - @"e" : @{@"rating" : [NSNumber numberWithDouble:DBL_MAX]}, - @"f" : @{@"rating" : [NSNumber numberWithDouble:-DBL_MAX]}, - @"g" : @{@"rating" : [NSNumber numberWithDouble:-DBL_MAX]}, - @"h" : @{@"rating" : [NSNumber numberWithDouble:DBL_MAX]} + @"d" : @{@"rating" : [NSNumber numberWithDouble:-DBL_MAX]} }]; FIRAggregateQuerySnapshot* snapshot = @@ -1146,20 +1048,19 @@ - (void)testPerformsSumThatIsValidButCouldOverflowDuringAggregation { aggregateFieldForSumOfField:@"rating"] ]]]; // Sum - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] - longLongValue], - [[NSNumber numberWithLong:0] longLongValue]); - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] - doubleValue], - [[NSNumber numberWithLong:0] doubleValue]); + long long ratingL = + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] + longLongValue]; + XCTAssertTrue(ratingL == [[NSNumber numberWithDouble:-INFINITY] longLongValue] || ratingL == 0 || + ratingL == [[NSNumber numberWithDouble:INFINITY] longLongValue]); + + double ratingD = + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] + doubleValue]; + XCTAssertTrue(ratingD == -INFINITY || ratingD == 0 || ratingD == INFINITY); } - (void)testPerformsSumOverResultSetOfZeroDocuments { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -1190,14 +1091,11 @@ - (void)testPerformsSumOverResultSetOfZeroDocuments { // Sum XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]], + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]], [NSNumber numberWithLong:0L]); } - (void)testPerformsSumOnlyOnNumericFields { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{@"rating" : [NSNumber numberWithLong:5]}, @"b" : @{@"rating" : [NSNumber numberWithLong:4]}, @@ -1213,19 +1111,16 @@ - (void)testPerformsSumOnlyOnNumericFields { // Count XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]] longValue], 4L); + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]] longValue], 4L); // Sum XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] longLongValue], [[NSNumber numberWithLong:10] longLongValue]); } - (void)testPerformsSumOfMinIEEE754 { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{@"rating" : [NSNumber numberWithDouble:__DBL_DENORM_MIN__]} }]; @@ -1237,15 +1132,12 @@ - (void)testPerformsSumOfMinIEEE754 { // Sum XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]] doubleValue], [[NSNumber numberWithDouble:__DBL_DENORM_MIN__] doubleValue]); } - (void)testPerformsAverageOfVariousNumericTypes { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"x" : @1, @@ -1306,17 +1198,14 @@ - (void)testPerformsAverageOfVariousNumericTypes { [self readSnapshotForAggregate:[testCollection aggregate:@[ testCase[@"agg"] ]]]; // Average - XCTAssertEqual([[snapshot valueForAggregation:testCase[@"agg"]] longValue], + XCTAssertEqual([[snapshot valueForAggregateField:testCase[@"agg"]] longValue], [testCase[@"expected"] longLongValue]); - XCTAssertEqualWithAccuracy([[snapshot valueForAggregation:testCase[@"agg"]] doubleValue], + XCTAssertEqualWithAccuracy([[snapshot valueForAggregateField:testCase[@"agg"]] doubleValue], [testCase[@"expected"] doubleValue], 0.00000000000001); } } - (void)testPerformsAverageCausingUnderflow { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{@"rating" : [NSNumber numberWithDouble:__DBL_DENORM_MIN__]}, @"b" : @{@"rating" : [NSNumber numberWithDouble:0]} @@ -1328,16 +1217,13 @@ - (void)testPerformsAverageCausingUnderflow { ]]]; // Average - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]] - doubleValue], - [[NSNumber numberWithDouble:0] doubleValue]); + XCTAssertEqual([[snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"rating"]] + doubleValue], + [[NSNumber numberWithDouble:0] doubleValue]); } - (void)testPerformsAverageOfMinIEEE754 { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{@"rating" : [NSNumber numberWithDouble:__DBL_DENORM_MIN__]} }]; @@ -1348,16 +1234,13 @@ - (void)testPerformsAverageOfMinIEEE754 { ]]]; // Average - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]] - doubleValue], - [[NSNumber numberWithDouble:__DBL_DENORM_MIN__] doubleValue]); + XCTAssertEqual([[snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"rating"]] + doubleValue], + [[NSNumber numberWithDouble:__DBL_DENORM_MIN__] doubleValue]); } - (void)testPerformsAverageOverflowIEEE754DuringAccumulation { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{@"rating" : [NSNumber numberWithDouble:DBL_MAX]}, @"b" : @{@"rating" : [NSNumber numberWithDouble:DBL_MAX]} @@ -1369,16 +1252,13 @@ - (void)testPerformsAverageOverflowIEEE754DuringAccumulation { ]]]; // Average - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]] - doubleValue], - [[NSNumber numberWithDouble:INFINITY] doubleValue]); + XCTAssertEqual([[snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"rating"]] + doubleValue], + [[NSNumber numberWithDouble:INFINITY] doubleValue]); } - (void)testPerformsAverageOverResultSetOfZeroDocuments { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{ @"author" : @"authorA", @@ -1408,15 +1288,12 @@ - (void)testPerformsAverageOverResultSetOfZeroDocuments { aggregateFieldForAverageOfField:@"pages"] ]]]; // Average - XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"pages"]], - [NSNull null]); + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"pages"]], + [NSNull null]); } - (void)testPerformsAverageOnlyOnNumericFields { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator"); - FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{ @"a" : @{@"rating" : [NSNumber numberWithLong:5]}, @"b" : @{@"rating" : [NSNumber numberWithLong:4]}, @@ -1432,13 +1309,13 @@ - (void)testPerformsAverageOnlyOnNumericFields { // Count XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]] longValue], 4L); + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]] longValue], 4L); // Average - XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]] - doubleValue], - [[NSNumber numberWithDouble:5] doubleValue]); + XCTAssertEqual([[snapshot valueForAggregateField:[FIRAggregateField + aggregateFieldForAverageOfField:@"rating"]] + doubleValue], + [[NSNumber numberWithDouble:5] doubleValue]); } @end diff --git a/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm b/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm index 7f6800cc327..bc7097b06ec 100644 --- a/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRQueryTests.mm @@ -1508,14 +1508,14 @@ - (void)testMultipleInequalityInAggregateQuery { [FIRAggregateField aggregateFieldForSumOfField:@"sort"], [FIRAggregateField aggregateFieldForAverageOfField:@"v"], ]]]; - XCTAssertEqual([snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]], + XCTAssertEqual([snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForCount]], [NSNumber numberWithLong:3L]); XCTAssertEqual( - [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"sort"]] + [[snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForSumOfField:@"sort"]] longValue], 6L); XCTAssertEqual( - [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"v"]], + [snapshot valueForAggregateField:[FIRAggregateField aggregateFieldForAverageOfField:@"v"]], [NSNumber numberWithDouble:1.0]); } diff --git a/Firestore/Source/API/FIRAggregateField.mm b/Firestore/Source/API/FIRAggregateField.mm index a163db652ba..9c6373ca9f1 100644 --- a/Firestore/Source/API/FIRAggregateField.mm +++ b/Firestore/Source/API/FIRAggregateField.mm @@ -16,11 +16,11 @@ #import "FIRAggregateField.h" -#include - #import "Firestore/Source/API/FIRAggregateField+Internal.h" #import "Firestore/Source/API/FIRFieldPath+Internal.h" +#import "Firestore/core/src/model/aggregate_field.h" + using firebase::firestore::model::AggregateField; NS_ASSUME_NONNULL_BEGIN diff --git a/Firestore/Source/API/FIRAggregateQuery+Internal.h b/Firestore/Source/API/FIRAggregateQuery+Internal.h index 0b24ab4fc46..88244159257 100644 --- a/Firestore/Source/API/FIRAggregateQuery+Internal.h +++ b/Firestore/Source/API/FIRAggregateQuery+Internal.h @@ -24,8 +24,8 @@ NS_ASSUME_NONNULL_BEGIN @interface FIRAggregateQuery (/* init */) - (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithQueryAndAggregations:(FIRQuery *)query - aggregations:(NSArray *)aggregations +- (instancetype)initWithQuery:(FIRQuery *)query + aggregateFields:(NSArray *)aggregations NS_DESIGNATED_INITIALIZER; @end diff --git a/Firestore/Source/API/FIRAggregateQuery.mm b/Firestore/Source/API/FIRAggregateQuery.mm index 013d0926c76..8975de79f14 100644 --- a/Firestore/Source/API/FIRAggregateQuery.mm +++ b/Firestore/Source/API/FIRAggregateQuery.mm @@ -37,13 +37,13 @@ @implementation FIRAggregateQuery { std::unique_ptr _aggregateQuery; } -- (instancetype)initWithQueryAndAggregations:(FIRQuery *)query - aggregations:(NSArray *)aggregations { +- (instancetype)initWithQuery:(FIRQuery *)query + aggregateFields:(NSArray *)aggregateFields { if (self = [super init]) { _query = query; std::vector _aggregateFields; - for (FIRAggregateField *field in aggregations) { + for (FIRAggregateField *field in aggregateFields) { _aggregateFields.push_back([field createInternalValue]); } diff --git a/Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h b/Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h index 539368c3aa7..55cc17d2842 100644 --- a/Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h +++ b/Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h @@ -35,26 +35,4 @@ NS_ASSUME_NONNULL_BEGIN @end -// TODO(sum/avg) move the contents of this FuturePublicApi category to -// ../Public/FirebaseFirestore/FIRAggregateQuerySnapshot.h -@interface FIRAggregateQuerySnapshot (FuturePublicApi) - -/** - * Gets the aggregation result for the specified aggregation without loss of precision. No coercion - * of data types or values is performed. - * - * See the `AggregateField` class for the expected aggregration result values and types. Numeric - * aggregation results will be boxed in an `NSNumber`. - * - * @param aggregation An instance of `AggregateField` that specifies which aggregation result to - * return. - * @return Returns the aggregation result from the server without loss of precision. - * @warning Throws an `InvalidArgument` exception if the aggregation was not requested in the - * `AggregateQuery`. - * @see `AggregateField` - */ -- (nullable id)valueForAggregation:(FIRAggregateField *)aggregation NS_SWIFT_NAME(get(_:)); - -@end - NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/API/FIRAggregateQuerySnapshot.mm b/Firestore/Source/API/FIRAggregateQuerySnapshot.mm index bbfd811c9f3..393d04843ef 100644 --- a/Firestore/Source/API/FIRAggregateQuerySnapshot.mm +++ b/Firestore/Source/API/FIRAggregateQuerySnapshot.mm @@ -71,25 +71,25 @@ - (NSUInteger)hash { #pragma mark - Public Methods - (NSNumber *)count { - return (NSNumber *)[self valueForAggregation:[FIRAggregateField aggregateFieldForCount]]; + return (NSNumber *)[self valueForAggregateField:[FIRAggregateField aggregateFieldForCount]]; } - (FIRAggregateQuery *)query { return _query; } -- (nullable id)valueForAggregation:(FIRAggregateField *)aggregation { +- (id)valueForAggregateField:(FIRAggregateField *)aggregateField { FIRServerTimestampBehavior serverTimestampBehavior = FIRServerTimestampBehaviorNone; - AggregateAlias alias = [aggregation createAlias]; + AggregateAlias alias = [aggregateField createAlias]; absl::optional fieldValue = _result.Get(alias.StringValue()); if (!fieldValue) { std::string path{""}; - if (aggregation.fieldPath) { - path = [aggregation.fieldPath internalValue].CanonicalString(); + if (aggregateField.fieldPath) { + path = [aggregateField.fieldPath internalValue].CanonicalString(); } - ThrowInvalidArgument("'%s(%s)' was not requested in the aggregation query.", [aggregation name], - path); + ThrowInvalidArgument("'%s(%s)' was not requested in the aggregation query.", + [aggregateField name], path); } FSTUserDataWriter *dataWriter = [[FSTUserDataWriter alloc] initWithFirestore:_query.query.firestore.wrapped diff --git a/Firestore/Source/API/FIRQuery+Internal.h b/Firestore/Source/API/FIRQuery+Internal.h index 10de3ffb042..a3e2f9cb6ae 100644 --- a/Firestore/Source/API/FIRQuery+Internal.h +++ b/Firestore/Source/API/FIRQuery+Internal.h @@ -47,27 +47,4 @@ NS_ASSUME_NONNULL_BEGIN @end -// TODO(sum/avg) move the contents of this FuturePublicApi category to -// ../Public/FirebaseFirestore/FIRAggregateQuerySnapshot.h -@interface FIRQuery (FuturePublicApi) - -/** - * Creates and returns a new `AggregateQuery` that aggregates the documents in the result set - * of this query, without actually downloading the documents. - * - * Using an `AggregateQuery` to perform aggregations is efficient because only the final aggregation - * values, not the documents' data, is downloaded. The query can even aggregate the documents if the - * result set would be prohibitively large to download entirely (e.g. thousands of documents). - * - * @param aggregations Specifies the aggregation operations to perform on the result set of this - * query. - * - * @return An `AggregateQuery` encapsulating this `Query` and `AggregateField`s, which can be used - * to query the server for the aggregation results. - */ -- (FIRAggregateQuery *)aggregate:(NSArray *)aggregations - NS_SWIFT_NAME(aggregate(_:)); - -@end - NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/API/FIRQuery.mm b/Firestore/Source/API/FIRQuery.mm index 5f15c4afe39..98518183049 100644 --- a/Firestore/Source/API/FIRQuery.mm +++ b/Firestore/Source/API/FIRQuery.mm @@ -485,11 +485,11 @@ - (FIRQuery *)queryEndingAtValues:(NSArray *)fieldValues { - (FIRAggregateQuery *)count { FIRAggregateField *countAF = [FIRAggregateField aggregateFieldForCount]; - return [[FIRAggregateQuery alloc] initWithQueryAndAggregations:self aggregations:@[ countAF ]]; + return [[FIRAggregateQuery alloc] initWithQuery:self aggregateFields:@[ countAF ]]; } -- (FIRAggregateQuery *)aggregate:(NSArray *)aggregations { - return [[FIRAggregateQuery alloc] initWithQueryAndAggregations:self aggregations:aggregations]; +- (FIRAggregateQuery *)aggregate:(NSArray *)aggregateFields { + return [[FIRAggregateQuery alloc] initWithQuery:self aggregateFields:aggregateFields]; } #pragma mark - Private Methods diff --git a/Firestore/Source/API/FIRAggregateField.h b/Firestore/Source/Public/FirebaseFirestore/FIRAggregateField.h similarity index 94% rename from Firestore/Source/API/FIRAggregateField.h rename to Firestore/Source/Public/FirebaseFirestore/FIRAggregateField.h index 705b3bf2345..be7826489a3 100644 --- a/Firestore/Source/API/FIRAggregateField.h +++ b/Firestore/Source/Public/FirebaseFirestore/FIRAggregateField.h @@ -20,8 +20,6 @@ NS_ASSUME_NONNULL_BEGIN @class FIRFieldPath; -// TODO(sum/avg) move this entire file to ../Public/FirebaseFirestore when the API can be public - /** * Represents an aggregation that can be performed by Firestore. */ @@ -84,7 +82,7 @@ NS_SWIFT_NAME(AggregateField) * Create an `AggregateField` object that can be used to compute the average of * a specified field over a range of documents in the result set of a query. * - * The result of an average operation will always be a 64-bit integer value, a double, or NaN. + * The result of an average operation will always be a double or NaN. * * - Averaging over zero documents or fields will result in a double value representing NaN. * - Averaging over NaN will result in a double value representing NaN. @@ -99,7 +97,7 @@ NS_SWIFT_NAME(AggregateField) * Create an `AggregateField` object that can be used to compute the average of * a specified field over a range of documents in the result set of a query. * - * The result of an average operation will always be a 64-bit integer value, a double, or NaN. + * The result of an average operation will always be a double or NaN. * * - Averaging over zero documents or fields will result in a double value representing NaN. * - Averaging over NaN will result in a double value representing NaN. diff --git a/Firestore/Source/Public/FirebaseFirestore/FIRAggregateQuerySnapshot.h b/Firestore/Source/Public/FirebaseFirestore/FIRAggregateQuerySnapshot.h index 8d41ba7c687..9a558bebed2 100644 --- a/Firestore/Source/Public/FirebaseFirestore/FIRAggregateQuerySnapshot.h +++ b/Firestore/Source/Public/FirebaseFirestore/FIRAggregateQuerySnapshot.h @@ -19,6 +19,7 @@ NS_ASSUME_NONNULL_BEGIN @class FIRAggregateQuery; +@class FIRAggregateField; /** * The results of executing an `AggregateQuery`. @@ -36,6 +37,22 @@ NS_SWIFT_NAME(AggregateQuerySnapshot) /** The number of documents in the result set of the underlying query. */ @property(nonatomic, readonly) NSNumber* count; +/** + * Gets the aggregate result for the specified aggregate field without loss of precision. No + * coercion of data types or values is performed. + * + * See the `AggregateField` class for the expected aggregate result values and types. Numeric + * aggregate results will be boxed in an `NSNumber`. + * + * @param aggregateField An instance of `AggregateField` that specifies which aggregate result to + * return. + * @return Returns the aggregate result from the server without loss of precision. + * @warning Throws an `InvalidArgument` exception if the aggregate field was not requested in the + * `AggregateQuery`. + * @see `AggregateField` + */ +- (id)valueForAggregateField:(FIRAggregateField*)aggregateField NS_SWIFT_NAME(get(_:)); + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Public/FirebaseFirestore/FIRQuery.h b/Firestore/Source/Public/FirebaseFirestore/FIRQuery.h index 0c3ca3c5704..843dff16090 100644 --- a/Firestore/Source/Public/FirebaseFirestore/FIRQuery.h +++ b/Firestore/Source/Public/FirebaseFirestore/FIRQuery.h @@ -20,6 +20,7 @@ #import "FIRListenerRegistration.h" @class FIRAggregateQuery; +@class FIRAggregateField; @class FIRFieldPath; @class FIRFirestore; @class FIRFilter; @@ -559,11 +560,28 @@ NS_SWIFT_NAME(Query) * the documents. * * Using this `AggregateQuery` to count the documents is efficient because only the final count, - * not the documents' data, is downloaded. The query can even count the documents if the result - * set would be prohibitively large to download entirely (e.g. thousands of documents). + * not the documents' data, is downloaded. This allows for counting document collections that would + * otherwise be too large to download (e.g. containing thousands of documents). */ @property(nonatomic, readonly) FIRAggregateQuery *count; +/** + * Creates and returns a new `AggregateQuery` that aggregates the documents in the result set + * of this query, without actually downloading the documents. + * + * Using an `AggregateQuery` to perform aggregations is efficient because only the final aggregation + * values, not the documents' data, is downloaded. This allows for aggregating document collections + * that would otherwise be too large to download (e.g. containing thousands of documents). + * + * @param aggregateFields Specifies the aggregate operations to perform on the result set of this + * query. + * + * @return An `AggregateQuery` encapsulating this `Query` and `AggregateField`s, which can be used + * to query the server for the aggregation results. + */ +- (FIRAggregateQuery *)aggregate:(NSArray *)aggregateFields + NS_SWIFT_NAME(aggregate(_:)); + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Public/FirebaseFirestore/FirebaseFirestore.h b/Firestore/Source/Public/FirebaseFirestore/FirebaseFirestore.h index 03ce435e4be..6746aa489e8 100644 --- a/Firestore/Source/Public/FirebaseFirestore/FirebaseFirestore.h +++ b/Firestore/Source/Public/FirebaseFirestore/FirebaseFirestore.h @@ -14,6 +14,7 @@ * limitations under the License. */ +#import "FIRAggregateField.h" #import "FIRAggregateQuery.h" #import "FIRAggregateQuerySnapshot.h" #import "FIRAggregateSource.h" diff --git a/Firestore/Swift/Tests/Integration/AggregationIntegrationTests.swift b/Firestore/Swift/Tests/Integration/AggregationIntegrationTests.swift index 2155a966df4..385ec492e1c 100644 --- a/Firestore/Swift/Tests/Integration/AggregationIntegrationTests.swift +++ b/Firestore/Swift/Tests/Integration/AggregationIntegrationTests.swift @@ -18,386 +18,314 @@ import FirebaseFirestore import FirebaseFirestoreSwift import Foundation -// TODO(sum/avg) remove `sumAvgIsPublic` from the directive below to enable these tests when sum/avg -// is public -#if sumAvgIsPublic && swift(>=5.5.2) - @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) - - class AggregationIntegrationTests: FSTIntegrationTestCase { - func testCount() async throws { - let collection = collectionRef() - try await collection.addDocument(data: [:]) - let snapshot = try await collection.count.getAggregation(source: .server) - XCTAssertEqual(snapshot.count, 1) - } +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +class AggregationIntegrationTests: FSTIntegrationTestCase { + func testCount() async throws { + let collection = collectionRef() + try await collection.addDocument(data: [:]) + let snapshot = try await collection.count.getAggregation(source: .server) + XCTAssertEqual(snapshot.count, 1) + } - func testCanRunAggregateQuery() async throws { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - try XCTSkipIf( - !FSTIntegrationTestCase.isRunningAgainstEmulator(), - "only tested against emulator" - ) - - let collection = collectionRef() - try await collection.addDocument(data: ["author": "authorA", - "title": "titleA", - "pages": 100, - "height": 24.5, - "weight": 24.1, - "foo": 1, - "bar": 2, - "baz": 3]) - try await collection.addDocument(data: ["author": "authorB", - "title": "titleB", - "pages": 50, - "height": 25.5, - "weight": 75.5, - "foo": 1, - "bar": 2, - "baz": 3]) + func testCanRunAggregateQuery() async throws { + let collection = collectionRef() + try await collection.addDocument(data: ["author": "authorA", + "title": "titleA", + "pages": 100, + "height": 24.5, + "weight": 24.1, + "foo": 1, + "bar": 2, + "baz": 3]) + try await collection.addDocument(data: ["author": "authorB", + "title": "titleB", + "pages": 50, + "height": 25.5, + "weight": 75.5, + "foo": 1, + "bar": 2, + "baz": 3]) + + let snapshot = try await collection.aggregate([ + AggregateField.count(), + AggregateField.sum("pages"), + AggregateField.average("pages"), + ]).getAggregation(source: .server) + + // Count + XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 2) + + // Sum + XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 150) + XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? Double, 150) + XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? Int64, 150) + + // Average + XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNumber, 75.0) + XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? Double, 75.0) + XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? Int64, 75) + } + func testCannotPerformMoreThanMaxAggregations() async throws { + let collection = collectionRef() + try await collection.addDocument(data: ["author": "authorA", + "title": "titleA", + "pages": 100, + "height": 24.5, + "weight": 24.1, + "foo": 1, + "bar": 2, + "baz": 3]) + + // Max is 5, we're attempting 6. I also like to live dangerously. + do { let snapshot = try await collection.aggregate([ AggregateField.count(), AggregateField.sum("pages"), AggregateField.sum("weight"), AggregateField.average("pages"), AggregateField.average("weight"), + AggregateField.average("foo"), ]).getAggregation(source: .server) - - // Count - XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 2) - - // Sum - XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 150) - XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? Double, 150) - XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? Int64, 150) - XCTAssertEqual(snapshot.get(AggregateField.sum("weight")) as? NSNumber, 99.6) - XCTAssertEqual(snapshot.get(AggregateField.sum("weight")) as? Double, 99.6) - - // Average - XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNumber, 75.0) - XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? Double, 75.0) - XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? Int64, 75) - XCTAssertEqual(snapshot.get(AggregateField.average("weight")) as? NSNumber, 49.8) - XCTAssertEqual(snapshot.get(AggregateField.average("weight")) as? Double, 49.8) - } - - func testCannotPerformMoreThanMaxAggregations() async throws { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - try XCTSkipIf( - !FSTIntegrationTestCase.isRunningAgainstEmulator(), - "only tested against emulator" - ) - - let collection = collectionRef() - try await collection.addDocument(data: ["author": "authorA", - "title": "titleA", - "pages": 100, - "height": 24.5, - "weight": 24.1, - "foo": 1, - "bar": 2, - "baz": 3]) - - // Max is 5, we're attempting 6. I also like to live dangerously. - do { - let snapshot = try await collection.aggregate([ - AggregateField.count(), - AggregateField.sum("pages"), - AggregateField.sum("weight"), - AggregateField.average("pages"), - AggregateField.average("weight"), - AggregateField.average("foo"), - ]).getAggregation(source: .server) - XCTFail("Error expected.") - } catch let error as NSError { - XCTAssertNotNil(error) - XCTAssertTrue(error.localizedDescription.contains("maximum number of aggregations")) - } - } - - func testPerformsAggregationsWhenNaNExistsForSomeFieldValues() async throws { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - try XCTSkipIf( - !FSTIntegrationTestCase.isRunningAgainstEmulator(), - "only tested against emulator" - ) - - let collection = collectionRef() - try await collection.addDocument(data: ["author": "authorA", - "title": "titleA", - "pages": 100, - "year": 1980, - "rating": 4]) - try await collection.addDocument(data: ["author": "authorB", - "title": "titleB", - "pages": 50, - "year": 2020, - "rating": Double.nan]) - - let snapshot = try await collection.aggregate([ - AggregateField.sum("pages"), - AggregateField.sum("rating"), - AggregateField.average("pages"), - AggregateField.average("rating"), - ]).getAggregation(source: .server) - - // Sum - XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 150) - XCTAssertTrue((snapshot.get(AggregateField.sum("rating")) as? Double)?.isNaN ?? false) - - // Average - XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNumber, 75.0) - XCTAssertTrue((snapshot.get(AggregateField.average("rating")) as? Double)?.isNaN ?? false) - } - - func testThrowsAnErrorWhenGettingTheResultOfAnUnrequestedAggregation() async throws { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - try XCTSkipIf( - !FSTIntegrationTestCase.isRunningAgainstEmulator(), - "only tested against emulator" - ) - - let collection = collectionRef() - try await collection.addDocument(data: [:]) - - let snapshot = try await collection.aggregate([AggregateField.average("foo")]) - .getAggregation(source: .server) - - XCTAssertTrue(FSTNSExceptionUtil.testForException({ - snapshot.count - }, reasonContains: "'count()' was not requested in the aggregation query")) - - XCTAssertTrue(FSTNSExceptionUtil.testForException({ - snapshot.get(AggregateField.sum("foo")) - }, reasonContains: "'sum(foo)' was not requested in the aggregation query")) - - XCTAssertTrue(FSTNSExceptionUtil.testForException({ - snapshot.get(AggregateField.average("bar")) - }, reasonContains: "'avg(bar)' was not requested in the aggregation query")) - } - - func testPerformsAggregationsOnNestedMapValues() async throws { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - try XCTSkipIf( - !FSTIntegrationTestCase.isRunningAgainstEmulator(), - "only tested against emulator" - ) - - let collection = collectionRef() - try await collection.addDocument(data: ["metadata": [ - "pages": 100, - "rating": [ - "critic": 2, - "user": 5, - ], - ]]) - try await collection.addDocument(data: ["metadata": [ - "pages": 50, - "rating": [ - "critic": 4, - "user": 4, - ], - ]]) - - let snapshot = try await collection.aggregate([ - AggregateField.count(), - AggregateField.sum("metadata.pages"), - AggregateField.sum(FieldPath(["metadata", "rating", "user"])), - AggregateField.average("metadata.pages"), - AggregateField.average(FieldPath(["metadata", "rating", "critic"])), - ]).getAggregation(source: .server) - - // Count - XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 2) - - // Sum - XCTAssertEqual( - snapshot.get(AggregateField.sum(FieldPath(["metadata", "pages"]))) as? NSNumber, - 150 - ) - XCTAssertEqual(snapshot.get(AggregateField.sum("metadata.pages")) as? NSNumber, 150) - XCTAssertEqual(snapshot.get(AggregateField.sum("metadata.rating.user")) as? NSNumber, 9) - - // Average - XCTAssertEqual( - snapshot.get(AggregateField.average(FieldPath(["metadata", "pages"]))) as? Double, - 75.0 - ) - XCTAssertEqual( - snapshot.get(AggregateField.average("metadata.rating.critic")) as? Double, - 3.0 - ) + XCTFail("Error expected.") + } catch let error as NSError { + XCTAssertNotNil(error) + XCTAssertTrue(error.localizedDescription.contains("maximum number of aggregations")) } + } - func testSumOverflow() async throws { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - try XCTSkipIf( - !FSTIntegrationTestCase.isRunningAgainstEmulator(), - "only tested against emulator" - ) - - let collection = collectionRef() - try await collection.addDocument(data: [ - "longOverflow": Int64.max, - "accumulationOverflow": Int64.max, - "positiveInfinity": Double.greatestFiniteMagnitude, - "negativeInfinity": -Double.greatestFiniteMagnitude, - ]) - try await collection.addDocument(data: [ - "longOverflow": Int64.max, - "accumulationOverflow": 1, - "positiveInfinity": Double.greatestFiniteMagnitude, - "negativeInfinity": -Double.greatestFiniteMagnitude, - ]) - try await collection.addDocument(data: [ - "longOverflow": Int64.max, - "accumulationOverflow": -101, - "positiveInfinity": Double.greatestFiniteMagnitude, - "negativeInfinity": -Double.greatestFiniteMagnitude, - ]) + func testPerformsAggregationsWhenNaNExistsForSomeFieldValues() async throws { + try XCTSkipIf(!FSTIntegrationTestCase.isRunningAgainstEmulator(), + "Skip this test if running against production because it requires a composite index.") + + let collection = collectionRef() + try await collection.addDocument(data: ["author": "authorA", + "title": "titleA", + "pages": 100, + "year": 1980, + "rating": 4]) + try await collection.addDocument(data: ["author": "authorB", + "title": "titleB", + "pages": 50, + "year": 2020, + "rating": Double.nan]) + + let snapshot = try await collection.aggregate([ + AggregateField.sum("pages"), + AggregateField.sum("rating"), + AggregateField.average("pages"), + AggregateField.average("rating"), + ]).getAggregation(source: .server) + + // Sum + XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 150) + XCTAssertTrue((snapshot.get(AggregateField.sum("rating")) as? Double)?.isNaN ?? false) + + // Average + XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNumber, 75.0) + XCTAssertTrue((snapshot.get(AggregateField.average("rating")) as? Double)?.isNaN ?? false) + } - let snapshot = try await collection.aggregate([ - AggregateField.sum("longOverflow"), - AggregateField.sum("accumulationOverflow"), - AggregateField.sum("positiveInfinity"), - AggregateField.sum("negativeInfinity"), - ]).getAggregation(source: .server) + func testThrowsAnErrorWhenGettingTheResultOfAnUnrequestedAggregation() async throws { + let collection = collectionRef() + try await collection.addDocument(data: [:]) - // Sum - XCTAssertEqual( - snapshot.get(AggregateField.sum("longOverflow")) as? Double, - Double(Int64.max) + Double(Int64.max) + Double(Int64.max) - ) - XCTAssertEqual( - snapshot.get(AggregateField.sum("accumulationOverflow")) as? Int64, - Int64.max - 100 - ) - XCTAssertEqual( - snapshot.get(AggregateField.sum("positiveInfinity")) as? Double, - Double.infinity - ) - XCTAssertEqual( - snapshot.get(AggregateField.sum("negativeInfinity")) as? Double, - -Double.infinity - ) - } + let snapshot = try await collection.aggregate([AggregateField.average("foo")]) + .getAggregation(source: .server) - func testAverageOverflow() async throws { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - try XCTSkipIf( - !FSTIntegrationTestCase.isRunningAgainstEmulator(), - "only tested against emulator" - ) - - let collection = collectionRef() - try await collection.addDocument(data: [ - "longOverflow": Int64.max, - "doubleOverflow": Double.greatestFiniteMagnitude, - "negativeInfinity": -Double.greatestFiniteMagnitude, - ]) - try await collection.addDocument(data: [ - "longOverflow": Int64.max, - "doubleOverflow": Double.greatestFiniteMagnitude, - "negativeInfinity": -Double.greatestFiniteMagnitude, - ]) - try await collection.addDocument(data: [ - "longOverflow": Int64.max, - "doubleOverflow": Double.greatestFiniteMagnitude, - "negativeInfinity": -Double.greatestFiniteMagnitude, - ]) + XCTAssertTrue(FSTNSExceptionUtil.testForException({ + snapshot.count + }, reasonContains: "'count()' was not requested in the aggregation query")) - let snapshot = try await collection.aggregate([ - AggregateField.average("longOverflow"), - AggregateField.average("doubleOverflow"), - AggregateField.average("negativeInfinity"), - ]).getAggregation(source: .server) + XCTAssertTrue(FSTNSExceptionUtil.testForException({ + snapshot.get(AggregateField.sum("foo")) + }, reasonContains: "'sum(foo)' was not requested in the aggregation query")) - // Average - XCTAssertEqual( - snapshot.get(AggregateField.average("longOverflow")) as? Double, - Double(Int64.max) - ) - XCTAssertEqual( - snapshot.get(AggregateField.average("doubleOverflow")) as? Double, - Double.infinity - ) - XCTAssertEqual( - snapshot.get(AggregateField.average("negativeInfinity")) as? Double, - -Double.infinity - ) - } + XCTAssertTrue(FSTNSExceptionUtil.testForException({ + snapshot.get(AggregateField.average("bar")) + }, reasonContains: "'avg(bar)' was not requested in the aggregation query")) + } - func testAverageUnderflow() async throws { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - try XCTSkipIf( - !FSTIntegrationTestCase.isRunningAgainstEmulator(), - "only tested against emulator" - ) + func testPerformsAggregationsOnNestedMapValues() async throws { + let collection = collectionRef() + try await collection.addDocument(data: ["metadata": [ + "pages": 100, + "rating": [ + "critic": 2, + "user": 5, + ], + ]]) + try await collection.addDocument(data: ["metadata": [ + "pages": 50, + "rating": [ + "critic": 4, + "user": 4, + ], + ]]) + + let snapshot = try await collection.aggregate([ + AggregateField.count(), + AggregateField.sum("metadata.pages"), + AggregateField.average("metadata.pages"), + ]).getAggregation(source: .server) + + // Count + XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 2) + + // Sum + XCTAssertEqual( + snapshot.get(AggregateField.sum(FieldPath(["metadata", "pages"]))) as? NSNumber, + 150 + ) + XCTAssertEqual(snapshot.get(AggregateField.sum("metadata.pages")) as? NSNumber, 150) + + // Average + XCTAssertEqual( + snapshot.get(AggregateField.average(FieldPath(["metadata", "pages"]))) as? Double, + 75.0 + ) + } - let collection = collectionRef() - try await collection.addDocument(data: ["underflowSmall": Double.leastNonzeroMagnitude]) - try await collection.addDocument(data: ["underflowSmall": 0]) + func testSumOverflow() async throws { + try XCTSkipIf(!FSTIntegrationTestCase.isRunningAgainstEmulator(), + "Skip this test if running against production because it requires a composite index.") + + let collection = collectionRef() + try await collection.addDocument(data: [ + "longOverflow": Int64.max, + "accumulationOverflow": Int64.max, + "positiveInfinity": Double.greatestFiniteMagnitude, + "negativeInfinity": -Double.greatestFiniteMagnitude, + ]) + try await collection.addDocument(data: [ + "longOverflow": Int64.max, + "accumulationOverflow": 1, + "positiveInfinity": Double.greatestFiniteMagnitude, + "negativeInfinity": -Double.greatestFiniteMagnitude, + ]) + try await collection.addDocument(data: [ + "longOverflow": Int64.max, + "accumulationOverflow": -101, + "positiveInfinity": Double.greatestFiniteMagnitude, + "negativeInfinity": -Double.greatestFiniteMagnitude, + ]) + + let snapshot = try await collection.aggregate([ + AggregateField.sum("longOverflow"), + AggregateField.sum("accumulationOverflow"), + AggregateField.sum("positiveInfinity"), + AggregateField.sum("negativeInfinity"), + ]).getAggregation(source: .server) + + // Sum + XCTAssertEqual( + snapshot.get(AggregateField.sum("longOverflow")) as? Double, + Double(Int64.max) + Double(Int64.max) + Double(Int64.max) + ) + XCTAssertEqual( + snapshot.get(AggregateField.sum("accumulationOverflow")) as? Int64, + Int64.max - 100 + ) + XCTAssertEqual( + snapshot.get(AggregateField.sum("positiveInfinity")) as? Double, + Double.infinity + ) + XCTAssertEqual( + snapshot.get(AggregateField.sum("negativeInfinity")) as? Double, + -Double.infinity + ) + } - let snapshot = try await collection.aggregate([AggregateField.average("underflowSmall")]) - .getAggregation(source: .server) + func testAverageOverflow() async throws { + try XCTSkipIf(!FSTIntegrationTestCase.isRunningAgainstEmulator(), + "Skip this test if running against production because it requires a composite index.") + + let collection = collectionRef() + try await collection.addDocument(data: [ + "longOverflow": Int64.max, + "doubleOverflow": Double.greatestFiniteMagnitude, + "negativeInfinity": -Double.greatestFiniteMagnitude, + ]) + try await collection.addDocument(data: [ + "longOverflow": Int64.max, + "doubleOverflow": Double.greatestFiniteMagnitude, + "negativeInfinity": -Double.greatestFiniteMagnitude, + ]) + try await collection.addDocument(data: [ + "longOverflow": Int64.max, + "doubleOverflow": Double.greatestFiniteMagnitude, + "negativeInfinity": -Double.greatestFiniteMagnitude, + ]) + + let snapshot = try await collection.aggregate([ + AggregateField.average("longOverflow"), + AggregateField.average("doubleOverflow"), + AggregateField.average("negativeInfinity"), + ]).getAggregation(source: .server) + + // Average + XCTAssertEqual( + snapshot.get(AggregateField.average("longOverflow")) as? Double, + Double(Int64.max) + ) + XCTAssertEqual( + snapshot.get(AggregateField.average("doubleOverflow")) as? Double, + Double.infinity + ) + XCTAssertEqual( + snapshot.get(AggregateField.average("negativeInfinity")) as? Double, + -Double.infinity + ) + } - // Average - XCTAssertEqual(snapshot.get(AggregateField.average("underflowSmall")) as? Double, 0.0) - } + func testAverageUnderflow() async throws { + let collection = collectionRef() + try await collection.addDocument(data: ["underflowSmall": Double.leastNonzeroMagnitude]) + try await collection.addDocument(data: ["underflowSmall": 0]) - func testPerformsAggregateOverResultSetOfZeroDocuments() async throws { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - try XCTSkipIf( - !FSTIntegrationTestCase.isRunningAgainstEmulator(), - "only tested against emulator" - ) + let snapshot = try await collection.aggregate([AggregateField.average("underflowSmall")]) + .getAggregation(source: .server) - let collection = collectionRef() - try await collection.addDocument(data: ["pages": 100]) - try await collection.addDocument(data: ["pages": 50]) + // Average + XCTAssertEqual(snapshot.get(AggregateField.average("underflowSmall")) as? Double, 0.0) + } - let snapshot = try await collection.whereField("pages", isGreaterThan: 200) - .aggregate([AggregateField.count(), AggregateField.sum("pages"), - AggregateField.average("pages")]).getAggregation(source: .server) + func testPerformsAggregateOverResultSetOfZeroDocuments() async throws { + let collection = collectionRef() + try await collection.addDocument(data: ["pages": 100]) + try await collection.addDocument(data: ["pages": 50]) - // Count - XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 0) + let snapshot = try await collection.whereField("pages", isGreaterThan: 200) + .aggregate([AggregateField.count(), AggregateField.sum("pages"), + AggregateField.average("pages")]).getAggregation(source: .server) - // Sum - XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 0) + // Count + XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 0) - // Average - // TODO: (sum/avg) this design is bad, will require and API update - XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNull, NSNull()) - } + // Sum + XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 0) - func testPerformsAggregateOverResultSetOfZeroFields() async throws { - // TODO(sum/avg) remove the check below when sum and avg are supported in production - try XCTSkipIf( - !FSTIntegrationTestCase.isRunningAgainstEmulator(), - "only tested against emulator" - ) + // Average + XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNull, NSNull()) + } - let collection = collectionRef() - try await collection.addDocument(data: ["pages": 100]) - try await collection.addDocument(data: ["pages": 50]) + func testPerformsAggregateOverResultSetOfZeroFields() async throws { + let collection = collectionRef() + try await collection.addDocument(data: ["pages": 100]) + try await collection.addDocument(data: ["pages": 50]) - let snapshot = try await collection - .aggregate([AggregateField.count(), AggregateField.sum("notInMyDocs"), - AggregateField.average("notInMyDocs")]).getAggregation(source: .server) + let snapshot = try await collection + .aggregate([AggregateField.count(), AggregateField.sum("notInMyDocs"), + AggregateField.average("notInMyDocs")]).getAggregation(source: .server) - // Count - 0 because aggregation is performed on documents matching the query AND documents - // that have all aggregated fields - XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 0) + // Count - 0 because aggregation is performed on documents matching the query AND documents + // that have all aggregated fields + XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 0) - // Sum - XCTAssertEqual(snapshot.get(AggregateField.sum("notInMyDocs")) as? NSNumber, 0) + // Sum + XCTAssertEqual(snapshot.get(AggregateField.sum("notInMyDocs")) as? NSNumber, 0) - // Average - // TODO: (sum/avg) this design is bad, will require and API update - XCTAssertEqual(snapshot.get(AggregateField.average("notInMyDocs")) as? NSNull, NSNull()) - } + // Average + XCTAssertEqual(snapshot.get(AggregateField.average("notInMyDocs")) as? NSNull, NSNull()) } - -#endif +} diff --git a/Firestore/core/src/api/query_core.cc b/Firestore/core/src/api/query_core.cc index f66676dffe3..132ea52e3e3 100644 --- a/Firestore/core/src/api/query_core.cc +++ b/Firestore/core/src/api/query_core.cc @@ -432,8 +432,8 @@ std::string Query::Describe(Operator op) const { } AggregateQuery Query::Aggregate( - std::vector&& aggregations) const { - return AggregateQuery(*this, std::move(aggregations)); + std::vector&& aggregateFields) const { + return AggregateQuery(*this, std::move(aggregateFields)); } // TODO(b/280805906) Remove this count specific API after the c++ SDK migrates diff --git a/Firestore/core/src/api/query_core.h b/Firestore/core/src/api/query_core.h index f6588cc7927..7a429dec91b 100644 --- a/Firestore/core/src/api/query_core.h +++ b/Firestore/core/src/api/query_core.h @@ -193,12 +193,12 @@ class Query { /** * Creates a new `AggregateQuery` that performs the specified aggregations. * - * @param aggregations The aggregations to be performed by the created + * @param aggregateFields The aggregations to be performed by the created * `AggregateQuery`. * * @return The created `AggregateQuery`. */ - AggregateQuery Aggregate(std::vector&& aggregations) const; + AggregateQuery Aggregate(std::vector&& aggregateFields) const; // TODO(b/280805906) Remove this count specific API after the c++ SDK migrates // to the new Aggregate API diff --git a/Firestore/core/src/remote/remote_objc_bridge.cc b/Firestore/core/src/remote/remote_objc_bridge.cc index eac349f4cf4..ae7fcb246ea 100644 --- a/Firestore/core/src/remote/remote_objc_bridge.cc +++ b/Firestore/core/src/remote/remote_objc_bridge.cc @@ -278,7 +278,7 @@ DatastoreSerializer::EncodeAggregateQueryRequest( const std::vector& aggregates, absl::flat_hash_map& aliasMap) const { Message result; - auto encodedTarget = serializer_.EncodeQueryTarget(query.ToTarget()); + auto encodedTarget = serializer_.EncodeQueryTarget(query.ToAggregateTarget()); result->parent = encodedTarget.parent; result->which_query_type = google_firestore_v1_RunAggregationQueryRequest_structured_aggregation_query_tag; // NOLINT