mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-11 22:55:59 +05:45
This simplifies the import process back into google3 and allows us to add additional directories to the project without needing to update the project files for each directory we add. This shows up most clearly in the change to Firestore/Example/Firestore.xcodeproj/project.pbxproj: this no longer needs to list essentially every directory in the project as a header search path. * Clang-format configuration for C++ files * Add support C++-only sources to the podspec * Podspec support for fully qualified imports * xcodeproj changes for fully-qualified imports * Use fully-qualified imports in Firestore sources
546 lines
22 KiB
Objective-C
546 lines
22 KiB
Objective-C
/*
|
|
* Copyright 2017 Google
|
|
*
|
|
* 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 "FIRQuery.h"
|
|
|
|
#import "FIRDocumentReference.h"
|
|
#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
|
|
#import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h"
|
|
#import "Firestore/Source/API/FIRFieldPath+Internal.h"
|
|
#import "Firestore/Source/API/FIRFirestore+Internal.h"
|
|
#import "Firestore/Source/API/FIRListenerRegistration+Internal.h"
|
|
#import "Firestore/Source/API/FIRQuery+Internal.h"
|
|
#import "Firestore/Source/API/FIRQuerySnapshot+Internal.h"
|
|
#import "Firestore/Source/API/FIRQuery_Init.h"
|
|
#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h"
|
|
#import "Firestore/Source/API/FSTUserDataConverter.h"
|
|
#import "Firestore/Source/Core/FSTEventManager.h"
|
|
#import "Firestore/Source/Core/FSTFirestoreClient.h"
|
|
#import "Firestore/Source/Core/FSTQuery.h"
|
|
#import "Firestore/Source/Model/FSTDocument.h"
|
|
#import "Firestore/Source/Model/FSTDocumentKey.h"
|
|
#import "Firestore/Source/Model/FSTFieldValue.h"
|
|
#import "Firestore/Source/Model/FSTPath.h"
|
|
#import "Firestore/Source/Util/FSTAssert.h"
|
|
#import "Firestore/Source/Util/FSTAsyncQueryListener.h"
|
|
#import "Firestore/Source/Util/FSTUsageValidation.h"
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
@interface FIRQueryListenOptions ()
|
|
|
|
- (instancetype)initWithIncludeQueryMetadataChanges:(BOOL)includeQueryMetadataChanges
|
|
includeDocumentMetadataChanges:(BOOL)includeDocumentMetadataChanges
|
|
NS_DESIGNATED_INITIALIZER;
|
|
|
|
@end
|
|
|
|
@implementation FIRQueryListenOptions
|
|
|
|
+ (instancetype)options {
|
|
return [[FIRQueryListenOptions alloc] init];
|
|
}
|
|
|
|
- (instancetype)initWithIncludeQueryMetadataChanges:(BOOL)includeQueryMetadataChanges
|
|
includeDocumentMetadataChanges:(BOOL)includeDocumentMetadataChanges {
|
|
if (self = [super init]) {
|
|
_includeQueryMetadataChanges = includeQueryMetadataChanges;
|
|
_includeDocumentMetadataChanges = includeDocumentMetadataChanges;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (instancetype)init {
|
|
return [self initWithIncludeQueryMetadataChanges:NO includeDocumentMetadataChanges:NO];
|
|
}
|
|
|
|
- (instancetype)includeQueryMetadataChanges:(BOOL)includeQueryMetadataChanges {
|
|
return [[FIRQueryListenOptions alloc]
|
|
initWithIncludeQueryMetadataChanges:includeQueryMetadataChanges
|
|
includeDocumentMetadataChanges:_includeDocumentMetadataChanges];
|
|
}
|
|
|
|
- (instancetype)includeDocumentMetadataChanges:(BOOL)includeDocumentMetadataChanges {
|
|
return [[FIRQueryListenOptions alloc]
|
|
initWithIncludeQueryMetadataChanges:_includeQueryMetadataChanges
|
|
includeDocumentMetadataChanges:includeDocumentMetadataChanges];
|
|
}
|
|
|
|
@end
|
|
|
|
@interface FIRQuery ()
|
|
@property(nonatomic, strong, readonly) FSTQuery *query;
|
|
@end
|
|
|
|
@implementation FIRQuery (Internal)
|
|
+ (instancetype)referenceWithQuery:(FSTQuery *)query firestore:(FIRFirestore *)firestore {
|
|
return [[FIRQuery alloc] initWithQuery:query firestore:firestore];
|
|
}
|
|
@end
|
|
|
|
@implementation FIRQuery
|
|
|
|
#pragma mark - Constructor Methods
|
|
|
|
- (instancetype)initWithQuery:(FSTQuery *)query firestore:(FIRFirestore *)firestore {
|
|
if (self = [super init]) {
|
|
_query = query;
|
|
_firestore = firestore;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#pragma mark - NSObject Methods
|
|
|
|
- (BOOL)isEqual:(nullable id)other {
|
|
if (other == self) return YES;
|
|
if (!other || ![[other class] isEqual:[self class]]) return NO;
|
|
|
|
return [self isEqualToQuery:other];
|
|
}
|
|
|
|
- (BOOL)isEqualToQuery:(nullable FIRQuery *)query {
|
|
if (self == query) return YES;
|
|
if (query == nil) return NO;
|
|
if (self.firestore != query.firestore && ![self.firestore isEqual:query.firestore]) return NO;
|
|
if (self.query != query.query && ![self.query isEqual:query.query]) return NO;
|
|
return YES;
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
NSUInteger hash = [self.firestore hash];
|
|
hash = hash * 31u + [self.query hash];
|
|
return hash;
|
|
}
|
|
|
|
#pragma mark - Public Methods
|
|
|
|
- (void)getDocumentsWithCompletion:(void (^)(FIRQuerySnapshot *_Nullable snapshot,
|
|
NSError *_Nullable error))completion {
|
|
FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES
|
|
includeDocumentMetadataChanges:YES
|
|
waitForSyncWhenOnline:YES];
|
|
|
|
dispatch_semaphore_t registered = dispatch_semaphore_create(0);
|
|
__block id<FIRListenerRegistration> listenerRegistration;
|
|
FIRQuerySnapshotBlock listener = ^(FIRQuerySnapshot *snapshot, NSError *error) {
|
|
if (error) {
|
|
completion(nil, error);
|
|
return;
|
|
}
|
|
|
|
// Remove query first before passing event to user to avoid user actions affecting the
|
|
// now stale query.
|
|
dispatch_semaphore_wait(registered, DISPATCH_TIME_FOREVER);
|
|
[listenerRegistration remove];
|
|
|
|
completion(snapshot, nil);
|
|
};
|
|
|
|
listenerRegistration = [self addSnapshotListenerInternalWithOptions:options listener:listener];
|
|
dispatch_semaphore_signal(registered);
|
|
}
|
|
|
|
- (id<FIRListenerRegistration>)addSnapshotListener:(FIRQuerySnapshotBlock)listener {
|
|
return [self addSnapshotListenerWithOptions:nil listener:listener];
|
|
}
|
|
|
|
- (id<FIRListenerRegistration>)addSnapshotListenerWithOptions:
|
|
(nullable FIRQueryListenOptions *)options
|
|
listener:(FIRQuerySnapshotBlock)listener {
|
|
return [self addSnapshotListenerInternalWithOptions:[self internalOptions:options]
|
|
listener:listener];
|
|
}
|
|
|
|
- (id<FIRListenerRegistration>)
|
|
addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
|
|
listener:(FIRQuerySnapshotBlock)listener {
|
|
FIRFirestore *firestore = self.firestore;
|
|
FSTQuery *query = self.query;
|
|
|
|
FSTViewSnapshotHandler snapshotHandler = ^(FSTViewSnapshot *snapshot, NSError *error) {
|
|
if (error) {
|
|
listener(nil, error);
|
|
return;
|
|
}
|
|
|
|
FIRSnapshotMetadata *metadata =
|
|
[FIRSnapshotMetadata snapshotMetadataWithPendingWrites:snapshot.hasPendingWrites
|
|
fromCache:snapshot.fromCache];
|
|
|
|
listener([FIRQuerySnapshot snapshotWithFirestore:firestore
|
|
originalQuery:query
|
|
snapshot:snapshot
|
|
metadata:metadata],
|
|
nil);
|
|
};
|
|
|
|
FSTAsyncQueryListener *asyncListener =
|
|
[[FSTAsyncQueryListener alloc] initWithDispatchQueue:self.firestore.client.userDispatchQueue
|
|
snapshotHandler:snapshotHandler];
|
|
|
|
FSTQueryListener *internalListener =
|
|
[firestore.client listenToQuery:query
|
|
options:internalOptions
|
|
viewSnapshotHandler:[asyncListener asyncSnapshotHandler]];
|
|
return [[FSTListenerRegistration alloc] initWithClient:self.firestore.client
|
|
asyncListener:asyncListener
|
|
internalListener:internalListener];
|
|
}
|
|
|
|
- (FIRQuery *)queryWhereField:(NSString *)field isEqualTo:(id)value {
|
|
return [self queryWithFilterOperator:FSTRelationFilterOperatorEqual field:field value:value];
|
|
}
|
|
|
|
- (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isEqualTo:(id)value {
|
|
return [self queryWithFilterOperator:FSTRelationFilterOperatorEqual
|
|
path:path.internalValue
|
|
value:value];
|
|
}
|
|
|
|
- (FIRQuery *)queryWhereField:(NSString *)field isLessThan:(id)value {
|
|
return [self queryWithFilterOperator:FSTRelationFilterOperatorLessThan field:field value:value];
|
|
}
|
|
|
|
- (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isLessThan:(id)value {
|
|
return [self queryWithFilterOperator:FSTRelationFilterOperatorLessThan
|
|
path:path.internalValue
|
|
value:value];
|
|
}
|
|
|
|
- (FIRQuery *)queryWhereField:(NSString *)field isLessThanOrEqualTo:(id)value {
|
|
return [self queryWithFilterOperator:FSTRelationFilterOperatorLessThanOrEqual
|
|
field:field
|
|
value:value];
|
|
}
|
|
|
|
- (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isLessThanOrEqualTo:(id)value {
|
|
return [self queryWithFilterOperator:FSTRelationFilterOperatorLessThanOrEqual
|
|
path:path.internalValue
|
|
value:value];
|
|
}
|
|
|
|
- (FIRQuery *)queryWhereField:(NSString *)field isGreaterThan:(id)value {
|
|
return
|
|
[self queryWithFilterOperator:FSTRelationFilterOperatorGreaterThan field:field value:value];
|
|
}
|
|
|
|
- (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isGreaterThan:(id)value {
|
|
return [self queryWithFilterOperator:FSTRelationFilterOperatorGreaterThan
|
|
path:path.internalValue
|
|
value:value];
|
|
}
|
|
|
|
- (FIRQuery *)queryWhereField:(NSString *)field isGreaterThanOrEqualTo:(id)value {
|
|
return [self queryWithFilterOperator:FSTRelationFilterOperatorGreaterThanOrEqual
|
|
field:field
|
|
value:value];
|
|
}
|
|
|
|
- (FIRQuery *)queryWhereFieldPath:(FIRFieldPath *)path isGreaterThanOrEqualTo:(id)value {
|
|
return [self queryWithFilterOperator:FSTRelationFilterOperatorGreaterThanOrEqual
|
|
path:path.internalValue
|
|
value:value];
|
|
}
|
|
|
|
- (FIRQuery *)queryOrderedByField:(NSString *)field {
|
|
return
|
|
[self queryOrderedByFieldPath:[FIRFieldPath pathWithDotSeparatedString:field] descending:NO];
|
|
}
|
|
|
|
- (FIRQuery *)queryOrderedByFieldPath:(FIRFieldPath *)fieldPath {
|
|
return [self queryOrderedByFieldPath:fieldPath descending:NO];
|
|
}
|
|
|
|
- (FIRQuery *)queryOrderedByField:(NSString *)field descending:(BOOL)descending {
|
|
return [self queryOrderedByFieldPath:[FIRFieldPath pathWithDotSeparatedString:field]
|
|
descending:descending];
|
|
}
|
|
|
|
- (FIRQuery *)queryOrderedByFieldPath:(FIRFieldPath *)fieldPath descending:(BOOL)descending {
|
|
[self validateNewOrderByPath:fieldPath.internalValue];
|
|
if (self.query.startAt) {
|
|
FSTThrowInvalidUsage(
|
|
@"InvalidQueryException",
|
|
@"Invalid query. You must not specify a starting point before specifying the order by.");
|
|
}
|
|
if (self.query.endAt) {
|
|
FSTThrowInvalidUsage(
|
|
@"InvalidQueryException",
|
|
@"Invalid query. You must not specify an ending point before specifying the order by.");
|
|
}
|
|
FSTSortOrder *sortOrder =
|
|
[FSTSortOrder sortOrderWithFieldPath:fieldPath.internalValue ascending:!descending];
|
|
return [FIRQuery referenceWithQuery:[self.query queryByAddingSortOrder:sortOrder]
|
|
firestore:self.firestore];
|
|
}
|
|
|
|
- (FIRQuery *)queryLimitedTo:(NSInteger)limit {
|
|
if (limit <= 0) {
|
|
FSTThrowInvalidArgument(@"Invalid Query. Query limit (%ld) is invalid. Limit must be positive.",
|
|
(long)limit);
|
|
}
|
|
return [FIRQuery referenceWithQuery:[self.query queryBySettingLimit:limit] firestore:_firestore];
|
|
}
|
|
|
|
- (FIRQuery *)queryStartingAtDocument:(FIRDocumentSnapshot *)snapshot {
|
|
FSTBound *bound = [self boundFromSnapshot:snapshot isBefore:YES];
|
|
return [FIRQuery referenceWithQuery:[self.query queryByAddingStartAt:bound]
|
|
firestore:self.firestore];
|
|
}
|
|
|
|
- (FIRQuery *)queryStartingAtValues:(NSArray *)fieldValues {
|
|
FSTBound *bound = [self boundFromFieldValues:fieldValues isBefore:YES];
|
|
return [FIRQuery referenceWithQuery:[self.query queryByAddingStartAt:bound]
|
|
firestore:self.firestore];
|
|
}
|
|
|
|
- (FIRQuery *)queryStartingAfterDocument:(FIRDocumentSnapshot *)snapshot {
|
|
FSTBound *bound = [self boundFromSnapshot:snapshot isBefore:NO];
|
|
return [FIRQuery referenceWithQuery:[self.query queryByAddingStartAt:bound]
|
|
firestore:self.firestore];
|
|
}
|
|
|
|
- (FIRQuery *)queryStartingAfterValues:(NSArray *)fieldValues {
|
|
FSTBound *bound = [self boundFromFieldValues:fieldValues isBefore:NO];
|
|
return [FIRQuery referenceWithQuery:[self.query queryByAddingStartAt:bound]
|
|
firestore:self.firestore];
|
|
}
|
|
|
|
- (FIRQuery *)queryEndingBeforeDocument:(FIRDocumentSnapshot *)snapshot {
|
|
FSTBound *bound = [self boundFromSnapshot:snapshot isBefore:YES];
|
|
return
|
|
[FIRQuery referenceWithQuery:[self.query queryByAddingEndAt:bound] firestore:self.firestore];
|
|
}
|
|
|
|
- (FIRQuery *)queryEndingBeforeValues:(NSArray *)fieldValues {
|
|
FSTBound *bound = [self boundFromFieldValues:fieldValues isBefore:YES];
|
|
return
|
|
[FIRQuery referenceWithQuery:[self.query queryByAddingEndAt:bound] firestore:self.firestore];
|
|
}
|
|
|
|
- (FIRQuery *)queryEndingAtDocument:(FIRDocumentSnapshot *)snapshot {
|
|
FSTBound *bound = [self boundFromSnapshot:snapshot isBefore:NO];
|
|
return
|
|
[FIRQuery referenceWithQuery:[self.query queryByAddingEndAt:bound] firestore:self.firestore];
|
|
}
|
|
|
|
- (FIRQuery *)queryEndingAtValues:(NSArray *)fieldValues {
|
|
FSTBound *bound = [self boundFromFieldValues:fieldValues isBefore:NO];
|
|
return
|
|
[FIRQuery referenceWithQuery:[self.query queryByAddingEndAt:bound] firestore:self.firestore];
|
|
}
|
|
|
|
#pragma mark - Private Methods
|
|
|
|
/** Private helper for all of the queryWhereField: methods. */
|
|
- (FIRQuery *)queryWithFilterOperator:(FSTRelationFilterOperator)filterOperator
|
|
field:(NSString *)field
|
|
value:(id)value {
|
|
return [self queryWithFilterOperator:filterOperator
|
|
path:[FIRFieldPath pathWithDotSeparatedString:field].internalValue
|
|
value:value];
|
|
}
|
|
|
|
- (FIRQuery *)queryWithFilterOperator:(FSTRelationFilterOperator)filterOperator
|
|
path:(FSTFieldPath *)fieldPath
|
|
value:(id)value {
|
|
FSTFieldValue *fieldValue;
|
|
if ([fieldPath isKeyFieldPath]) {
|
|
if ([value isKindOfClass:[NSString class]]) {
|
|
NSString *documentKey = (NSString *)value;
|
|
if ([documentKey containsString:@"/"]) {
|
|
FSTThrowInvalidArgument(
|
|
@"Invalid query. When querying by document ID you must provide "
|
|
"a valid document ID, but '%@' contains a '/' character.",
|
|
documentKey);
|
|
} else if (documentKey.length == 0) {
|
|
FSTThrowInvalidArgument(
|
|
@"Invalid query. When querying by document ID you must provide "
|
|
"a valid document ID, but it was an empty string.");
|
|
}
|
|
FSTResourcePath *path = [self.query.path pathByAppendingSegment:documentKey];
|
|
fieldValue = [FSTReferenceValue referenceValue:[FSTDocumentKey keyWithPath:path]
|
|
databaseID:self.firestore.databaseID];
|
|
} else if ([value isKindOfClass:[FIRDocumentReference class]]) {
|
|
FIRDocumentReference *ref = (FIRDocumentReference *)value;
|
|
fieldValue = [FSTReferenceValue referenceValue:ref.key databaseID:self.firestore.databaseID];
|
|
} else {
|
|
FSTThrowInvalidArgument(
|
|
@"Invalid query. When querying by document ID you must provide a "
|
|
"valid string or DocumentReference, but it was of type: %@",
|
|
NSStringFromClass([value class]));
|
|
}
|
|
} else {
|
|
fieldValue = [self.firestore.dataConverter parsedQueryValue:value];
|
|
}
|
|
|
|
id<FSTFilter> filter;
|
|
if ([fieldValue isEqual:[FSTNullValue nullValue]]) {
|
|
if (filterOperator != FSTRelationFilterOperatorEqual) {
|
|
FSTThrowInvalidUsage(@"InvalidQueryException",
|
|
@"Invalid Query. You can only perform equality comparisons on nil / "
|
|
"NSNull.");
|
|
}
|
|
filter = [[FSTNullFilter alloc] initWithField:fieldPath];
|
|
} else if ([fieldValue isEqual:[FSTDoubleValue nanValue]]) {
|
|
if (filterOperator != FSTRelationFilterOperatorEqual) {
|
|
FSTThrowInvalidUsage(@"InvalidQueryException",
|
|
@"Invalid Query. You can only perform equality comparisons on NaN.");
|
|
}
|
|
filter = [[FSTNanFilter alloc] initWithField:fieldPath];
|
|
} else {
|
|
filter = [FSTRelationFilter filterWithField:fieldPath
|
|
filterOperator:filterOperator
|
|
value:fieldValue];
|
|
[self validateNewRelationFilter:filter];
|
|
}
|
|
return [FIRQuery referenceWithQuery:[self.query queryByAddingFilter:filter]
|
|
firestore:self.firestore];
|
|
}
|
|
|
|
- (void)validateNewRelationFilter:(FSTRelationFilter *)filter {
|
|
if ([filter isInequality]) {
|
|
FSTFieldPath *existingField = [self.query inequalityFilterField];
|
|
if (existingField && ![existingField isEqual:filter.field]) {
|
|
FSTThrowInvalidUsage(
|
|
@"InvalidQueryException",
|
|
@"Invalid Query. All where filters with an inequality "
|
|
"(lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) must be on the same "
|
|
"field. But you have inequality filters on '%@' and '%@'",
|
|
existingField, filter.field);
|
|
}
|
|
|
|
FSTFieldPath *firstOrderByField = [self.query firstSortOrderField];
|
|
if (firstOrderByField) {
|
|
[self validateOrderByField:firstOrderByField matchesInequalityField:filter.field];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)validateNewOrderByPath:(FSTFieldPath *)fieldPath {
|
|
if (![self.query firstSortOrderField]) {
|
|
// This is the first order by. It must match any inequality.
|
|
FSTFieldPath *inequalityField = [self.query inequalityFilterField];
|
|
if (inequalityField) {
|
|
[self validateOrderByField:fieldPath matchesInequalityField:inequalityField];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)validateOrderByField:(FSTFieldPath *)orderByField
|
|
matchesInequalityField:(FSTFieldPath *)inequalityField {
|
|
if (!([orderByField isEqual:inequalityField])) {
|
|
FSTThrowInvalidUsage(
|
|
@"InvalidQueryException",
|
|
@"Invalid query. You have a where filter with an "
|
|
"inequality (lessThan, lessThanOrEqual, greaterThan, or greaterThanOrEqual) on field '%@' "
|
|
"and so you must also use '%@' as your first queryOrderedBy field, but your first "
|
|
"queryOrderedBy is currently on field '%@' instead.",
|
|
inequalityField, inequalityField, orderByField);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a FSTBound from a query given the document.
|
|
*
|
|
* Note that the FSTBound will always include the key of the document and the position will be
|
|
* unambiguous.
|
|
*
|
|
* Will throw if the document does not contain all fields of the order by of the query.
|
|
*/
|
|
- (FSTBound *)boundFromSnapshot:(FIRDocumentSnapshot *)snapshot isBefore:(BOOL)isBefore {
|
|
if (![snapshot exists]) {
|
|
FSTThrowInvalidUsage(@"InvalidQueryException",
|
|
@"Invalid query. You are trying to start or end a query using a document "
|
|
@"that doesn't exist.");
|
|
}
|
|
FSTDocument *document = snapshot.internalDocument;
|
|
NSMutableArray<FSTFieldValue *> *components = [NSMutableArray array];
|
|
|
|
// Because people expect to continue/end a query at the exact document provided, we need to
|
|
// use the implicit sort order rather than the explicit sort order, because it's guaranteed to
|
|
// contain the document key. That way the position becomes unambiguous and the query
|
|
// continues/ends exactly at the provided document. Without the key (by using the explicit sort
|
|
// orders), multiple documents could match the position, yielding duplicate results.
|
|
for (FSTSortOrder *sortOrder in self.query.sortOrders) {
|
|
if ([sortOrder.field isEqual:[FSTFieldPath keyFieldPath]]) {
|
|
[components addObject:[FSTReferenceValue referenceValue:document.key
|
|
databaseID:self.firestore.databaseID]];
|
|
} else {
|
|
FSTFieldValue *value = [document fieldForPath:sortOrder.field];
|
|
if (value != nil) {
|
|
[components addObject:value];
|
|
} else {
|
|
FSTThrowInvalidUsage(@"InvalidQueryException",
|
|
@"Invalid query. You are trying to start or end a query using a "
|
|
"document for which the field '%@' (used as the order by) "
|
|
"does not exist.",
|
|
sortOrder.field.canonicalString);
|
|
}
|
|
}
|
|
}
|
|
return [FSTBound boundWithPosition:components isBefore:isBefore];
|
|
}
|
|
|
|
/** Converts a list of field values to an FSTBound. */
|
|
- (FSTBound *)boundFromFieldValues:(NSArray<id> *)fieldValues isBefore:(BOOL)isBefore {
|
|
// Use explicit sort order because it has to match the query the user made
|
|
NSArray<FSTSortOrder *> *explicitSortOrders = self.query.explicitSortOrders;
|
|
if (fieldValues.count > explicitSortOrders.count) {
|
|
FSTThrowInvalidUsage(@"InvalidQueryException",
|
|
@"Invalid query. You are trying to start or end a query using more values "
|
|
@"than were specified in the order by.");
|
|
}
|
|
|
|
NSMutableArray<FSTFieldValue *> *components = [NSMutableArray array];
|
|
[fieldValues enumerateObjectsUsingBlock:^(id rawValue, NSUInteger idx, BOOL *stop) {
|
|
FSTSortOrder *sortOrder = explicitSortOrders[idx];
|
|
if ([sortOrder.field isEqual:[FSTFieldPath keyFieldPath]]) {
|
|
if (![rawValue isKindOfClass:[NSString class]]) {
|
|
FSTThrowInvalidUsage(@"InvalidQueryException",
|
|
@"Invalid query. Expected a string for the document ID.");
|
|
}
|
|
NSString *documentID = (NSString *)rawValue;
|
|
if ([documentID containsString:@"/"]) {
|
|
FSTThrowInvalidUsage(@"InvalidQueryException",
|
|
@"Invalid query. Document ID '%@' contains a slash.", documentID);
|
|
}
|
|
FSTDocumentKey *key =
|
|
[FSTDocumentKey keyWithPath:[self.query.path pathByAppendingSegment:documentID]];
|
|
[components
|
|
addObject:[FSTReferenceValue referenceValue:key databaseID:self.firestore.databaseID]];
|
|
} else {
|
|
FSTFieldValue *fieldValue = [self.firestore.dataConverter parsedQueryValue:rawValue];
|
|
[components addObject:fieldValue];
|
|
}
|
|
}];
|
|
|
|
return [FSTBound boundWithPosition:components isBefore:isBefore];
|
|
}
|
|
|
|
/** Converts the public API options object to the internal options object. */
|
|
- (FSTListenOptions *)internalOptions:(nullable FIRQueryListenOptions *)options {
|
|
return [[FSTListenOptions alloc]
|
|
initWithIncludeQueryMetadataChanges:options.includeQueryMetadataChanges
|
|
includeDocumentMetadataChanges:options.includeDocumentMetadataChanges
|
|
waitForSyncWhenOnline:NO];
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|