Skip to content

Public count #10254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Firestore/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Unreleased
- [feature] Added `Query.count()`, which fetches the number of documents in the
result set without actually downloading the documents (#10246).

# 10.0.0
- [fixed] Fixed compiler warning about `@param comparator` (#10226).

Expand Down
80 changes: 77 additions & 3 deletions Firestore/Example/Tests/Integration/API/FIRCountTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,47 @@
#import <XCTest/XCTest.h>

#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
#import "Firestore/Source/API/FIRAggregateQuery+Internal.h"
#import "Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h"
#import "Firestore/Source/API/FIRQuery+Internal.h"
#import "Firestore/Source/Public/FirebaseFirestore/FIRAggregateQuery.h"
#import "Firestore/Source/Public/FirebaseFirestore/FIRAggregateQuerySnapshot.h"
#import "Firestore/Source/Public/FirebaseFirestore/FIRAggregateSource.h"

@interface FIRCountTests : FSTIntegrationTestCase
@end

@implementation FIRCountTests

- (void)testAggregateQueryEquals {
FIRCollectionReference* coll1 = [self collectionRefWithDocuments:@{}];
FIRCollectionReference* coll1Same = [[coll1 firestore] collectionWithPath:[coll1 path]];
FIRAggregateQuery* query1 = [coll1 count];
FIRAggregateQuery* query1Same = [coll1Same count];

FIRCollectionReference* sub = [[coll1 documentWithPath:@"bar"] collectionWithPath:@"baz"];
FIRAggregateQuery* query2 = [[[sub queryWhereField:@"a" isEqualTo:@1] queryLimitedTo:100] count];
FIRAggregateQuery* query2Same = [[[sub queryWhereField:@"a"
isEqualTo:@1] queryLimitedTo:100] count];
FIRAggregateQuery* query3 = [[[sub queryWhereField:@"b"
isEqualTo:@1] queryOrderedByField:@"c"] count];
FIRAggregateQuery* query3Same = [[[sub queryWhereField:@"b"
isEqualTo:@1] queryOrderedByField:@"c"] count];

XCTAssertEqualObjects(query1, query1Same);
XCTAssertEqualObjects(query2, query2Same);
XCTAssertEqualObjects(query3, query3Same);

XCTAssertEqual([query1 hash], [query1Same hash]);
XCTAssertEqual([query2 hash], [query2Same hash]);
XCTAssertEqual([query3 hash], [query3Same hash]);

XCTAssertFalse([query1 isEqual:nil]);
XCTAssertFalse([query1 isEqual:@"string"]);
XCTAssertFalse([query1 isEqual:query2]);
XCTAssertFalse([query2 isEqual:query3]);

XCTAssertNotEqual([query1 hash], [query2 hash]);
XCTAssertNotEqual([query2 hash], [query3 hash]);
}

- (void)testCanRunCountQuery {
// TODO(b/246758022): Remove this (and below) once COUNT is release for the backend.
if (![FSTIntegrationTestCase isRunningAgainstEmulator]) {
Expand Down Expand Up @@ -77,6 +109,48 @@ - (void)testCanRunCountWithOrderBys {
XCTAssertEqual(snapshot.count, [NSNumber numberWithLong:3L]);
}

- (void)testSnapshotEquals {
if (![FSTIntegrationTestCase isRunningAgainstEmulator]) {
return;
}

FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
@"a" : @{@"k" : @"a"},
@"b" : @{@"k" : @"b"},
@"c" : @{@"k" : @"c"}
}];

FIRAggregateQuerySnapshot* snapshot1 =
[self readSnapshotForAggregate:[[testCollection queryWhereField:@"k" isEqualTo:@"b"] count]];
FIRAggregateQuerySnapshot* snapshot1Same =
[self readSnapshotForAggregate:[[testCollection queryWhereField:@"k" isEqualTo:@"b"] count]];

FIRAggregateQuerySnapshot* snapshot2 =
[self readSnapshotForAggregate:[[testCollection queryWhereField:@"k" isEqualTo:@"a"] count]];
[self writeDocumentRef:[testCollection documentWithPath:@"d"] data:@{@"k" : @"a"}];
FIRAggregateQuerySnapshot* snapshot2Different =
[self readSnapshotForAggregate:[[testCollection queryWhereField:@"k" isEqualTo:@"a"] count]];

FIRAggregateQuerySnapshot* snapshot3 =
[self readSnapshotForAggregate:[[testCollection queryWhereField:@"k" isEqualTo:@"b"] count]];
FIRAggregateQuerySnapshot* snapshot3Different =
[self readSnapshotForAggregate:[[testCollection queryWhereField:@"k" isEqualTo:@"c"] count]];

XCTAssertEqualObjects(snapshot1, snapshot1Same);
XCTAssertEqual([snapshot1 hash], [snapshot1Same hash]);
XCTAssertEqualObjects([snapshot1 query], [[testCollection queryWhereField:@"k"
isEqualTo:@"b"] count]);

XCTAssertNotEqualObjects(snapshot1, nil);
XCTAssertNotEqualObjects(snapshot1, @"string");
XCTAssertNotEqualObjects(snapshot1, snapshot2);
XCTAssertNotEqual([snapshot1 hash], [snapshot2 hash]);
XCTAssertNotEqualObjects(snapshot2, snapshot2Different);
XCTAssertNotEqual([snapshot2 hash], [snapshot2Different hash]);
XCTAssertNotEqualObjects(snapshot3, snapshot3Different);
XCTAssertNotEqual([snapshot3 hash], [snapshot3Different hash]);
}

- (void)testTerminateDoesNotCrashWithFlyingCountQuery {
if (![FSTIntegrationTestCase isRunningAgainstEmulator]) {
return;
Expand Down
33 changes: 7 additions & 26 deletions Firestore/Source/API/FIRAggregateQuery+Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,16 @@
* limitations under the License.
*/

// TODO(b/246760853): Move FIRAggregateQuery to public headers to release it.

#import "FIRAggregateSource+Internal.h"
#import "FIRAggregateQuery.h"
#import "FIRQuery.h"

@class FIRAggregateQuerySnapshot;

/**
* An `AggregateQuery` computes some aggregation statistics from the result set of a base
* `Query`.
*/
NS_SWIFT_NAME(AggregateQuery)
@interface FIRAggregateQuery : NSObject
NS_ASSUME_NONNULL_BEGIN

- (instancetype _Nonnull)init NS_UNAVAILABLE;
- (instancetype _Nonnull)initWithQuery:(FIRQuery *_Nonnull)query NS_DESIGNATED_INITIALIZER;
@interface FIRAggregateQuery (/* init */)

/** The base `Query` for this aggregate query. */
@property(nonatomic, readonly) FIRQuery *_Nonnull query;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithQuery:(FIRQuery *)query NS_DESIGNATED_INITIALIZER;

/**
* Executes the aggregate query and reads back the results as a `FIRAggregateQuerySnapshot`.
*
* @param source indicates where the results should be fetched from.
* @param completion a block to execute once the results have been successfully read.
* snapshot will be `nil` only if error is `non-nil`.
*/
- (void)aggregationWithSource:(FIRAggregateSource)source
completion:(void (^_Nonnull)(FIRAggregateQuerySnapshot *_Nullable snapshot,
NSError *_Nullable error))completion
NS_SWIFT_NAME(aggregation(source:completion:));
@end

NS_ASSUME_NONNULL_END
26 changes: 24 additions & 2 deletions Firestore/Source/API/FIRAggregateQuery.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,39 @@
#include "Firestore/core/src/util/error_apple.h"
#include "Firestore/core/src/util/statusor.h"

NS_ASSUME_NONNULL_BEGIN

#pragma mark - FIRAggregateQuery

@implementation FIRAggregateQuery {
FIRQuery *_query;
std::unique_ptr<api::AggregateQuery> _aggregation;
}

- (instancetype _Nonnull)initWithQuery:(FIRQuery *)query {
- (instancetype)initWithQuery:(FIRQuery *)query {
if (self = [super init]) {
_query = query;
_aggregation = absl::make_unique<api::AggregateQuery>(query.apiQuery.Count());
}
return self;
}

#pragma mark - NSObject Methods

- (BOOL)isEqual:(nullable id)other {
if (other == self) return YES;
if (![[other class] isEqual:[self class]]) return NO;

auto otherQuery = static_cast<FIRAggregateQuery *>(other);
return [_query isEqual:otherQuery->_query];
}

- (NSUInteger)hash {
return [_query hash];
}

#pragma mark - Public Methods

- (FIRQuery *)query {
return _query;
}
Expand All @@ -46,7 +66,7 @@ - (void)aggregationWithSource:(FIRAggregateSource)source
NSError *_Nullable error))completion {
_aggregation->Get([self, completion](const firebase::firestore::util::StatusOr<int64_t> &result) {
if (result.ok()) {
completion([[FIRAggregateQuerySnapshot alloc] initWithCount:result.ValueOrDie() Query:self],
completion([[FIRAggregateQuerySnapshot alloc] initWithCount:result.ValueOrDie() query:self],
nil);
} else {
completion(nil, MakeNSError(result.status()));
Expand All @@ -55,3 +75,5 @@ - (void)aggregationWithSource:(FIRAggregateSource)source
}

@end

NS_ASSUME_NONNULL_END
27 changes: 8 additions & 19 deletions Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,18 @@
* limitations under the License.
*/

// TODO(b/246760853): Move FIRAggregateQuerySnapshot to public headers to release it.

#import "FIRAggregateQuery+Internal.h"
#import "FIRAggregateQuerySnapshot.h"

@class FIRAggregateQuery;

/**
* An `AggregateQuerySnapshot` contains results of a `AggregateQuery`.
*/
NS_SWIFT_NAME(AggregateQuerySnapshot)
@interface FIRAggregateQuerySnapshot : NSObject
NS_ASSUME_NONNULL_BEGIN

- (instancetype _Nonnull)init NS_UNAVAILABLE;
- (instancetype _Nonnull)initWithCount:(int64_t)result
Query:(FIRAggregateQuery* _Nonnull)query NS_DESIGNATED_INITIALIZER;
@interface FIRAggregateQuerySnapshot (/* init */)

/** The original `AggregateQuery` this snapshot is a result of. */
@property(nonatomic, readonly) FIRAggregateQuery* _Nonnull query;

/**
* The result of a document count aggregation. Null if no count aggregation is
* available in the result.
*/
@property(nonatomic, readonly) NSNumber* _Nullable count;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithCount:(int64_t)result
query:(FIRAggregateQuery *)query NS_DESIGNATED_INITIALIZER;

@end

NS_ASSUME_NONNULL_END
26 changes: 25 additions & 1 deletion Firestore/Source/API/FIRAggregateQuerySnapshot.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,41 @@

#import "FIRAggregateQuerySnapshot+Internal.h"

#import "FIRAggregateQuery.h"

NS_ASSUME_NONNULL_BEGIN

@implementation FIRAggregateQuerySnapshot {
int64_t _result;
FIRAggregateQuery* _query;
}

- (instancetype _Nonnull)initWithCount:(int64_t)count Query:(FIRAggregateQuery*)query {
- (instancetype)initWithCount:(int64_t)count query:(FIRAggregateQuery*)query {
if (self = [super init]) {
_result = count;
_query = query;
}
return self;
}

#pragma mark - NSObject Methods

- (BOOL)isEqual:(nullable id)other {
if (other == self) return YES;
if (![[other class] isEqual:[self class]]) return NO;

auto otherSnap = static_cast<FIRAggregateQuerySnapshot*>(other);
return _result == otherSnap->_result && [_query isEqual:otherSnap->_query];
}

- (NSUInteger)hash {
NSUInteger result = [_query hash];
result = 31 * result + [[self count] hash];
return result;
}

#pragma mark - Public Methods

- (NSNumber*)count {
return [NSNumber numberWithLongLong:_result];
}
Expand All @@ -38,3 +60,5 @@ - (FIRAggregateQuery*)query {
}

@end

NS_ASSUME_NONNULL_END
7 changes: 0 additions & 7 deletions Firestore/Source/API/FIRQuery+Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
* limitations under the License.
*/

#import "FIRAggregateQuery+Internal.h"
#import "FIRQuery.h"

#include <memory>
Expand Down Expand Up @@ -48,12 +47,6 @@ NS_ASSUME_NONNULL_BEGIN
// TODO(orquery): This method will become public API. Change visibility and add documentation.
- (FIRQuery *)queryWhereFilter:(FIRFilter *)filter;

// TODO(b/246760853): This property will become public API.
/**
* An `AggregateQuery` counting the number of documents matching this query.
*/
@property(nonatomic, readonly) FIRAggregateQuery *count;

@end

NS_ASSUME_NONNULL_END
1 change: 1 addition & 0 deletions Firestore/Source/API/FIRQuery.mm
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <utility>
#include <vector>

#import "FIRAggregateQuery+Internal.h"
#import "FIRDocumentReference.h"
#import "FIRFirestoreErrors.h"
#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
Expand Down
51 changes: 51 additions & 0 deletions Firestore/Source/Public/FirebaseFirestore/FIRAggregateQuery.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>

#import "FIRAggregateSource.h"

NS_ASSUME_NONNULL_BEGIN

@class FIRQuery;
@class FIRAggregateQuerySnapshot;

/**
* A query that calculates aggregations over an underlying query.
*/
NS_SWIFT_NAME(AggregateQuery)
@interface FIRAggregateQuery : NSObject

/** :nodoc: */
- (instancetype)init __attribute__((unavailable("FIRAggregateQuery cannot be created directly.")));

/** The query whose aggregations will be calculated by this object. */
@property(nonatomic, readonly) FIRQuery *query;

/**
* Executes this query.
*
* @param source The source from which to acquire the aggregate results.
* @param completion a block to execute once the results have been successfully read.
* snapshot will be `nil` only if error is `non-nil`.
*/
- (void)aggregationWithSource:(FIRAggregateSource)source
completion:(void (^)(FIRAggregateQuerySnapshot *_Nullable snapshot,
NSError *_Nullable error))completion
NS_SWIFT_NAME(getAggregation(source:completion:));
@end

NS_ASSUME_NONNULL_END
Loading