Skip to content

Commit

Permalink
Public count (#10254)
Browse files Browse the repository at this point in the history
* Revert "Revert "Public count (#10246)" (#10252)"

This reverts commit b695d99.

* Firestore: Re-write API docs for COUNT API (#10258)

* Add isEqual and hash for aggregate classes (#10261)

Co-authored-by: Denver Coneybeare <dconeybe@google.com>
Co-authored-by: Ehsan <ehsannas@gmail.com>
  • Loading branch information
3 people authored Sep 28, 2022
1 parent 7ad2380 commit 4232ac1
Show file tree
Hide file tree
Showing 13 changed files with 284 additions and 70 deletions.
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

0 comments on commit 4232ac1

Please sign in to comment.