Skip to content

Commit

Permalink
sum avg (#11650)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkDuckworth authored Oct 17, 2023
1 parent eaf81fa commit 54d4d70
Show file tree
Hide file tree
Showing 18 changed files with 462 additions and 665 deletions.
3 changes: 3 additions & 0 deletions Firestore/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
355 changes: 116 additions & 239 deletions Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Firestore/Example/Tests/Integration/API/FIRQueryTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}

Expand Down
4 changes: 2 additions & 2 deletions Firestore/Source/API/FIRAggregateField.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@

#import "FIRAggregateField.h"

#include <string>

#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
Expand Down
4 changes: 2 additions & 2 deletions Firestore/Source/API/FIRAggregateQuery+Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface FIRAggregateQuery (/* init */)

- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithQueryAndAggregations:(FIRQuery *)query
aggregations:(NSArray<FIRAggregateField *> *)aggregations
- (instancetype)initWithQuery:(FIRQuery *)query
aggregateFields:(NSArray<FIRAggregateField *> *)aggregations
NS_DESIGNATED_INITIALIZER;

@end
Expand Down
6 changes: 3 additions & 3 deletions Firestore/Source/API/FIRAggregateQuery.mm
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ @implementation FIRAggregateQuery {
std::unique_ptr<AggregateQuery> _aggregateQuery;
}

- (instancetype)initWithQueryAndAggregations:(FIRQuery *)query
aggregations:(NSArray<FIRAggregateField *> *)aggregations {
- (instancetype)initWithQuery:(FIRQuery *)query
aggregateFields:(NSArray<FIRAggregateField *> *)aggregateFields {
if (self = [super init]) {
_query = query;

std::vector<AggregateField> _aggregateFields;
for (FIRAggregateField *field in aggregations) {
for (FIRAggregateField *field in aggregateFields) {
_aggregateFields.push_back([field createInternalValue]);
}

Expand Down
22 changes: 0 additions & 22 deletions Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
14 changes: 7 additions & 7 deletions Firestore/Source/API/FIRAggregateQuerySnapshot.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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<google_firestore_v1_Value> 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
Expand Down
23 changes: 0 additions & 23 deletions Firestore/Source/API/FIRQuery+Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<FIRAggregateField *> *)aggregations
NS_SWIFT_NAME(aggregate(_:));

@end

NS_ASSUME_NONNULL_END
6 changes: 3 additions & 3 deletions Firestore/Source/API/FIRQuery.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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<FIRAggregateField *> *)aggregations {
return [[FIRAggregateQuery alloc] initWithQueryAndAggregations:self aggregations:aggregations];
- (FIRAggregateQuery *)aggregate:(NSArray<FIRAggregateField *> *)aggregateFields {
return [[FIRAggregateQuery alloc] initWithQuery:self aggregateFields:aggregateFields];
}

#pragma mark - Private Methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
NS_ASSUME_NONNULL_BEGIN

@class FIRAggregateQuery;
@class FIRAggregateField;

/**
* The results of executing an `AggregateQuery`.
Expand All @@ -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
22 changes: 20 additions & 2 deletions Firestore/Source/Public/FirebaseFirestore/FIRQuery.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#import "FIRListenerRegistration.h"

@class FIRAggregateQuery;
@class FIRAggregateField;
@class FIRFieldPath;
@class FIRFirestore;
@class FIRFilter;
Expand Down Expand Up @@ -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<FIRAggregateField *> *)aggregateFields
NS_SWIFT_NAME(aggregate(_:));

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

#import "FIRAggregateField.h"
#import "FIRAggregateQuery.h"
#import "FIRAggregateQuerySnapshot.h"
#import "FIRAggregateSource.h"
Expand Down
Loading

0 comments on commit 54d4d70

Please sign in to comment.