diff options
Diffstat (limited to 'Firestore/Source')
66 files changed, 1159 insertions, 1177 deletions
diff --git a/Firestore/Source/API/FIRDocumentReference.mm b/Firestore/Source/API/FIRDocumentReference.mm index c2fc546..5ad606c 100644 --- a/Firestore/Source/API/FIRDocumentReference.mm +++ b/Firestore/Source/API/FIRDocumentReference.mm @@ -125,6 +125,11 @@ NS_ASSUME_NONNULL_BEGIN } - (void)setData:(NSDictionary<NSString *, id> *)documentData + mergeFields:(NSArray<id> *)mergeFields { + return [self setData:documentData mergeFields:mergeFields completion:nil]; +} + +- (void)setData:(NSDictionary<NSString *, id> *)documentData completion:(nullable void (^)(NSError *_Nullable error))completion { return [self setData:documentData merge:NO completion:completion]; } @@ -132,8 +137,19 @@ NS_ASSUME_NONNULL_BEGIN - (void)setData:(NSDictionary<NSString *, id> *)documentData merge:(BOOL)merge completion:(nullable void (^)(NSError *_Nullable error))completion { - FSTParsedSetData *parsed = merge ? [self.firestore.dataConverter parsedMergeData:documentData] - : [self.firestore.dataConverter parsedSetData:documentData]; + FSTParsedSetData *parsed = + merge ? [self.firestore.dataConverter parsedMergeData:documentData fieldMask:nil] + : [self.firestore.dataConverter parsedSetData:documentData]; + return [self.firestore.client + writeMutations:[parsed mutationsWithKey:self.key precondition:Precondition::None()] + completion:completion]; +} + +- (void)setData:(NSDictionary<NSString *, id> *)documentData + mergeFields:(NSArray<id> *)mergeFields + completion:(nullable void (^)(NSError *_Nullable error))completion { + FSTParsedSetData *parsed = + [self.firestore.dataConverter parsedMergeData:documentData fieldMask:mergeFields]; return [self.firestore.client writeMutations:[parsed mutationsWithKey:self.key precondition:Precondition::None()] completion:completion]; @@ -264,8 +280,8 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions }; FSTAsyncQueryListener *asyncListener = - [[FSTAsyncQueryListener alloc] initWithDispatchQueue:self.firestore.client.userDispatchQueue - snapshotHandler:snapshotHandler]; + [[FSTAsyncQueryListener alloc] initWithExecutor:self.firestore.client.userExecutor + snapshotHandler:snapshotHandler]; FSTQueryListener *internalListener = [firestore.client listenToQuery:query diff --git a/Firestore/Source/API/FIRFieldPath.mm b/Firestore/Source/API/FIRFieldPath.mm index d0d8714..4fd0022 100644 --- a/Firestore/Source/API/FIRFieldPath.mm +++ b/Firestore/Source/API/FIRFieldPath.mm @@ -25,6 +25,7 @@ #import "Firestore/Source/Util/FSTUsageValidation.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" +#include "Firestore/core/src/firebase/firestore/util/hashing.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; @@ -114,7 +115,7 @@ NS_ASSUME_NONNULL_BEGIN } - (NSUInteger)hash { - return _internalValue.Hash(); + return util::Hash(_internalValue); } - (const firebase::firestore::model::FieldPath &)internalValue { diff --git a/Firestore/Source/API/FIRFirestore.mm b/Firestore/Source/API/FIRFirestore.mm index fe461d6..e5f0c12 100644 --- a/Firestore/Source/API/FIRFirestore.mm +++ b/Firestore/Source/API/FIRFirestore.mm @@ -45,12 +45,16 @@ #include "Firestore/core/src/firebase/firestore/util/string_apple.h" #include "absl/memory/memory.h" +#include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h" + namespace util = firebase::firestore::util; using firebase::firestore::auth::CredentialsProvider; using firebase::firestore::auth::FirebaseCredentialsProvider; using firebase::firestore::core::DatabaseInfo; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::ResourcePath; +using util::internal::Executor; +using util::internal::ExecutorLibdispatch; NS_ASSUME_NONNULL_BEGIN @@ -272,12 +276,13 @@ extern "C" NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain"; const DatabaseInfo database_info(*self.databaseID, util::MakeStringView(_persistenceKey), util::MakeStringView(_settings.host), _settings.sslEnabled); - FSTDispatchQueue *userDispatchQueue = [FSTDispatchQueue queueWith:_settings.dispatchQueue]; + std::unique_ptr<Executor> userExecutor = + absl::make_unique<ExecutorLibdispatch>(_settings.dispatchQueue); _client = [FSTFirestoreClient clientWithDatabaseInfo:database_info usePersistence:_settings.persistenceEnabled credentialsProvider:_credentialsProvider.get() - userDispatchQueue:userDispatchQueue + userExecutor:std::move(userExecutor) workerDispatchQueue:_workerDispatchQueue]; } } diff --git a/Firestore/Source/API/FIRQuery.mm b/Firestore/Source/API/FIRQuery.mm index 32d8327..596f6ac 100644 --- a/Firestore/Source/API/FIRQuery.mm +++ b/Firestore/Source/API/FIRQuery.mm @@ -183,8 +183,8 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions }; FSTAsyncQueryListener *asyncListener = - [[FSTAsyncQueryListener alloc] initWithDispatchQueue:self.firestore.client.userDispatchQueue - snapshotHandler:snapshotHandler]; + [[FSTAsyncQueryListener alloc] initWithExecutor:self.firestore.client.userExecutor + snapshotHandler:snapshotHandler]; FSTQueryListener *internalListener = [firestore.client listenToQuery:query @@ -456,8 +456,8 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions if (fieldPath.IsKeyFieldPath()) { if (filterOperator == FSTRelationFilterOperatorArrayContains) { FSTThrowInvalidArgument( - @"Invalid query. You can't do arrayContains queries on document ID since document IDs " - @"are not arrays."); + @"Invalid query. You can't perform arrayContains queries on document ID since document " + "IDs are not arrays."); } if ([value isKindOfClass:[NSString class]]) { NSString *documentKey = (NSString *)value; diff --git a/Firestore/Source/API/FIRTransaction.mm b/Firestore/Source/API/FIRTransaction.mm index 668a359..b5bdefa 100644 --- a/Firestore/Source/API/FIRTransaction.mm +++ b/Firestore/Source/API/FIRTransaction.mm @@ -68,8 +68,19 @@ NS_ASSUME_NONNULL_BEGIN forDocument:(FIRDocumentReference *)document merge:(BOOL)merge { [self validateReference:document]; - FSTParsedSetData *parsed = merge ? [self.firestore.dataConverter parsedMergeData:data] - : [self.firestore.dataConverter parsedSetData:data]; + FSTParsedSetData *parsed = merge + ? [self.firestore.dataConverter parsedMergeData:data fieldMask:nil] + : [self.firestore.dataConverter parsedSetData:data]; + [self.internalTransaction setData:parsed forDocument:document.key]; + return self; +} + +- (FIRTransaction *)setData:(NSDictionary<NSString *, id> *)data + forDocument:(FIRDocumentReference *)document + mergeFields:(NSArray<id> *)mergeFields { + [self validateReference:document]; + FSTParsedSetData *parsed = + [self.firestore.dataConverter parsedMergeData:data fieldMask:mergeFields]; [self.internalTransaction setData:parsed forDocument:document.key]; return self; } diff --git a/Firestore/Source/API/FIRWriteBatch.mm b/Firestore/Source/API/FIRWriteBatch.mm index 1185dae..366c708 100644 --- a/Firestore/Source/API/FIRWriteBatch.mm +++ b/Firestore/Source/API/FIRWriteBatch.mm @@ -70,8 +70,21 @@ NS_ASSUME_NONNULL_BEGIN merge:(BOOL)merge { [self verifyNotCommitted]; [self validateReference:document]; - FSTParsedSetData *parsed = merge ? [self.firestore.dataConverter parsedMergeData:data] - : [self.firestore.dataConverter parsedSetData:data]; + FSTParsedSetData *parsed = merge + ? [self.firestore.dataConverter parsedMergeData:data fieldMask:nil] + : [self.firestore.dataConverter parsedSetData:data]; + [self.mutations + addObjectsFromArray:[parsed mutationsWithKey:document.key precondition:Precondition::None()]]; + return self; +} + +- (FIRWriteBatch *)setData:(NSDictionary<NSString *, id> *)data + forDocument:(FIRDocumentReference *)document + mergeFields:(NSArray<id> *)mergeFields { + [self verifyNotCommitted]; + [self validateReference:document]; + FSTParsedSetData *parsed = + [self.firestore.dataConverter parsedMergeData:data fieldMask:mergeFields]; [self.mutations addObjectsFromArray:[parsed mutationsWithKey:document.key precondition:Precondition::None()]]; return self; diff --git a/Firestore/Source/API/FSTUserDataConverter.h b/Firestore/Source/API/FSTUserDataConverter.h index 98a65ae..27a5f09 100644 --- a/Firestore/Source/API/FSTUserDataConverter.h +++ b/Firestore/Source/API/FSTUserDataConverter.h @@ -27,7 +27,6 @@ @class FSTObjectValue; @class FSTFieldValue; @class FSTMutation; -@class FSTSnapshotVersion; NS_ASSUME_NONNULL_BEGIN @@ -130,7 +129,7 @@ typedef id _Nullable (^FSTPreConverterBlock)(id _Nullable); - (FSTParsedSetData *)parsedSetData:(id)input; /** Parse document data from a setData call with `merge:YES`. */ -- (FSTParsedSetData *)parsedMergeData:(id)input; +- (FSTParsedSetData *)parsedMergeData:(id)input fieldMask:(nullable NSArray<id> *)fieldMask; /** Parse update data from an updateData call. */ - (FSTParsedUpdateData *)parsedUpdateData:(id)input; diff --git a/Firestore/Source/API/FSTUserDataConverter.mm b/Firestore/Source/API/FSTUserDataConverter.mm index 2794398..6d01c75 100644 --- a/Firestore/Source/API/FSTUserDataConverter.mm +++ b/Firestore/Source/API/FSTUserDataConverter.mm @@ -412,7 +412,7 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) { return self; } -- (FSTParsedSetData *)parsedMergeData:(id)input { +- (FSTParsedSetData *)parsedMergeData:(id)input fieldMask:(nullable NSArray<id> *)fieldMask { // NOTE: The public API is typed as NSDictionary but we type 'input' as 'id' since we can't trust // Obj-C to verify the type for us. if (![input isKindOfClass:[NSDictionary class]]) { @@ -424,9 +424,45 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) { path:absl::make_unique<FieldPath>(FieldPath::EmptyPath())]; FSTObjectValue *updateData = (FSTObjectValue *)[self parseData:input context:context]; + FieldMask convertedFieldMask; + std::vector<FieldTransform> convertedFieldTransform; + + if (fieldMask) { + __block std::vector<FieldPath> fieldMaskPaths{}; + [fieldMask enumerateObjectsUsingBlock:^(id fieldPath, NSUInteger idx, BOOL *stop) { + FieldPath path{}; + + if ([fieldPath isKindOfClass:[NSString class]]) { + path = [FIRFieldPath pathWithDotSeparatedString:fieldPath].internalValue; + } else if ([fieldPath isKindOfClass:[FIRFieldPath class]]) { + path = ((FIRFieldPath *)fieldPath).internalValue; + } else { + FSTThrowInvalidArgument( + @"All elements in mergeFields: must be NSStrings or FIRFieldPaths."); + } + + if ([updateData valueForPath:path] == nil) { + FSTThrowInvalidArgument( + @"Field '%s' is specified in your field mask but missing from your input data.", + path.CanonicalString().c_str()); + } + + fieldMaskPaths.push_back(path); + }]; + convertedFieldMask = FieldMask(fieldMaskPaths); + std::copy_if(context.fieldTransforms->begin(), context.fieldTransforms->end(), + std::back_inserter(convertedFieldTransform), + [&](const FieldTransform &fieldTransform) { + return convertedFieldMask.covers(fieldTransform.path()); + }); + } else { + convertedFieldMask = FieldMask{*context.fieldMask}; + convertedFieldTransform = *context.fieldTransforms; + } + return [[FSTParsedSetData alloc] initWithData:updateData - fieldMask:FieldMask{*context.fieldMask} - fieldTransforms:*context.fieldTransforms]; + fieldMask:convertedFieldMask + fieldTransforms:convertedFieldTransform]; } - (FSTParsedSetData *)parsedSetData:(id)input { diff --git a/Firestore/Source/Core/FSTFirestoreClient.h b/Firestore/Source/Core/FSTFirestoreClient.h index 7285e65..94c2284 100644 --- a/Firestore/Source/Core/FSTFirestoreClient.h +++ b/Firestore/Source/Core/FSTFirestoreClient.h @@ -15,6 +15,7 @@ */ #import <Foundation/Foundation.h> +#include <memory> #import "Firestore/Source/Core/FSTTypes.h" #import "Firestore/Source/Core/FSTViewSnapshot.h" @@ -23,6 +24,7 @@ #include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h" #include "Firestore/core/src/firebase/firestore/core/database_info.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" +#include "Firestore/core/src/firebase/firestore/util/executor.h" @class FIRDocumentReference; @class FIRDocumentSnapshot; @@ -50,14 +52,15 @@ NS_ASSUME_NONNULL_BEGIN /** * Creates and returns a FSTFirestoreClient with the given parameters. * - * All callbacks and events will be triggered on the provided userDispatchQueue. + * All callbacks and events will be triggered on the provided userExecutor. */ -+ (instancetype)clientWithDatabaseInfo:(const firebase::firestore::core::DatabaseInfo &)databaseInfo - usePersistence:(BOOL)usePersistence - credentialsProvider:(firebase::firestore::auth::CredentialsProvider *) - credentialsProvider // no passing ownership - userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue - workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue; ++ (instancetype) +clientWithDatabaseInfo:(const firebase::firestore::core::DatabaseInfo &)databaseInfo + usePersistence:(BOOL)usePersistence + credentialsProvider:(firebase::firestore::auth::CredentialsProvider *) + credentialsProvider // no passing ownership + userExecutor:(std::unique_ptr<firebase::firestore::util::internal::Executor>)userExecutor + workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue; - (instancetype)init __attribute__((unavailable("Use static constructor method."))); @@ -111,7 +114,7 @@ NS_ASSUME_NONNULL_BEGIN * Dispatch queue for user callbacks / events. This will often be the "Main Dispatch Queue" of the * app but the developer can configure it to a different queue if they so choose. */ -@property(nonatomic, strong, readonly) FSTDispatchQueue *userDispatchQueue; +- (firebase::firestore::util::internal::Executor *)userExecutor; @end diff --git a/Firestore/Source/Core/FSTFirestoreClient.mm b/Firestore/Source/Core/FSTFirestoreClient.mm index 4f1a20b..cede958 100644 --- a/Firestore/Source/Core/FSTFirestoreClient.mm +++ b/Firestore/Source/Core/FSTFirestoreClient.mm @@ -18,6 +18,7 @@ #include <future> // NOLINT(build/c++11) #include <memory> +#include <utility> #import "FIRFirestoreErrors.h" #import "Firestore/Source/API/FIRDocumentReference+Internal.h" @@ -56,6 +57,9 @@ using firebase::firestore::auth::CredentialsProvider; using firebase::firestore::auth::User; using firebase::firestore::core::DatabaseInfo; using firebase::firestore::model::DatabaseId; +using firebase::firestore::model::DocumentKeySet; + +using firebase::firestore::util::internal::Executor; NS_ASSUME_NONNULL_BEGIN @@ -67,7 +71,7 @@ NS_ASSUME_NONNULL_BEGIN usePersistence:(BOOL)usePersistence credentialsProvider: (CredentialsProvider *)credentialsProvider // no passing ownership - userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue + userExecutor:(std::unique_ptr<Executor>)userExecutor workerDispatchQueue:(FSTDispatchQueue *)queue NS_DESIGNATED_INITIALIZER; @property(nonatomic, assign, readonly) const DatabaseInfo *databaseInfo; @@ -90,18 +94,24 @@ NS_ASSUME_NONNULL_BEGIN @end -@implementation FSTFirestoreClient +@implementation FSTFirestoreClient { + std::unique_ptr<Executor> _userExecutor; +} + +- (Executor *)userExecutor { + return _userExecutor.get(); +} + (instancetype)clientWithDatabaseInfo:(const DatabaseInfo &)databaseInfo usePersistence:(BOOL)usePersistence credentialsProvider: (CredentialsProvider *)credentialsProvider // no passing ownership - userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue + userExecutor:(std::unique_ptr<Executor>)userExecutor workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue { return [[FSTFirestoreClient alloc] initWithDatabaseInfo:databaseInfo usePersistence:usePersistence credentialsProvider:credentialsProvider - userDispatchQueue:userDispatchQueue + userExecutor:std::move(userExecutor) workerDispatchQueue:workerDispatchQueue]; } @@ -109,12 +119,12 @@ NS_ASSUME_NONNULL_BEGIN usePersistence:(BOOL)usePersistence credentialsProvider: (CredentialsProvider *)credentialsProvider // no passing ownership - userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue + userExecutor:(std::unique_ptr<Executor>)userExecutor workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue { if (self = [super init]) { _databaseInfo = databaseInfo; _credentialsProvider = credentialsProvider; - _userDispatchQueue = userDispatchQueue; + _userExecutor = std::move(userExecutor); _workerDispatchQueue = workerDispatchQueue; auto userPromise = std::make_shared<std::promise<User>>(); @@ -229,9 +239,7 @@ NS_ASSUME_NONNULL_BEGIN [self.workerDispatchQueue dispatchAsync:^{ [self.remoteStore disableNetwork]; if (completion) { - [self.userDispatchQueue dispatchAsync:^{ - completion(nil); - }]; + self->_userExecutor->Execute([=] { completion(nil); }); } }]; } @@ -240,9 +248,7 @@ NS_ASSUME_NONNULL_BEGIN [self.workerDispatchQueue dispatchAsync:^{ [self.remoteStore enableNetwork]; if (completion) { - [self.userDispatchQueue dispatchAsync:^{ - completion(nil); - }]; + self->_userExecutor->Execute([=] { completion(nil); }); } }]; } @@ -254,9 +260,7 @@ NS_ASSUME_NONNULL_BEGIN [self.remoteStore shutdown]; [self.persistence shutdown]; if (completion) { - [self.userDispatchQueue dispatchAsync:^{ - completion(nil); - }]; + self->_userExecutor->Execute([=] { completion(nil); }); } }]; } @@ -311,11 +315,9 @@ NS_ASSUME_NONNULL_BEGIN completion:(void (^)(FIRQuerySnapshot *_Nullable query, NSError *_Nullable error))completion { [self.workerDispatchQueue dispatchAsync:^{ - FSTDocumentDictionary *docs = [self.localStore executeQuery:query.query]; - FSTDocumentKeySet *remoteKeys = [FSTDocumentKeySet keySet]; - FSTView *view = [[FSTView alloc] initWithQuery:query.query remoteDocuments:remoteKeys]; + FSTView *view = [[FSTView alloc] initWithQuery:query.query remoteDocuments:DocumentKeySet{}]; FSTViewDocumentChanges *viewDocChanges = [view computeChangesWithDocuments:docs]; FSTViewChange *viewChange = [view applyChangesToDocuments:viewDocChanges]; FSTAssert(viewChange.limboChanges.count == 0, @@ -339,18 +341,14 @@ NS_ASSUME_NONNULL_BEGIN [self.workerDispatchQueue dispatchAsync:^{ if (mutations.count == 0) { if (completion) { - [self.userDispatchQueue dispatchAsync:^{ - completion(nil); - }]; + self->_userExecutor->Execute([=] { completion(nil); }); } } else { [self.syncEngine writeMutations:mutations completion:^(NSError *error) { // Dispatch the result back onto the user dispatch queue. if (completion) { - [self.userDispatchQueue dispatchAsync:^{ - completion(error); - }]; + self->_userExecutor->Execute([=] { completion(error); }); } }]; } @@ -361,17 +359,16 @@ NS_ASSUME_NONNULL_BEGIN updateBlock:(FSTTransactionBlock)updateBlock completion:(FSTVoidIDErrorBlock)completion { [self.workerDispatchQueue dispatchAsync:^{ - [self.syncEngine transactionWithRetries:retries - workerDispatchQueue:self.workerDispatchQueue - updateBlock:updateBlock - completion:^(id _Nullable result, NSError *_Nullable error) { - // Dispatch the result back onto the user dispatch queue. - if (completion) { - [self.userDispatchQueue dispatchAsync:^{ - completion(result, error); - }]; - } - }]; + [self.syncEngine + transactionWithRetries:retries + workerDispatchQueue:self.workerDispatchQueue + updateBlock:updateBlock + completion:^(id _Nullable result, NSError *_Nullable error) { + // Dispatch the result back onto the user dispatch queue. + if (completion) { + self->_userExecutor->Execute([=] { completion(result, error); }); + } + }]; }]; } diff --git a/Firestore/Source/Core/FSTQuery.mm b/Firestore/Source/Core/FSTQuery.mm index 0cd11e8..d3961e8 100644 --- a/Firestore/Source/Core/FSTQuery.mm +++ b/Firestore/Source/Core/FSTQuery.mm @@ -28,6 +28,7 @@ #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "Firestore/core/src/firebase/firestore/util/hashing.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; @@ -259,7 +260,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe } - (NSUInteger)hash { - return _field.Hash(); + return util::Hash(_field); } @end @@ -305,7 +306,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe } - (NSUInteger)hash { - return _field.Hash(); + return util::Hash(_field); } @end diff --git a/Firestore/Source/Core/FSTSnapshotVersion.h b/Firestore/Source/Core/FSTSnapshotVersion.h deleted file mode 100644 index 8649d40..0000000 --- a/Firestore/Source/Core/FSTSnapshotVersion.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 <Foundation/Foundation.h> - -NS_ASSUME_NONNULL_BEGIN - -@class FIRTimestamp; - -/** - * A version of a document in Firestore. This corresponds to the version timestamp, such as - * update_time or read_time. - */ -@interface FSTSnapshotVersion : NSObject <NSCopying> - -/** Creates a new version that is smaller than all other versions. */ -+ (instancetype)noVersion; - -/** Creates a new version representing the given timestamp. */ -+ (instancetype)versionWithTimestamp:(FIRTimestamp *)timestamp; - -- (instancetype)init NS_UNAVAILABLE; - -- (NSComparisonResult)compare:(FSTSnapshotVersion *)other; - -@property(nonatomic, strong, readonly) FIRTimestamp *timestamp; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Core/FSTSnapshotVersion.mm b/Firestore/Source/Core/FSTSnapshotVersion.mm deleted file mode 100644 index 58b2be4..0000000 --- a/Firestore/Source/Core/FSTSnapshotVersion.mm +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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 "Firestore/Source/Core/FSTSnapshotVersion.h" - -#import "FIRTimestamp.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation FSTSnapshotVersion - -+ (instancetype)noVersion { - static FSTSnapshotVersion *min; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - FIRTimestamp *timestamp = [[FIRTimestamp alloc] initWithSeconds:0 nanoseconds:0]; - min = [FSTSnapshotVersion versionWithTimestamp:timestamp]; - }); - return min; -} - -+ (instancetype)versionWithTimestamp:(FIRTimestamp *)timestamp { - return [[FSTSnapshotVersion alloc] initWithTimestamp:timestamp]; -} - -- (instancetype)initWithTimestamp:(FIRTimestamp *)timestamp { - self = [super init]; - if (self) { - _timestamp = timestamp; - } - return self; -} - -#pragma mark - NSObject methods - -- (BOOL)isEqual:(id)object { - if (self == object) { - return YES; - } - if (![object isKindOfClass:[FSTSnapshotVersion class]]) { - return NO; - } - return [self.timestamp isEqual:((FSTSnapshotVersion *)object).timestamp]; -} - -- (NSUInteger)hash { - return self.timestamp.hash; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"<FSTSnapshotVersion: %@>", self.timestamp]; -} - -- (id)copyWithZone:(NSZone *_Nullable)zone { - // Implements NSCopying without actually copying because timestamps are immutable. - return self; -} - -#pragma mark - Public methods - -- (NSComparisonResult)compare:(FSTSnapshotVersion *)other { - return [self.timestamp compare:other.timestamp]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Core/FSTSyncEngine.mm b/Firestore/Source/Core/FSTSyncEngine.mm index 138fb41..ed97d6c 100644 --- a/Firestore/Source/Core/FSTSyncEngine.mm +++ b/Firestore/Source/Core/FSTSyncEngine.mm @@ -21,10 +21,10 @@ #include <map> #include <set> #include <unordered_map> +#include <utility> #import "FIRFirestoreErrors.h" #import "Firestore/Source/Core/FSTQuery.h" -#import "Firestore/Source/Core/FSTSnapshotVersion.h" #import "Firestore/Source/Core/FSTTransaction.h" #import "Firestore/Source/Core/FSTView.h" #import "Firestore/Source/Core/FSTViewSnapshot.h" @@ -45,12 +45,15 @@ #include "Firestore/core/src/firebase/firestore/auth/user.h" #include "Firestore/core/src/firebase/firestore/core/target_id_generator.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" using firebase::firestore::auth::HashUser; using firebase::firestore::auth::User; using firebase::firestore::core::TargetIdGenerator; using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::SnapshotVersion; using firebase::firestore::model::TargetId; +using firebase::firestore::model::DocumentKeySet; NS_ASSUME_NONNULL_BEGIN @@ -184,9 +187,9 @@ static const FSTListenSequenceNumber kIrrelevantSequenceNumber = -1; FSTQueryData *queryData = [self.localStore allocateQuery:query]; FSTDocumentDictionary *docs = [self.localStore executeQuery:query]; - FSTDocumentKeySet *remoteKeys = [self.localStore remoteDocumentKeysForTarget:queryData.targetID]; + DocumentKeySet remoteKeys = [self.localStore remoteDocumentKeysForTarget:queryData.targetID]; - FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:remoteKeys]; + FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:std::move(remoteKeys)]; FSTViewDocumentChanges *viewDocChanges = [view computeChangesWithDocuments:docs]; FSTViewChange *viewChange = [view applyChangesToDocuments:viewDocChanges]; FSTAssert(viewChange.limboChanges.count == 0, @@ -301,8 +304,7 @@ static const FSTListenSequenceNumber kIrrelevantSequenceNumber = -1; if (iter == self->_limboKeysByTarget.end()) { FSTQueryView *qv = self.queryViewsByTarget[targetID]; FSTAssert(qv, @"Missing queryview for non-limbo query: %i", [targetID intValue]); - [remoteEvent filterUpdatesFromTargetChange:targetChange - existingDocuments:qv.view.syncedDocuments]; + [targetChange.mapping filterUpdatesUsingExistingKeys:qv.view.syncedDocuments]; } else { [remoteEvent synthesizeDeleteForLimboTargetChange:targetChange key:iter->second]; } @@ -346,10 +348,15 @@ static const FSTListenSequenceNumber kIrrelevantSequenceNumber = -1; NSMutableDictionary<NSNumber *, FSTTargetChange *> *targetChanges = [NSMutableDictionary dictionary]; FSTDeletedDocument *doc = - [FSTDeletedDocument documentWithKey:limboKey version:[FSTSnapshotVersion noVersion]]; - FSTRemoteEvent *event = [FSTRemoteEvent eventWithSnapshotVersion:[FSTSnapshotVersion noVersion] - targetChanges:targetChanges - documentUpdates:{{limboKey, doc}}]; + [FSTDeletedDocument documentWithKey:limboKey version:SnapshotVersion::None()]; + DocumentKeySet limboDocuments = DocumentKeySet{doc.key}; + FSTRemoteEvent *event = + [[FSTRemoteEvent alloc] initWithSnapshotVersion:SnapshotVersion::None() + targetChanges:targetChanges + documentUpdates:{ + { limboKey, doc } + } + limboDocuments:std::move(limboDocuments)]; [self applyRemoteEvent:event]; } else { FSTQueryView *queryView = self.queryViewsByTarget[@(targetID)]; diff --git a/Firestore/Source/Core/FSTTransaction.mm b/Firestore/Source/Core/FSTTransaction.mm index 4aabd5a..5c36b20 100644 --- a/Firestore/Source/Core/FSTTransaction.mm +++ b/Firestore/Source/Core/FSTTransaction.mm @@ -23,19 +23,21 @@ #import "FIRFirestoreErrors.h" #import "Firestore/Source/API/FSTUserDataConverter.h" -#import "Firestore/Source/Core/FSTSnapshotVersion.h" #import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentKeySet.h" #import "Firestore/Source/Model/FSTMutation.h" #import "Firestore/Source/Remote/FSTDatastore.h" #import "Firestore/Source/Util/FSTAssert.h" #import "Firestore/Source/Util/FSTUsageValidation.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" #include "Firestore/core/src/firebase/firestore/model/precondition.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" using firebase::firestore::model::DocumentKey; using firebase::firestore::model::Precondition; +using firebase::firestore::model::SnapshotVersion; +using firebase::firestore::model::DocumentKeySet; NS_ASSUME_NONNULL_BEGIN @@ -53,7 +55,7 @@ NS_ASSUME_NONNULL_BEGIN @end @implementation FSTTransaction { - std::map<DocumentKey, FSTSnapshotVersion *> _readVersions; + std::map<DocumentKey, SnapshotVersion> _readVersions; } + (instancetype)transactionWithDatastore:(FSTDatastore *)datastore { @@ -79,11 +81,11 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)recordVersionForDocument:(FSTMaybeDocument *)doc error:(NSError **)error { FSTAssert(error != nil, @"nil error parameter"); *error = nil; - FSTSnapshotVersion *docVersion = doc.version; + SnapshotVersion docVersion = doc.version; if ([doc isKindOfClass:[FSTDeletedDocument class]]) { // For deleted docs, we must record an explicit no version to build the right precondition // when writing. - docVersion = [FSTSnapshotVersion noVersion]; + docVersion = SnapshotVersion::None(); } if (_readVersions.find(doc.key) == _readVersions.end()) { _readVersions[doc.key] = docVersion; @@ -159,8 +161,8 @@ NS_ASSUME_NONNULL_BEGIN return Precondition::Exists(true); } - FSTSnapshotVersion *version = iter->second; - if ([version isEqual:[FSTSnapshotVersion noVersion]]) { + const SnapshotVersion &version = iter->second; + if (version == SnapshotVersion::None()) { // The document was read, but doesn't exist. // Return an error because the precondition is impossible if (error) { @@ -200,7 +202,7 @@ NS_ASSUME_NONNULL_BEGIN precondition:[self preconditionForDocumentKey:key]] ]]; // Since the delete will be applied before all following writes, we need to ensure that the // precondition for the next write will be exists without timestamp. - _readVersions[key] = [FSTSnapshotVersion noVersion]; + _readVersions[key] = SnapshotVersion::None(); } - (void)commitWithCompletion:(FSTVoidErrorBlock)completion { @@ -215,15 +217,15 @@ NS_ASSUME_NONNULL_BEGIN } // Make a list of read documents that haven't been written. - FSTDocumentKeySet *unwritten = [FSTDocumentKeySet keySet]; + DocumentKeySet unwritten; for (const auto &kv : _readVersions) { - unwritten = [unwritten setByAddingObject:kv.first]; + unwritten = unwritten.insert(kv.first); }; // For each mutation, note that the doc was written. for (FSTMutation *mutation in self.mutations) { - unwritten = [unwritten setByRemovingObject:mutation.key]; + unwritten = unwritten.erase(mutation.key); } - if (unwritten.count) { + if (!unwritten.empty()) { // TODO(klimt): This is a temporary restriction, until "verify" is supported on the backend. completion([NSError errorWithDomain:FIRFirestoreErrorDomain diff --git a/Firestore/Source/Core/FSTView.h b/Firestore/Source/Core/FSTView.h index 431b863..fc6cead 100644 --- a/Firestore/Source/Core/FSTView.h +++ b/Firestore/Source/Core/FSTView.h @@ -18,9 +18,9 @@ #import "Firestore/Source/Core/FSTTypes.h" #import "Firestore/Source/Model/FSTDocumentDictionary.h" -#import "Firestore/Source/Model/FSTDocumentKeySet.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" @class FSTDocumentSet; @class FSTDocumentViewChangeSet; @@ -38,6 +38,8 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init NS_UNAVAILABLE; +- (const firebase::firestore::model::DocumentKeySet &)mutatedKeys; + /** The new set of docs that should be in the view. */ @property(nonatomic, strong, readonly) FSTDocumentSet *documentSet; @@ -50,8 +52,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property(nonatomic, assign, readonly) BOOL needsRefill; -@property(nonatomic, strong, readonly) FSTDocumentKeySet *mutatedKeys; - @end #pragma mark - FSTLimboDocumentChange @@ -97,7 +97,8 @@ typedef NS_ENUM(NSInteger, FSTLimboDocumentChangeType) { - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithQuery:(FSTQuery *)query - remoteDocuments:(FSTDocumentKeySet *)remoteDocuments NS_DESIGNATED_INITIALIZER; + remoteDocuments:(firebase::firestore::model::DocumentKeySet)remoteDocuments + NS_DESIGNATED_INITIALIZER; /** * Iterates over a set of doc changes, applies the query limit, and computes what the new results @@ -152,7 +153,7 @@ typedef NS_ENUM(NSInteger, FSTLimboDocumentChangeType) { * The set of remote documents that the server has told us belongs to the target associated with * this view. */ -@property(nonatomic, strong, readonly) FSTDocumentKeySet *syncedDocuments; +- (const firebase::firestore::model::DocumentKeySet &)syncedDocuments; @end diff --git a/Firestore/Source/Core/FSTView.mm b/Firestore/Source/Core/FSTView.mm index d87951a..d254a82 100644 --- a/Firestore/Source/Core/FSTView.mm +++ b/Firestore/Source/Core/FSTView.mm @@ -29,6 +29,7 @@ #include "Firestore/core/src/firebase/firestore/model/document_key.h" using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::DocumentKeySet; NS_ASSUME_NONNULL_BEGIN @@ -40,26 +41,32 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithDocumentSet:(FSTDocumentSet *)documentSet changeSet:(FSTDocumentViewChangeSet *)changeSet needsRefill:(BOOL)needsRefill - mutatedKeys:(FSTDocumentKeySet *)mutatedKeys NS_DESIGNATED_INITIALIZER; + mutatedKeys:(DocumentKeySet)mutatedKeys NS_DESIGNATED_INITIALIZER; @end -@implementation FSTViewDocumentChanges +@implementation FSTViewDocumentChanges { + DocumentKeySet _mutatedKeys; +} - (instancetype)initWithDocumentSet:(FSTDocumentSet *)documentSet changeSet:(FSTDocumentViewChangeSet *)changeSet needsRefill:(BOOL)needsRefill - mutatedKeys:(FSTDocumentKeySet *)mutatedKeys { + mutatedKeys:(DocumentKeySet)mutatedKeys { self = [super init]; if (self) { _documentSet = documentSet; _changeSet = changeSet; _needsRefill = needsRefill; - _mutatedKeys = mutatedKeys; + _mutatedKeys = std::move(mutatedKeys); } return self; } +- (const DocumentKeySet &)mutatedKeys { + return _mutatedKeys; +} + @end #pragma mark - FSTLimboDocumentChange @@ -165,32 +172,33 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang @property(nonatomic, strong) FSTDocumentSet *documentSet; -/** Documents included in the remote target. */ -@property(nonatomic, strong) FSTDocumentKeySet *syncedDocuments; - -/** Documents in the view but not in the remote target */ -@property(nonatomic, strong) FSTDocumentKeySet *limboDocuments; +@end -/** Document Keys that have local changes. */ -@property(nonatomic, strong) FSTDocumentKeySet *mutatedKeys; +@implementation FSTView { + /** Documents included in the remote target. */ + DocumentKeySet _syncedDocuments; -@end + /** Documents in the view but not in the remote target */ + DocumentKeySet _limboDocuments; -@implementation FSTView + /** Document Keys that have local changes. */ + DocumentKeySet _mutatedKeys; +} -- (instancetype)initWithQuery:(FSTQuery *)query - remoteDocuments:(nonnull FSTDocumentKeySet *)remoteDocuments { +- (instancetype)initWithQuery:(FSTQuery *)query remoteDocuments:(DocumentKeySet)remoteDocuments { self = [super init]; if (self) { _query = query; _documentSet = [FSTDocumentSet documentSetWithComparator:query.comparator]; - _syncedDocuments = remoteDocuments; - _limboDocuments = [FSTDocumentKeySet keySet]; - _mutatedKeys = [FSTDocumentKeySet keySet]; + _syncedDocuments = std::move(remoteDocuments); } return self; } +- (const DocumentKeySet &)syncedDocuments { + return _syncedDocuments; +} + - (FSTViewDocumentChanges *)computeChangesWithDocuments:(FSTMaybeDocumentDictionary *)docChanges { return [self computeChangesWithDocuments:docChanges previousChanges:nil]; } @@ -202,8 +210,8 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang previousChanges ? previousChanges.changeSet : [FSTDocumentViewChangeSet changeSet]; FSTDocumentSet *oldDocumentSet = previousChanges ? previousChanges.documentSet : self.documentSet; - __block FSTDocumentKeySet *newMutatedKeys = - previousChanges ? previousChanges.mutatedKeys : self.mutatedKeys; + __block DocumentKeySet newMutatedKeys = + previousChanges ? previousChanges.mutatedKeys : _mutatedKeys; __block FSTDocumentSet *newDocumentSet = oldDocumentSet; __block BOOL needsRefill = NO; @@ -236,13 +244,13 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang if (newDoc) { newDocumentSet = [newDocumentSet documentSetByAddingDocument:newDoc]; if (newDoc.hasLocalMutations) { - newMutatedKeys = [newMutatedKeys setByAddingObject:key]; + newMutatedKeys = newMutatedKeys.insert(key); } else { - newMutatedKeys = [newMutatedKeys setByRemovingObject:key]; + newMutatedKeys = newMutatedKeys.erase(key); } } else { newDocumentSet = [newDocumentSet documentSetByRemovingKey:key]; - newMutatedKeys = [newMutatedKeys setByRemovingObject:key]; + newMutatedKeys = newMutatedKeys.erase(key); } // Calculate change @@ -311,7 +319,7 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang FSTDocumentSet *oldDocuments = self.documentSet; self.documentSet = docChanges.documentSet; - self.mutatedKeys = docChanges.mutatedKeys; + _mutatedKeys = docChanges.mutatedKeys; // Sort changes based on type and query comparator. NSArray<FSTDocumentViewChange *> *changes = [docChanges.changeSet changes]; @@ -325,7 +333,7 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang }]; [self applyTargetChange:targetChange]; NSArray<FSTLimboDocumentChange *> *limboChanges = [self updateLimboDocuments]; - BOOL synced = self.limboDocuments.count == 0 && self.isCurrent; + BOOL synced = _limboDocuments.empty() && self.isCurrent; FSTSyncState newSyncState = synced ? FSTSyncStateSynced : FSTSyncStateLocal; BOOL syncStateChanged = newSyncState != self.syncState; self.syncState = newSyncState; @@ -340,7 +348,7 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang oldDocuments:oldDocuments documentChanges:changes fromCache:newSyncState == FSTSyncStateLocal - hasPendingWrites:!docChanges.mutatedKeys.isEmpty + hasPendingWrites:!docChanges.mutatedKeys.empty() syncStateChanged:syncStateChanged]; return [FSTViewChange changeWithSnapshot:snapshot limboChanges:limboChanges]; @@ -358,7 +366,7 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang initWithDocumentSet:self.documentSet changeSet:[FSTDocumentViewChangeSet changeSet] needsRefill:NO - mutatedKeys:self.mutatedKeys]]; + mutatedKeys:_mutatedKeys]]; } else { // No effect, just return a no-op FSTViewChange. return [[FSTViewChange alloc] initWithSnapshot:nil limboChanges:@[]]; @@ -370,7 +378,7 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang /** Returns whether the doc for the given key should be in limbo. */ - (BOOL)shouldBeLimboDocumentKey:(const DocumentKey &)key { // If the remote end says it's part of this query, it's not in limbo. - if ([self.syncedDocuments containsObject:key]) { + if (_syncedDocuments.contains(key)) { return NO; } // The local store doesn't think it's a result, so it shouldn't be in limbo. @@ -395,16 +403,14 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang if (targetChange) { FSTTargetMapping *targetMapping = targetChange.mapping; if ([targetMapping isKindOfClass:[FSTResetMapping class]]) { - self.syncedDocuments = ((FSTResetMapping *)targetMapping).documents; + _syncedDocuments = ((FSTResetMapping *)targetMapping).documents; } else if ([targetMapping isKindOfClass:[FSTUpdateMapping class]]) { - [((FSTUpdateMapping *)targetMapping).addedDocuments - enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) { - self.syncedDocuments = [self.syncedDocuments setByAddingObject:key]; - }]; - [((FSTUpdateMapping *)targetMapping).removedDocuments - enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) { - self.syncedDocuments = [self.syncedDocuments setByRemovingObject:key]; - }]; + for (const DocumentKey &key : ((FSTUpdateMapping *)targetMapping).addedDocuments) { + _syncedDocuments = _syncedDocuments.insert(key); + } + for (const DocumentKey &key : ((FSTUpdateMapping *)targetMapping).removedDocuments) { + _syncedDocuments = _syncedDocuments.erase(key); + } } switch (targetChange.currentStatusUpdate) { @@ -428,29 +434,29 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang } // TODO(klimt): Do this incrementally so that it's not quadratic when updating many documents. - FSTDocumentKeySet *oldLimboDocuments = self.limboDocuments; - self.limboDocuments = [FSTDocumentKeySet keySet]; + DocumentKeySet oldLimboDocuments = std::move(_limboDocuments); + _limboDocuments = DocumentKeySet{}; for (FSTDocument *doc in self.documentSet.documentEnumerator) { if ([self shouldBeLimboDocumentKey:doc.key]) { - self.limboDocuments = [self.limboDocuments setByAddingObject:doc.key]; + _limboDocuments = _limboDocuments.insert(doc.key); } } // Diff the new limbo docs with the old limbo docs. NSMutableArray<FSTLimboDocumentChange *> *changes = - [NSMutableArray arrayWithCapacity:(oldLimboDocuments.count + self.limboDocuments.count)]; - [oldLimboDocuments enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) { - if (![self.limboDocuments containsObject:key]) { + [NSMutableArray arrayWithCapacity:(oldLimboDocuments.size() + _limboDocuments.size())]; + for (const DocumentKey &key : oldLimboDocuments) { + if (!_limboDocuments.contains(key)) { [changes addObject:[FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeRemoved key:key]]; } - }]; - [self.limboDocuments enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) { - if (![oldLimboDocuments containsObject:key]) { + } + for (const DocumentKey &key : _limboDocuments) { + if (!oldLimboDocuments.contains(key)) { [changes addObject:[FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeAdded key:key]]; } - }]; + } return changes; } diff --git a/Firestore/Source/Local/FSTLevelDB.mm b/Firestore/Source/Local/FSTLevelDB.mm index fae85e7..bc2f2eb 100644 --- a/Firestore/Source/Local/FSTLevelDB.mm +++ b/Firestore/Source/Local/FSTLevelDB.mm @@ -243,6 +243,10 @@ using leveldb::WriteOptions; _ptr.reset(); } +- (_Nullable id<FSTReferenceDelegate>)referenceDelegate { + return nil; +} + #pragma mark - Error and Status + (nullable NSError *)errorWithStatus:(Status)status description:(NSString *)description, ... { diff --git a/Firestore/Source/Local/FSTLevelDBMutationQueue.mm b/Firestore/Source/Local/FSTLevelDBMutationQueue.mm index 75c3cf6..2c9f68d 100644 --- a/Firestore/Source/Local/FSTLevelDBMutationQueue.mm +++ b/Firestore/Source/Local/FSTLevelDBMutationQueue.mm @@ -511,6 +511,7 @@ using leveldb::WriteOptions; documentKey:mutation.key batchID:batchID]; _db.currentTransaction->Delete(key); + [_db.referenceDelegate removeMutationReference:mutation.key]; [garbageCollector addPotentialGarbageKey:mutation.key]; } } diff --git a/Firestore/Source/Local/FSTLevelDBQueryCache.mm b/Firestore/Source/Local/FSTLevelDBQueryCache.mm index 5fde7d7..68b6f98 100644 --- a/Firestore/Source/Local/FSTLevelDBQueryCache.mm +++ b/Firestore/Source/Local/FSTLevelDBQueryCache.mm @@ -18,6 +18,7 @@ #include <memory> #include <string> +#include <utility> #import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h" #import "Firestore/Source/Core/FSTQuery.h" @@ -28,6 +29,7 @@ #import "Firestore/Source/Util/FSTAssert.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" #include "absl/strings/match.h" NS_ASSUME_NONNULL_BEGIN @@ -35,9 +37,11 @@ NS_ASSUME_NONNULL_BEGIN using firebase::firestore::local::LevelDbTransaction; using Firestore::StringView; using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::SnapshotVersion; using leveldb::DB; using leveldb::Slice; using leveldb::Status; +using firebase::firestore::model::DocumentKeySet; @interface FSTLevelDBQueryCache () @@ -55,7 +59,7 @@ using leveldb::Status; * The last received snapshot version. This is part of `metadata` but we store it separately to * avoid extra conversion to/from GPBTimestamp. */ - FSTSnapshotVersion *_lastRemoteSnapshotVersion; + SnapshotVersion _lastRemoteSnapshotVersion; } + (nullable FSTPBTargetGlobal *)readTargetMetadataWithTransaction: @@ -135,13 +139,14 @@ using leveldb::Status; return self.metadata.highestListenSequenceNumber; } -- (FSTSnapshotVersion *)lastRemoteSnapshotVersion { +- (const SnapshotVersion &)lastRemoteSnapshotVersion { return _lastRemoteSnapshotVersion; } -- (void)setLastRemoteSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion { - _lastRemoteSnapshotVersion = snapshotVersion; - self.metadata.lastRemoteSnapshotVersion = [self.serializer encodedVersion:snapshotVersion]; +- (void)setLastRemoteSnapshotVersion:(SnapshotVersion)snapshotVersion { + _lastRemoteSnapshotVersion = std::move(snapshotVersion); + self.metadata.lastRemoteSnapshotVersion = + [self.serializer encodedVersion:_lastRemoteSnapshotVersion]; _db.currentTransaction->Put([FSTLevelDBTargetGlobalKey key], self.metadata); } @@ -278,30 +283,30 @@ using leveldb::Status; #pragma mark Matching Key tracking -- (void)addMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID { +- (void)addMatchingKeys:(const DocumentKeySet &)keys forTargetID:(FSTTargetID)targetID { // Store an empty value in the index which is equivalent to serializing a GPBEmpty message. In the // future if we wanted to store some other kind of value here, we can parse these empty values as // with some other protocol buffer (and the parser will see all default values). std::string emptyBuffer; - [keys enumerateObjectsUsingBlock:^(FSTDocumentKey *documentKey, BOOL *stop) { + for (const DocumentKey &key : keys) { self->_db.currentTransaction->Put( - [FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:documentKey], - emptyBuffer); + [FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:key], emptyBuffer); self->_db.currentTransaction->Put( - [FSTLevelDBDocumentTargetKey keyWithDocumentKey:documentKey targetID:targetID], - emptyBuffer); - }]; + [FSTLevelDBDocumentTargetKey keyWithDocumentKey:key targetID:targetID], emptyBuffer); + [self->_db.referenceDelegate addReference:key target:targetID]; + }; } -- (void)removeMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID { - [keys enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) { +- (void)removeMatchingKeys:(const DocumentKeySet &)keys forTargetID:(FSTTargetID)targetID { + for (const DocumentKey &key : keys) { self->_db.currentTransaction->Delete( [FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:key]); self->_db.currentTransaction->Delete( [FSTLevelDBDocumentTargetKey keyWithDocumentKey:key targetID:targetID]); + [self->_db.referenceDelegate removeReference:key target:targetID]; [self.garbageCollector addPotentialGarbageKey:key]; - }]; + } } - (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID { @@ -327,12 +332,12 @@ using leveldb::Status; } } -- (FSTDocumentKeySet *)matchingKeysForTargetID:(FSTTargetID)targetID { +- (DocumentKeySet)matchingKeysForTargetID:(FSTTargetID)targetID { std::string indexPrefix = [FSTLevelDBTargetDocumentKey keyPrefixWithTargetID:targetID]; auto indexIterator = _db.currentTransaction->NewIterator(); indexIterator->Seek(indexPrefix); - FSTDocumentKeySet *result = [FSTDocumentKeySet keySet]; + DocumentKeySet result; FSTLevelDBTargetDocumentKey *rowKey = [[FSTLevelDBTargetDocumentKey alloc] init]; for (; indexIterator->Valid(); indexIterator->Next()) { absl::string_view indexKey = indexIterator->key(); @@ -342,7 +347,7 @@ using leveldb::Status; break; } - result = [result setByAddingObject:rowKey.documentKey]; + result = result.insert(rowKey.documentKey); } return result; diff --git a/Firestore/Source/Local/FSTLocalDocumentsView.h b/Firestore/Source/Local/FSTLocalDocumentsView.h index e75e0f3..bb5bb22 100644 --- a/Firestore/Source/Local/FSTLocalDocumentsView.h +++ b/Firestore/Source/Local/FSTLocalDocumentsView.h @@ -17,9 +17,9 @@ #import <Foundation/Foundation.h> #import "Firestore/Source/Model/FSTDocumentDictionary.h" -#import "Firestore/Source/Model/FSTDocumentKeySet.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" @class FSTMaybeDocument; @class FSTQuery; @@ -53,7 +53,8 @@ NS_ASSUME_NONNULL_BEGIN * If we don't have cached state for a document in `keys`, a FSTDeletedDocument will be stored * for that key in the resulting set. */ -- (FSTMaybeDocumentDictionary *)documentsForKeys:(FSTDocumentKeySet *)keys; +- (FSTMaybeDocumentDictionary *)documentsForKeys: + (const firebase::firestore::model::DocumentKeySet &)keys; /** Performs a query against the local view of all documents. */ - (FSTDocumentDictionary *)documentsMatchingQuery:(FSTQuery *)query; diff --git a/Firestore/Source/Local/FSTLocalDocumentsView.mm b/Firestore/Source/Local/FSTLocalDocumentsView.mm index e9b9423..471840a 100644 --- a/Firestore/Source/Local/FSTLocalDocumentsView.mm +++ b/Firestore/Source/Local/FSTLocalDocumentsView.mm @@ -17,7 +17,6 @@ #import "Firestore/Source/Local/FSTLocalDocumentsView.h" #import "Firestore/Source/Core/FSTQuery.h" -#import "Firestore/Source/Core/FSTSnapshotVersion.h" #import "Firestore/Source/Local/FSTMutationQueue.h" #import "Firestore/Source/Local/FSTRemoteDocumentCache.h" #import "Firestore/Source/Model/FSTDocument.h" @@ -28,9 +27,12 @@ #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" using firebase::firestore::model::DocumentKey; using firebase::firestore::model::ResourcePath; +using firebase::firestore::model::SnapshotVersion; +using firebase::firestore::model::DocumentKeySet; NS_ASSUME_NONNULL_BEGIN @@ -64,14 +66,14 @@ NS_ASSUME_NONNULL_BEGIN return [self localDocument:remoteDoc key:key]; } -- (FSTMaybeDocumentDictionary *)documentsForKeys:(FSTDocumentKeySet *)keys { +- (FSTMaybeDocumentDictionary *)documentsForKeys:(const DocumentKeySet &)keys { FSTMaybeDocumentDictionary *results = [FSTMaybeDocumentDictionary maybeDocumentDictionary]; - for (FSTDocumentKey *key in keys.objectEnumerator) { + for (const DocumentKey &key : keys) { // TODO(mikelehen): PERF: Consider fetching all remote documents at once rather than one-by-one. FSTMaybeDocument *maybeDoc = [self documentForKey:key]; // TODO(http://b/32275378): Don't conflate missing / deleted. if (!maybeDoc) { - maybeDoc = [FSTDeletedDocument documentWithKey:key version:[FSTSnapshotVersion noVersion]]; + maybeDoc = [FSTDeletedDocument documentWithKey:key version:SnapshotVersion::None()]; } results = [results dictionaryBySettingObject:maybeDoc forKey:key]; } @@ -105,7 +107,7 @@ NS_ASSUME_NONNULL_BEGIN // Now use the mutation queue to discover any other documents that may match the query after // applying mutations. - FSTDocumentKeySet *matchingKeys = [FSTDocumentKeySet keySet]; + DocumentKeySet matchingKeys; NSArray<FSTMutationBatch *> *matchingMutationBatches = [self.mutationQueue allMutationBatchesAffectingQuery:query]; for (FSTMutationBatch *batch in matchingMutationBatches) { @@ -114,13 +116,13 @@ NS_ASSUME_NONNULL_BEGIN // If the key is already in the results, we can skip it. if (![results containsKey:mutation.key]) { - matchingKeys = [matchingKeys setByAddingObject:mutation.key]; + matchingKeys = matchingKeys.insert(mutation.key); } } } // Now add in results for the matchingKeys. - for (FSTDocumentKey *key in matchingKeys.objectEnumerator) { + for (const DocumentKey &key : matchingKeys) { FSTMaybeDocument *doc = [self documentForKey:key]; if ([doc isKindOfClass:[FSTDocument class]]) { results = [results dictionaryBySettingObject:(FSTDocument *)doc forKey:key]; diff --git a/Firestore/Source/Local/FSTLocalSerializer.h b/Firestore/Source/Local/FSTLocalSerializer.h index 6ca7f01..b75f3e6 100644 --- a/Firestore/Source/Local/FSTLocalSerializer.h +++ b/Firestore/Source/Local/FSTLocalSerializer.h @@ -16,11 +16,12 @@ #import <Foundation/Foundation.h> +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" + @class FSTMaybeDocument; @class FSTMutationBatch; @class FSTQueryData; @class FSTSerializerBeta; -@class FSTSnapshotVersion; @class FSTPBMaybeDocument; @class FSTPBTarget; @@ -61,11 +62,11 @@ NS_ASSUME_NONNULL_BEGIN /** Decodes an FSTPBTarget proto from local storage into an FSTQueryData model. */ - (FSTQueryData *)decodedQueryData:(FSTPBTarget *)target; -/** Encodes an FSTSnapshotVersion model into a GPBTimestamp proto. */ -- (GPBTimestamp *)encodedVersion:(FSTSnapshotVersion *)version; +/** Encodes a SnapshotVersion model into a GPBTimestamp proto. */ +- (GPBTimestamp *)encodedVersion:(const firebase::firestore::model::SnapshotVersion &)version; -/** Decodes a GPBTimestamp proto into a FSTSnapshotVersion model. */ -- (FSTSnapshotVersion *)decodedVersion:(GPBTimestamp *)version; +/** Decodes a GPBTimestamp proto into a SnapshotVersion model. */ +- (firebase::firestore::model::SnapshotVersion)decodedVersion:(GPBTimestamp *)version; @end diff --git a/Firestore/Source/Local/FSTLocalSerializer.mm b/Firestore/Source/Local/FSTLocalSerializer.mm index 61e173a..2c5ab4d 100644 --- a/Firestore/Source/Local/FSTLocalSerializer.mm +++ b/Firestore/Source/Local/FSTLocalSerializer.mm @@ -18,6 +18,7 @@ #include <cinttypes> +#import "FIRTimestamp.h" #import "Firestore/Protos/objc/firestore/local/MaybeDocument.pbobjc.h" #import "Firestore/Protos/objc/firestore/local/Mutation.pbobjc.h" #import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h" @@ -30,9 +31,13 @@ #import "Firestore/Source/Remote/FSTSerializerBeta.h" #import "Firestore/Source/Util/FSTAssert.h" +#include "Firestore/core/include/firebase/firestore/timestamp.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" +using firebase::Timestamp; using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::SnapshotVersion; @interface FSTLocalSerializer () @@ -99,8 +104,8 @@ using firebase::firestore::model::DocumentKey; FSTSerializerBeta *remoteSerializer = self.remoteSerializer; FSTObjectValue *data = [remoteSerializer decodedFields:document.fields]; - const DocumentKey key = [remoteSerializer decodedDocumentKey:document.name]; - FSTSnapshotVersion *version = [remoteSerializer decodedVersion:document.updateTime]; + DocumentKey key = [remoteSerializer decodedDocumentKey:document.name]; + SnapshotVersion version = [remoteSerializer decodedVersion:document.updateTime]; return [FSTDocument documentWithData:data key:key version:version hasLocalMutations:NO]; } @@ -118,8 +123,8 @@ using firebase::firestore::model::DocumentKey; - (FSTDeletedDocument *)decodedDeletedDocument:(FSTPBNoDocument *)proto { FSTSerializerBeta *remoteSerializer = self.remoteSerializer; - const DocumentKey key = [remoteSerializer decodedDocumentKey:proto.name]; - FSTSnapshotVersion *version = [remoteSerializer decodedVersion:proto.readTime]; + DocumentKey key = [remoteSerializer decodedDocumentKey:proto.name]; + SnapshotVersion version = [remoteSerializer decodedVersion:proto.readTime]; return [FSTDeletedDocument documentWithKey:key version:version]; } @@ -128,7 +133,8 @@ using firebase::firestore::model::DocumentKey; FSTPBWriteBatch *proto = [FSTPBWriteBatch message]; proto.batchId = batch.batchID; - proto.localWriteTime = [remoteSerializer encodedTimestamp:batch.localWriteTime]; + proto.localWriteTime = [remoteSerializer + encodedTimestamp:Timestamp{batch.localWriteTime.seconds, batch.localWriteTime.nanoseconds}]; NSMutableArray<GCFSWrite *> *writes = proto.writesArray; for (FSTMutation *mutation in batch.mutations) { @@ -146,11 +152,13 @@ using firebase::firestore::model::DocumentKey; [mutations addObject:[remoteSerializer decodedMutation:write]]; } - FIRTimestamp *localWriteTime = [remoteSerializer decodedTimestamp:batch.localWriteTime]; + Timestamp localWriteTime = [remoteSerializer decodedTimestamp:batch.localWriteTime]; - return [[FSTMutationBatch alloc] initWithBatchID:batchID - localWriteTime:localWriteTime - mutations:mutations]; + return [[FSTMutationBatch alloc] + initWithBatchID:batchID + localWriteTime:[FIRTimestamp timestampWithSeconds:localWriteTime.seconds() + nanoseconds:localWriteTime.nanoseconds()] + mutations:mutations]; } - (FSTPBTarget *)encodedQueryData:(FSTQueryData *)queryData { @@ -181,7 +189,7 @@ using firebase::firestore::model::DocumentKey; FSTTargetID targetID = target.targetId; FSTListenSequenceNumber sequenceNumber = target.lastListenSequenceNumber; - FSTSnapshotVersion *version = [remoteSerializer decodedVersion:target.snapshotVersion]; + SnapshotVersion version = [remoteSerializer decodedVersion:target.snapshotVersion]; NSData *resumeToken = target.resumeToken; FSTQuery *query; @@ -206,11 +214,11 @@ using firebase::firestore::model::DocumentKey; resumeToken:resumeToken]; } -- (GPBTimestamp *)encodedVersion:(FSTSnapshotVersion *)version { +- (GPBTimestamp *)encodedVersion:(const SnapshotVersion &)version { return [self.remoteSerializer encodedVersion:version]; } -- (FSTSnapshotVersion *)decodedVersion:(GPBTimestamp *)version { +- (SnapshotVersion)decodedVersion:(GPBTimestamp *)version { return [self.remoteSerializer decodedVersion:version]; } diff --git a/Firestore/Source/Local/FSTLocalStore.h b/Firestore/Source/Local/FSTLocalStore.h index 82402e4..1f4146a 100644 --- a/Firestore/Source/Local/FSTLocalStore.h +++ b/Firestore/Source/Local/FSTLocalStore.h @@ -18,11 +18,11 @@ #import "Firestore/Source/Core/FSTTypes.h" #import "Firestore/Source/Model/FSTDocumentDictionary.h" -#import "Firestore/Source/Model/FSTDocumentKeySet.h" -#import "Firestore/Source/Model/FSTDocumentVersionDictionary.h" #include "Firestore/core/src/firebase/firestore/auth/user.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" @class FSTLocalViewChanges; @class FSTLocalWriteResult; @@ -141,7 +141,7 @@ NS_ASSUME_NONNULL_BEGIN * Returns the last consistent snapshot processed (used by the RemoteStore to determine whether to * buffer incoming snapshots from the backend). */ -- (FSTSnapshotVersion *)lastRemoteSnapshotVersion; +- (const firebase::firestore::model::SnapshotVersion &)lastRemoteSnapshotVersion; /** * Updates the "ground-state" (remote) documents. We assume that the remote event reflects any @@ -156,7 +156,7 @@ NS_ASSUME_NONNULL_BEGIN * Returns the keys of the documents that are associated with the given targetID in the remote * table. */ -- (FSTDocumentKeySet *)remoteDocumentKeysForTarget:(FSTTargetID)targetID; +- (firebase::firestore::model::DocumentKeySet)remoteDocumentKeysForTarget:(FSTTargetID)targetID; /** * Collects garbage if necessary. diff --git a/Firestore/Source/Local/FSTLocalStore.mm b/Firestore/Source/Local/FSTLocalStore.mm index b5dfeec..0d6a785 100644 --- a/Firestore/Source/Local/FSTLocalStore.mm +++ b/Firestore/Source/Local/FSTLocalStore.mm @@ -21,7 +21,6 @@ #import "FIRTimestamp.h" #import "Firestore/Source/Core/FSTListenSequence.h" #import "Firestore/Source/Core/FSTQuery.h" -#import "Firestore/Source/Core/FSTSnapshotVersion.h" #import "Firestore/Source/Local/FSTGarbageCollector.h" #import "Firestore/Source/Local/FSTLocalDocumentsView.h" #import "Firestore/Source/Local/FSTLocalViewChanges.h" @@ -43,11 +42,14 @@ #include "Firestore/core/src/firebase/firestore/auth/user.h" #include "Firestore/core/src/firebase/firestore/core/target_id_generator.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" using firebase::firestore::auth::User; -using firebase::firestore::model::DocumentKey; using firebase::firestore::core::TargetIdGenerator; using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::SnapshotVersion; +using firebase::firestore::model::DocumentKeySet; +using firebase::firestore::model::DocumentVersionMap; NS_ASSUME_NONNULL_BEGIN @@ -112,6 +114,7 @@ NS_ASSUME_NONNULL_BEGIN _localDocuments = [FSTLocalDocumentsView viewWithRemoteDocumentCache:_remoteDocumentCache mutationQueue:_mutationQueue]; _localViewReferences = [[FSTReferenceSet alloc] init]; + [_persistence.referenceDelegate addInMemoryPins:_localViewReferences]; _garbageCollector = garbageCollector; [_garbageCollector addGarbageSource:_queryCache]; @@ -186,11 +189,11 @@ NS_ASSUME_NONNULL_BEGIN mutationQueue:self.mutationQueue]; // Union the old/new changed keys. - FSTDocumentKeySet *changedKeys = [FSTDocumentKeySet keySet]; + DocumentKeySet changedKeys; for (NSArray<FSTMutationBatch *> *batches in @[ oldBatches, newBatches ]) { for (FSTMutationBatch *batch in batches) { for (FSTMutation *mutation in batch.mutations) { - changedKeys = [changedKeys setByAddingObject:mutation.key]; + changedKeys = changedKeys.insert(mutation.key); } } } @@ -205,7 +208,7 @@ NS_ASSUME_NONNULL_BEGIN FIRTimestamp *localWriteTime = [FIRTimestamp timestamp]; FSTMutationBatch *batch = [self.mutationQueue addMutationBatchWithWriteTime:localWriteTime mutations:mutations]; - FSTDocumentKeySet *keys = [batch keys]; + DocumentKeySet keys = [batch keys]; FSTMaybeDocumentDictionary *changedDocuments = [self.localDocuments documentsForKeys:keys]; return [FSTLocalWriteResult resultForBatchID:batch.batchID changes:changedDocuments]; }); @@ -217,10 +220,9 @@ NS_ASSUME_NONNULL_BEGIN [mutationQueue acknowledgeBatch:batchResult.batch streamToken:batchResult.streamToken]; - FSTDocumentKeySet *affected; + DocumentKeySet affected; if ([self shouldHoldBatchResultWithVersion:batchResult.commitVersion]) { [self.heldBatchResults addObject:batchResult]; - affected = [FSTDocumentKeySet keySet]; } else { affected = [self releaseBatchResults:@[ batchResult ]]; } @@ -239,7 +241,7 @@ NS_ASSUME_NONNULL_BEGIN FSTBatchID lastAcked = [self.mutationQueue highestAcknowledgedBatchID]; FSTAssert(batchID > lastAcked, @"Acknowledged batches can't be rejected."); - FSTDocumentKeySet *affected = [self removeMutationBatch:toReject]; + DocumentKeySet affected = [self removeMutationBatch:toReject]; [self.mutationQueue performConsistencyCheck]; @@ -256,12 +258,14 @@ NS_ASSUME_NONNULL_BEGIN [&]() { [self.mutationQueue setLastStreamToken:streamToken]; }); } -- (FSTSnapshotVersion *)lastRemoteSnapshotVersion { +- (const SnapshotVersion &)lastRemoteSnapshotVersion { return [self.queryCache lastRemoteSnapshotVersion]; } - (FSTMaybeDocumentDictionary *)applyRemoteEvent:(FSTRemoteEvent *)remoteEvent { return self.persistence.run("Apply remote event", [&]() -> FSTMaybeDocumentDictionary * { + // TODO(gsoltis): move the sequence number into the reference delegate. + FSTListenSequenceNumber sequenceNumber = [self.listenSequence next]; id<FSTQueryCache> queryCache = self.queryCache; [remoteEvent.targetChanges enumerateKeysAndObjectsUsingBlock:^( @@ -274,6 +278,18 @@ NS_ASSUME_NONNULL_BEGIN return; } + // Update the resume token if the change includes one. Don't clear any preexisting value. + // Bump the sequence number as well, so that documents being removed now are ordered later + // than documents that were previously removed from this target. + NSData *resumeToken = change.resumeToken; + if (resumeToken.length > 0) { + queryData = [queryData queryDataByReplacingSnapshotVersion:change.snapshotVersion + resumeToken:resumeToken + sequenceNumber:sequenceNumber]; + self.targetIDs[targetIDNumber] = queryData; + [self.queryCache updateQueryData:queryData]; + } + FSTTargetMapping *mapping = change.mapping; if (mapping) { // First make sure that all references are deleted. @@ -291,61 +307,58 @@ NS_ASSUME_NONNULL_BEGIN FSTFail(@"Unknown mapping type: %@", mapping); } } - - // Update the resume token if the change includes one. Don't clear any preexisting value. - NSData *resumeToken = change.resumeToken; - if (resumeToken.length > 0) { - queryData = [queryData queryDataByReplacingSnapshotVersion:change.snapshotVersion - resumeToken:resumeToken]; - self.targetIDs[targetIDNumber] = queryData; - [self.queryCache updateQueryData:queryData]; - } }]; // TODO(klimt): This could probably be an NSMutableDictionary. - FSTDocumentKeySet *changedDocKeys = [FSTDocumentKeySet keySet]; + DocumentKeySet changedDocKeys; + const DocumentKeySet &limboDocuments = remoteEvent.limboDocumentChanges; for (const auto &kv : remoteEvent.documentUpdates) { const DocumentKey &key = kv.first; FSTMaybeDocument *doc = kv.second; - changedDocKeys = [changedDocKeys setByAddingObject:key]; + changedDocKeys = changedDocKeys.insert(key); FSTMaybeDocument *existingDoc = [self.remoteDocumentCache entryForKey:key]; // Make sure we don't apply an old document version to the remote cache, though we - // make an exception for [SnapshotVersion noVersion] which can happen for manufactured + // make an exception for SnapshotVersion::None() which can happen for manufactured // events (e.g. in the case of a limbo document resolution failing). - if (!existingDoc || [doc.version isEqual:[FSTSnapshotVersion noVersion]] || - [doc.version compare:existingDoc.version] != NSOrderedAscending) { + if (!existingDoc || SnapshotVersion{doc.version} == SnapshotVersion::None() || + SnapshotVersion{doc.version} >= SnapshotVersion{existingDoc.version}) { [self.remoteDocumentCache addEntry:doc]; } else { FSTLog( @"FSTLocalStore Ignoring outdated watch update for %s. " - "Current version: %@ Watch version: %@", - key.ToString().c_str(), existingDoc.version, doc.version); + "Current version: %s Watch version: %s", + key.ToString().c_str(), existingDoc.version.timestamp().ToString().c_str(), + doc.version.timestamp().ToString().c_str()); } // The document might be garbage because it was unreferenced by everything. // Make sure to mark it as garbage if it is... [self.garbageCollector addPotentialGarbageKey:key]; + if (limboDocuments.contains(key)) { + [self.persistence.referenceDelegate limboDocumentUpdated:key]; + } } // HACK: The only reason we allow omitting snapshot version is so we can synthesize remote // events when we get permission denied errors while trying to resolve the state of a locally // cached document that is in limbo. - FSTSnapshotVersion *lastRemoteVersion = [self.queryCache lastRemoteSnapshotVersion]; - FSTSnapshotVersion *remoteVersion = remoteEvent.snapshotVersion; - if (![remoteVersion isEqual:[FSTSnapshotVersion noVersion]]) { - FSTAssert([remoteVersion compare:lastRemoteVersion] != NSOrderedAscending, - @"Watch stream reverted to previous snapshot?? (%@ < %@)", remoteVersion, - lastRemoteVersion); + const SnapshotVersion &lastRemoteVersion = [self.queryCache lastRemoteSnapshotVersion]; + const SnapshotVersion &remoteVersion = remoteEvent.snapshotVersion; + if (remoteVersion != SnapshotVersion::None()) { + FSTAssert(remoteVersion >= lastRemoteVersion, + @"Watch stream reverted to previous snapshot?? (%s < %s)", + remoteVersion.timestamp().ToString().c_str(), + lastRemoteVersion.timestamp().ToString().c_str()); [self.queryCache setLastRemoteSnapshotVersion:remoteVersion]; } - FSTDocumentKeySet *releasedWriteKeys = [self releaseHeldBatchResults]; + DocumentKeySet releasedWriteKeys = [self releaseHeldBatchResults]; // Union the two key sets. - __block FSTDocumentKeySet *keysToRecalc = changedDocKeys; - [releasedWriteKeys enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) { - keysToRecalc = [keysToRecalc setByAddingObject:key]; - }]; + DocumentKeySet keysToRecalc = changedDocKeys; + for (const DocumentKey &key : releasedWriteKeys) { + keysToRecalc = keysToRecalc.insert(key); + } return [self.localDocuments documentsForKeys:keysToRecalc]; }); @@ -358,6 +371,9 @@ NS_ASSUME_NONNULL_BEGIN FSTQueryData *queryData = [self.queryCache queryDataForQuery:view.query]; FSTAssert(queryData, @"Local view changes contain unallocated query."); FSTTargetID targetID = queryData.targetID; + for (const DocumentKey &key : view.removedKeys) { + [self->_persistence.referenceDelegate removeReference:key target:targetID]; + } [localViewReferences addReferencesToKeys:view.addedKeys forID:targetID]; [localViewReferences removeReferencesToKeys:view.removedKeys forID:targetID]; } @@ -408,6 +424,7 @@ NS_ASSUME_NONNULL_BEGIN if (self.garbageCollector.isEager) { [self.queryCache removeQueryData:queryData]; } + [self.persistence.referenceDelegate removeTarget:queryData]; [self.targetIDs removeObjectForKey:@(queryData.targetID)]; // If this was the last watch target, then we won't get any more watch snapshots, so we should @@ -424,8 +441,8 @@ NS_ASSUME_NONNULL_BEGIN }); } -- (FSTDocumentKeySet *)remoteDocumentKeysForTarget:(FSTTargetID)targetID { - return self.persistence.run("RemoteDocumentKeysForTarget", [&]() -> FSTDocumentKeySet * { +- (DocumentKeySet)remoteDocumentKeysForTarget:(FSTTargetID)targetID { + return self.persistence.run("RemoteDocumentKeysForTarget", [&]() -> DocumentKeySet { return [self.queryCache matchingKeysForTargetID:targetID]; }); } @@ -449,7 +466,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return the set of keys of docs that were modified by those writes. */ -- (FSTDocumentKeySet *)releaseHeldBatchResults { +- (DocumentKeySet)releaseHeldBatchResults { NSMutableArray<FSTMutationBatchResult *> *toRelease = [NSMutableArray array]; for (FSTMutationBatchResult *batchResult in self.heldBatchResults) { if (![self isRemoteUpToVersion:batchResult.commitVersion]) { @@ -459,25 +476,24 @@ NS_ASSUME_NONNULL_BEGIN } if (toRelease.count == 0) { - return [FSTDocumentKeySet keySet]; + return DocumentKeySet{}; } else { [self.heldBatchResults removeObjectsInRange:NSMakeRange(0, toRelease.count)]; return [self releaseBatchResults:toRelease]; } } -- (BOOL)isRemoteUpToVersion:(FSTSnapshotVersion *)version { +- (BOOL)isRemoteUpToVersion:(const SnapshotVersion &)version { // If there are no watch targets, then we won't get remote snapshots, and are always "up-to-date." - return [version compare:self.queryCache.lastRemoteSnapshotVersion] != NSOrderedDescending || - self.targetIDs.count == 0; + return version <= self.queryCache.lastRemoteSnapshotVersion || self.targetIDs.count == 0; } -- (BOOL)shouldHoldBatchResultWithVersion:(FSTSnapshotVersion *)version { +- (BOOL)shouldHoldBatchResultWithVersion:(const SnapshotVersion &)version { // Check if watcher isn't up to date or prior results are already held. return ![self isRemoteUpToVersion:version] || self.heldBatchResults.count > 0; } -- (FSTDocumentKeySet *)releaseBatchResults:(NSArray<FSTMutationBatchResult *> *)batchResults { +- (DocumentKeySet)releaseBatchResults:(NSArray<FSTMutationBatchResult *> *)batchResults { NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array]; for (FSTMutationBatchResult *batchResult in batchResults) { [self applyBatchResult:batchResult]; @@ -487,36 +503,37 @@ NS_ASSUME_NONNULL_BEGIN return [self removeMutationBatches:batches]; } -- (FSTDocumentKeySet *)removeMutationBatch:(FSTMutationBatch *)batch { +- (DocumentKeySet)removeMutationBatch:(FSTMutationBatch *)batch { return [self removeMutationBatches:@[ batch ]]; } /** Removes all the mutation batches named in the given array. */ -- (FSTDocumentKeySet *)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches { - // TODO(klimt): Could this be an NSMutableDictionary? - __block FSTDocumentKeySet *affectedDocs = [FSTDocumentKeySet keySet]; - +- (DocumentKeySet)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches { + DocumentKeySet affectedDocs; for (FSTMutationBatch *batch in batches) { for (FSTMutation *mutation in batch.mutations) { const DocumentKey &key = mutation.key; - affectedDocs = [affectedDocs setByAddingObject:key]; + affectedDocs = affectedDocs.insert(key); } } [self.mutationQueue removeMutationBatches:batches]; - return affectedDocs; } - (void)applyBatchResult:(FSTMutationBatchResult *)batchResult { FSTMutationBatch *batch = batchResult.batch; - FSTDocumentKeySet *docKeys = batch.keys; - [docKeys enumerateObjectsUsingBlock:^(FSTDocumentKey *docKey, BOOL *stop) { + DocumentKeySet docKeys = batch.keys; + const DocumentVersionMap &versions = batchResult.docVersions; + for (const DocumentKey &docKey : docKeys) { FSTMaybeDocument *_Nullable remoteDoc = [self.remoteDocumentCache entryForKey:docKey]; FSTMaybeDocument *_Nullable doc = remoteDoc; - FSTSnapshotVersion *ackVersion = batchResult.docVersions[docKey]; - FSTAssert(ackVersion, @"docVersions should contain every doc in the write."); - if (!doc || [doc.version compare:ackVersion] == NSOrderedAscending) { + + auto ackVersionIter = versions.find(docKey); + FSTAssert(ackVersionIter != versions.end(), + @"docVersions should contain every doc in the write."); + const SnapshotVersion &ackVersion = ackVersionIter->second; + if (!doc || doc.version < ackVersion) { doc = [batch applyTo:doc documentKey:docKey mutationBatchResult:batchResult]; if (!doc) { FSTAssert(!remoteDoc, @"Mutation batch %@ applied to document %@ resulted in nil.", batch, @@ -525,7 +542,7 @@ NS_ASSUME_NONNULL_BEGIN [self.remoteDocumentCache addEntry:doc]; } } - }]; + } } @end diff --git a/Firestore/Source/Local/FSTLocalViewChanges.h b/Firestore/Source/Local/FSTLocalViewChanges.h index eb84642..143d010 100644 --- a/Firestore/Source/Local/FSTLocalViewChanges.h +++ b/Firestore/Source/Local/FSTLocalViewChanges.h @@ -16,7 +16,7 @@ #import <Foundation/Foundation.h> -#import "Firestore/Source/Model/FSTDocumentKeySet.h" +#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" @class FSTDocumentSet; @class FSTMutation; @@ -34,16 +34,17 @@ NS_ASSUME_NONNULL_BEGIN @interface FSTLocalViewChanges : NSObject + (instancetype)changesForQuery:(FSTQuery *)query - addedKeys:(FSTDocumentKeySet *)addedKeys - removedKeys:(FSTDocumentKeySet *)removedKeys; + addedKeys:(firebase::firestore::model::DocumentKeySet)addedKeys + removedKeys:(firebase::firestore::model::DocumentKeySet)removedKeys; + (instancetype)changesForViewSnapshot:(FSTViewSnapshot *)viewSnapshot; - (id)init NS_UNAVAILABLE; @property(nonatomic, strong, readonly) FSTQuery *query; -@property(nonatomic, strong) FSTDocumentKeySet *addedKeys; -@property(nonatomic, strong) FSTDocumentKeySet *removedKeys; + +- (const firebase::firestore::model::DocumentKeySet &)addedKeys; +- (const firebase::firestore::model::DocumentKeySet &)removedKeys; @end diff --git a/Firestore/Source/Local/FSTLocalViewChanges.mm b/Firestore/Source/Local/FSTLocalViewChanges.mm index 9a7f445..eb6b259 100644 --- a/Firestore/Source/Local/FSTLocalViewChanges.mm +++ b/Firestore/Source/Local/FSTLocalViewChanges.mm @@ -16,31 +16,38 @@ #import "Firestore/Source/Local/FSTLocalViewChanges.h" +#include <utility> + #import "Firestore/Source/Core/FSTViewSnapshot.h" #import "Firestore/Source/Model/FSTDocument.h" +using firebase::firestore::model::DocumentKeySet; + NS_ASSUME_NONNULL_BEGIN @interface FSTLocalViewChanges () - (instancetype)initWithQuery:(FSTQuery *)query - addedKeys:(FSTDocumentKeySet *)addedKeys - removedKeys:(FSTDocumentKeySet *)removedKeys NS_DESIGNATED_INITIALIZER; + addedKeys:(DocumentKeySet)addedKeys + removedKeys:(DocumentKeySet)removedKeys NS_DESIGNATED_INITIALIZER; @end -@implementation FSTLocalViewChanges +@implementation FSTLocalViewChanges { + DocumentKeySet _addedKeys; + DocumentKeySet _removedKeys; +} + (instancetype)changesForViewSnapshot:(FSTViewSnapshot *)viewSnapshot { - FSTDocumentKeySet *addedKeys = [FSTDocumentKeySet keySet]; - FSTDocumentKeySet *removedKeys = [FSTDocumentKeySet keySet]; + DocumentKeySet addedKeys; + DocumentKeySet removedKeys; for (FSTDocumentViewChange *docChange in viewSnapshot.documentChanges) { switch (docChange.type) { case FSTDocumentViewChangeTypeAdded: - addedKeys = [addedKeys setByAddingObject:docChange.document.key]; + addedKeys = addedKeys.insert(docChange.document.key); break; case FSTDocumentViewChangeTypeRemoved: - removedKeys = [removedKeys setByAddingObject:docChange.document.key]; + removedKeys = removedKeys.insert(docChange.document.key); break; default: @@ -49,28 +56,39 @@ NS_ASSUME_NONNULL_BEGIN } } - return [self changesForQuery:viewSnapshot.query addedKeys:addedKeys removedKeys:removedKeys]; + return [self changesForQuery:viewSnapshot.query + addedKeys:std::move(addedKeys) + removedKeys:std::move(removedKeys)]; } + (instancetype)changesForQuery:(FSTQuery *)query - addedKeys:(FSTDocumentKeySet *)addedKeys - removedKeys:(FSTDocumentKeySet *)removedKeys { - return - [[FSTLocalViewChanges alloc] initWithQuery:query addedKeys:addedKeys removedKeys:removedKeys]; + addedKeys:(DocumentKeySet)addedKeys + removedKeys:(DocumentKeySet)removedKeys { + return [[FSTLocalViewChanges alloc] initWithQuery:query + addedKeys:std::move(addedKeys) + removedKeys:std::move(removedKeys)]; } - (instancetype)initWithQuery:(FSTQuery *)query - addedKeys:(FSTDocumentKeySet *)addedKeys - removedKeys:(FSTDocumentKeySet *)removedKeys { + addedKeys:(DocumentKeySet)addedKeys + removedKeys:(DocumentKeySet)removedKeys { self = [super init]; if (self) { _query = query; - _addedKeys = addedKeys; - _removedKeys = removedKeys; + _addedKeys = std::move(addedKeys); + _removedKeys = std::move(removedKeys); } return self; } +- (const DocumentKeySet &)addedKeys { + return _addedKeys; +} + +- (const DocumentKeySet &)removedKeys { + return _removedKeys; +} + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Local/FSTMemoryMutationQueue.h b/Firestore/Source/Local/FSTMemoryMutationQueue.h index f0786cc..fd46a6e 100644 --- a/Firestore/Source/Local/FSTMemoryMutationQueue.h +++ b/Firestore/Source/Local/FSTMemoryMutationQueue.h @@ -16,6 +16,7 @@ #import <Foundation/Foundation.h> +#import "Firestore/Source/Local/FSTMemoryPersistence.h" #import "Firestore/Source/Local/FSTMutationQueue.h" @protocol FSTGarbageCollector; @@ -24,7 +25,9 @@ NS_ASSUME_NONNULL_BEGIN @interface FSTMemoryMutationQueue : NSObject <FSTMutationQueue> -+ (instancetype)mutationQueue; +- (instancetype)initWithPersistence:(FSTMemoryPersistence *)persistence NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; /** The garbage collector to notify about potential garbage keys. */ @property(nonatomic, weak, readwrite, nullable) id<FSTGarbageCollector> garbageCollector; diff --git a/Firestore/Source/Local/FSTMemoryMutationQueue.mm b/Firestore/Source/Local/FSTMemoryMutationQueue.mm index 8028bb3..e05ee64 100644 --- a/Firestore/Source/Local/FSTMemoryMutationQueue.mm +++ b/Firestore/Source/Local/FSTMemoryMutationQueue.mm @@ -18,9 +18,11 @@ #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Local/FSTDocumentReference.h" +#import "Firestore/Source/Local/FSTMemoryPersistence.h" #import "Firestore/Source/Model/FSTMutation.h" #import "Firestore/Source/Model/FSTMutationBatch.h" #import "Firestore/Source/Util/FSTAssert.h" +#import "Firestore/third_party/Immutable/FSTImmutableSortedSet.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" @@ -72,14 +74,13 @@ static const NSComparator NumberComparator = ^NSComparisonResult(NSNumber *left, @end -@implementation FSTMemoryMutationQueue - -+ (instancetype)mutationQueue { - return [[FSTMemoryMutationQueue alloc] init]; +@implementation FSTMemoryMutationQueue { + FSTMemoryPersistence *_persistence; } -- (instancetype)init { +- (instancetype)initWithPersistence:(FSTMemoryPersistence *)persistence { if (self = [super init]) { + _persistence = persistence; _queue = [NSMutableArray array]; _batchesByDocumentKey = [FSTImmutableSortedSet setWithComparator:FSTDocumentReferenceComparatorByKey]; @@ -347,6 +348,7 @@ static const NSComparator NumberComparator = ^NSComparisonResult(NSNumber *left, for (FSTMutation *mutation in batch.mutations) { const DocumentKey &key = mutation.key; [garbageCollector addPotentialGarbageKey:key]; + [_persistence.referenceDelegate removeMutationReference:key]; FSTDocumentReference *reference = [[FSTDocumentReference alloc] initWithKey:key ID:batchID]; references = [references setByRemovingObject:reference]; diff --git a/Firestore/Source/Local/FSTMemoryPersistence.mm b/Firestore/Source/Local/FSTMemoryPersistence.mm index 8d74881..3466f3e 100644 --- a/Firestore/Source/Local/FSTMemoryPersistence.mm +++ b/Firestore/Source/Local/FSTMemoryPersistence.mm @@ -59,7 +59,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init { if (self = [super init]) { - _queryCache = [[FSTMemoryQueryCache alloc] init]; + _queryCache = [[FSTMemoryQueryCache alloc] initWithPersistence:self]; _remoteDocumentCache = [[FSTMemoryRemoteDocumentCache alloc] init]; } return self; @@ -78,6 +78,10 @@ NS_ASSUME_NONNULL_BEGIN self.started = NO; } +- (_Nullable id<FSTReferenceDelegate>)referenceDelegate { + return nil; +} + - (const FSTTransactionRunner &)run { return _transactionRunner; } @@ -85,7 +89,7 @@ NS_ASSUME_NONNULL_BEGIN - (id<FSTMutationQueue>)mutationQueueForUser:(const User &)user { id<FSTMutationQueue> queue = _mutationQueues[user]; if (!queue) { - queue = [FSTMemoryMutationQueue mutationQueue]; + queue = [[FSTMemoryMutationQueue alloc] initWithPersistence:self]; _mutationQueues[user] = queue; } return queue; diff --git a/Firestore/Source/Local/FSTMemoryQueryCache.h b/Firestore/Source/Local/FSTMemoryQueryCache.h index 98f0277..126ce59 100644 --- a/Firestore/Source/Local/FSTMemoryQueryCache.h +++ b/Firestore/Source/Local/FSTMemoryQueryCache.h @@ -20,11 +20,18 @@ NS_ASSUME_NONNULL_BEGIN +@class FSTMemoryPersistence; + /** * An implementation of the FSTQueryCache protocol that merely keeps queries in memory, suitable * for online only clients with persistence disabled. */ @interface FSTMemoryQueryCache : NSObject <FSTQueryCache> + +- (instancetype)initWithPersistence:(FSTMemoryPersistence *)persistence NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Local/FSTMemoryQueryCache.mm b/Firestore/Source/Local/FSTMemoryQueryCache.mm index 18d14f2..2eba4f6 100644 --- a/Firestore/Source/Local/FSTMemoryQueryCache.mm +++ b/Firestore/Source/Local/FSTMemoryQueryCache.mm @@ -16,12 +16,19 @@ #import "Firestore/Source/Local/FSTMemoryQueryCache.h" +#include <utility> + #import "Firestore/Source/Core/FSTQuery.h" -#import "Firestore/Source/Core/FSTSnapshotVersion.h" +#import "Firestore/Source/Local/FSTMemoryPersistence.h" #import "Firestore/Source/Local/FSTQueryData.h" #import "Firestore/Source/Local/FSTReferenceSet.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" + +using firebase::firestore::model::SnapshotVersion; +using firebase::firestore::model::DocumentKeySet; +using firebase::firestore::model::DocumentKey; NS_ASSUME_NONNULL_BEGIN @@ -38,18 +45,20 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, assign) FSTListenSequenceNumber highestListenSequenceNumber; -/** The last received snapshot version. */ -@property(nonatomic, strong) FSTSnapshotVersion *lastRemoteSnapshotVersion; - @end -@implementation FSTMemoryQueryCache +@implementation FSTMemoryQueryCache { + FSTMemoryPersistence *_persistence; + /** The last received snapshot version. */ + SnapshotVersion _lastRemoteSnapshotVersion; +} -- (instancetype)init { +- (instancetype)initWithPersistence:(FSTMemoryPersistence *)persistence { if (self = [super init]) { + _persistence = persistence; _queries = [NSMutableDictionary dictionary]; _references = [[FSTReferenceSet alloc] init]; - _lastRemoteSnapshotVersion = [FSTSnapshotVersion noVersion]; + _lastRemoteSnapshotVersion = SnapshotVersion::None(); } return self; } @@ -69,14 +78,13 @@ NS_ASSUME_NONNULL_BEGIN return _highestListenSequenceNumber; } -/*- (FSTSnapshotVersion *)lastRemoteSnapshotVersion { +- (const SnapshotVersion &)lastRemoteSnapshotVersion { return _lastRemoteSnapshotVersion; } -- (void)setLastRemoteSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion - group:(FSTWriteGroup *)group { - _lastRemoteSnapshotVersion = snapshotVersion; -}*/ +- (void)setLastRemoteSnapshotVersion:(SnapshotVersion)snapshotVersion { + _lastRemoteSnapshotVersion = std::move(snapshotVersion); +} - (void)addQueryData:(FSTQueryData *)queryData { self.queries[queryData.query] = queryData; @@ -113,19 +121,25 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark Reference tracking -- (void)addMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID { +- (void)addMatchingKeys:(const DocumentKeySet &)keys forTargetID:(FSTTargetID)targetID { [self.references addReferencesToKeys:keys forID:targetID]; + for (const DocumentKey &key : keys) { + [_persistence.referenceDelegate addReference:key target:targetID]; + } } -- (void)removeMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID { +- (void)removeMatchingKeys:(const DocumentKeySet &)keys forTargetID:(FSTTargetID)targetID { [self.references removeReferencesToKeys:keys forID:targetID]; + for (const DocumentKey &key : keys) { + [_persistence.referenceDelegate removeReference:key target:targetID]; + } } - (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID { [self.references removeReferencesForID:targetID]; } -- (FSTDocumentKeySet *)matchingKeysForTargetID:(FSTTargetID)targetID { +- (DocumentKeySet)matchingKeysForTargetID:(FSTTargetID)targetID { return [self.references referencedKeysForID:targetID]; } diff --git a/Firestore/Source/Local/FSTPersistence.h b/Firestore/Source/Local/FSTPersistence.h index 2294ef1..417ff3f 100644 --- a/Firestore/Source/Local/FSTPersistence.h +++ b/Firestore/Source/Local/FSTPersistence.h @@ -16,12 +16,16 @@ #import <Foundation/Foundation.h> +#import "Firestore/Source/Core/FSTTypes.h" #import "Firestore/Source/Util/FSTAssert.h" #include "Firestore/core/src/firebase/firestore/auth/user.h" +@class FSTDocumentKey; @protocol FSTMutationQueue; @protocol FSTQueryCache; +@class FSTQueryData; @protocol FSTRemoteDocumentCache; +@class FSTReferenceSet; NS_ASSUME_NONNULL_BEGIN @@ -56,6 +60,7 @@ NS_ASSUME_NONNULL_BEGIN * of its reads and writes in order to avoid relying on being able to read back uncommitted writes. */ struct FSTTransactionRunner; +@protocol FSTReferenceDelegate; @protocol FSTPersistence <NSObject> /** @@ -87,6 +92,12 @@ struct FSTTransactionRunner; @property(nonatomic, readonly, assign) const FSTTransactionRunner &run; +/** + * This property provides access to hooks around the document reference lifecycle. It is initially + * nullable while being implemented, but the goal is to eventually have it be non-nil. + */ +@property(nonatomic, readonly, strong) _Nullable id<FSTReferenceDelegate> referenceDelegate; + @end @protocol FSTTransactional @@ -97,6 +108,52 @@ struct FSTTransactionRunner; @end +/** + * An FSTReferenceDelegate instance handles all of the hooks into the document-reference lifecycle. + * This includes being added to a target, being removed from a target, being subject to mutation, + * and being mutated by the user. + * + * Different implementations may do different things with each of these events. Not every + * implementation needs to do something with every lifecycle hook. + * + * Implementations that care about sequence numbers are responsible for generating them and making + * them available. + */ +@protocol FSTReferenceDelegate + +/** + * Registers an FSTReferenceSet of documents that should be considered 'referenced' and not eligible + * for removal during garbage collection. + */ +- (void)addInMemoryPins:(FSTReferenceSet *)set; + +/** + * Notify the delegate that a target was removed. + */ +- (void)removeTarget:(FSTQueryData *)queryData; + +/** + * Notify the delegate that the given document was added to the given target. + */ +- (void)addReference:(FSTDocumentKey *)key target:(FSTTargetID)targetID; + +/** + * Notify the delegate that the given document was removed from the given target. + */ +- (void)removeReference:(FSTDocumentKey *)key target:(FSTTargetID)targetID; + +/** + * Notify the delegate that a document is no longer being mutated by the user. + */ +- (void)removeMutationReference:(FSTDocumentKey *)key; + +/** + * Notify the delegate that a limbo document was updated. + */ +- (void)limboDocumentUpdated:(FSTDocumentKey *)key; + +@end + struct FSTTransactionRunner { // Intentionally disable nullability checking for this function. We cannot properly annotate // the function because this function can handle both pointer and non-pointer types. It is an error diff --git a/Firestore/Source/Local/FSTQueryCache.h b/Firestore/Source/Local/FSTQueryCache.h index d797d49..1ad46aa 100644 --- a/Firestore/Source/Local/FSTQueryCache.h +++ b/Firestore/Source/Local/FSTQueryCache.h @@ -18,13 +18,14 @@ #import "Firestore/Source/Core/FSTTypes.h" #import "Firestore/Source/Local/FSTGarbageCollector.h" -#import "Firestore/Source/Model/FSTDocumentKeySet.h" + +#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" @class FSTDocumentSet; @class FSTMaybeDocument; @class FSTQuery; @class FSTQueryData; -@class FSTSnapshotVersion; NS_ASSUME_NONNULL_BEGIN @@ -61,7 +62,7 @@ NS_ASSUME_NONNULL_BEGIN * * This is updated whenever our we get a TargetChange with a read_time and empty target_ids. */ -- (FSTSnapshotVersion *)lastRemoteSnapshotVersion; +- (const firebase::firestore::model::SnapshotVersion &)lastRemoteSnapshotVersion; /** * Set the snapshot version representing the last consistent snapshot received from the backend. @@ -69,7 +70,7 @@ NS_ASSUME_NONNULL_BEGIN * * @param snapshotVersion The new snapshot version. */ -- (void)setLastRemoteSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion; +- (void)setLastRemoteSnapshotVersion:(firebase::firestore::model::SnapshotVersion)snapshotVersion; /** * Adds an entry in the cache. @@ -104,15 +105,17 @@ NS_ASSUME_NONNULL_BEGIN - (nullable FSTQueryData *)queryDataForQuery:(FSTQuery *)query; /** Adds the given document keys to cached query results of the given target ID. */ -- (void)addMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID; +- (void)addMatchingKeys:(const firebase::firestore::model::DocumentKeySet &)keys + forTargetID:(FSTTargetID)targetID; /** Removes the given document keys from the cached query results of the given target ID. */ -- (void)removeMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID; +- (void)removeMatchingKeys:(const firebase::firestore::model::DocumentKeySet &)keys + forTargetID:(FSTTargetID)targetID; /** Removes all the keys in the query results of the given target ID. */ - (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID; -- (FSTDocumentKeySet *)matchingKeysForTargetID:(FSTTargetID)targetID; +- (firebase::firestore::model::DocumentKeySet)matchingKeysForTargetID:(FSTTargetID)targetID; @end diff --git a/Firestore/Source/Local/FSTQueryData.h b/Firestore/Source/Local/FSTQueryData.h index 5db2de6..bde0a15 100644 --- a/Firestore/Source/Local/FSTQueryData.h +++ b/Firestore/Source/Local/FSTQueryData.h @@ -18,8 +18,9 @@ #import "Firestore/Source/Core/FSTTypes.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" + @class FSTQuery; -@class FSTSnapshotVersion; NS_ASSUME_NONNULL_BEGIN @@ -42,7 +43,7 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) { targetID:(FSTTargetID)targetID listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber purpose:(FSTQueryPurpose)purpose - snapshotVersion:(FSTSnapshotVersion *)snapshotVersion + snapshotVersion:(firebase::firestore::model::SnapshotVersion)snapshotVersion resumeToken:(NSData *)resumeToken NS_DESIGNATED_INITIALIZER; /** Convenience initializer for use when creating an FSTQueryData for the first time. */ @@ -53,9 +54,17 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) { - (instancetype)init NS_UNAVAILABLE; -/** Creates a new query data instance with an updated snapshot version and resume token. */ -- (instancetype)queryDataByReplacingSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion - resumeToken:(NSData *)resumeToken; +/** + * Creates a new query data instance with an updated snapshot version, resume token, and sequence + * number. + */ +- (instancetype)queryDataByReplacingSnapshotVersion: + (firebase::firestore::model::SnapshotVersion)snapshotVersion + resumeToken:(NSData *)resumeToken + sequenceNumber:(FSTListenSequenceNumber)sequenceNumber; + +/** The latest snapshot version seen for this target. */ +- (const firebase::firestore::model::SnapshotVersion &)snapshotVersion; /** The query being listened to. */ @property(nonatomic, strong, readonly) FSTQuery *query; @@ -71,9 +80,6 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) { /** The purpose of the query. */ @property(nonatomic, assign, readonly) FSTQueryPurpose purpose; -/** The latest snapshot version seen for this target. */ -@property(nonatomic, strong, readonly) FSTSnapshotVersion *snapshotVersion; - /** * An opaque, server-assigned token that allows watching a query to be resumed after disconnecting * without retransmitting all the data that matches the query. The resume token essentially diff --git a/Firestore/Source/Local/FSTQueryData.mm b/Firestore/Source/Local/FSTQueryData.mm index 6bb716a..16c3b2e 100644 --- a/Firestore/Source/Local/FSTQueryData.mm +++ b/Firestore/Source/Local/FSTQueryData.mm @@ -16,18 +16,25 @@ #import "Firestore/Source/Local/FSTQueryData.h" +#include <utility> + #import "Firestore/Source/Core/FSTQuery.h" -#import "Firestore/Source/Core/FSTSnapshotVersion.h" + +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" + +using firebase::firestore::model::SnapshotVersion; NS_ASSUME_NONNULL_BEGIN -@implementation FSTQueryData +@implementation FSTQueryData { + SnapshotVersion _snapshotVersion; +} - (instancetype)initWithQuery:(FSTQuery *)query targetID:(FSTTargetID)targetID listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber purpose:(FSTQueryPurpose)purpose - snapshotVersion:(FSTSnapshotVersion *)snapshotVersion + snapshotVersion:(SnapshotVersion)snapshotVersion resumeToken:(NSData *)resumeToken { self = [super init]; if (self) { @@ -35,7 +42,7 @@ NS_ASSUME_NONNULL_BEGIN _targetID = targetID; _sequenceNumber = sequenceNumber; _purpose = purpose; - _snapshotVersion = snapshotVersion; + _snapshotVersion = std::move(snapshotVersion); _resumeToken = [resumeToken copy]; } return self; @@ -49,10 +56,14 @@ NS_ASSUME_NONNULL_BEGIN targetID:targetID listenSequenceNumber:sequenceNumber purpose:purpose - snapshotVersion:[FSTSnapshotVersion noVersion] + snapshotVersion:SnapshotVersion::None() resumeToken:[NSData data]]; } +- (const firebase::firestore::model::SnapshotVersion &)snapshotVersion { + return _snapshotVersion; +} + - (BOOL)isEqual:(id)object { if (self == object) { return YES; @@ -63,7 +74,7 @@ NS_ASSUME_NONNULL_BEGIN FSTQueryData *other = (FSTQueryData *)object; return [self.query isEqual:other.query] && self.targetID == other.targetID && - self.purpose == other.purpose && [self.snapshotVersion isEqual:other.snapshotVersion] && + self.purpose == other.purpose && self.snapshotVersion == other.snapshotVersion && [self.resumeToken isEqual:other.resumeToken]; } @@ -71,25 +82,26 @@ NS_ASSUME_NONNULL_BEGIN NSUInteger result = [self.query hash]; result = result * 31 + self.targetID; result = result * 31 + self.purpose; - result = result * 31 + [self.snapshotVersion hash]; + result = result * 31 + self.snapshotVersion.Hash(); result = result * 31 + [self.resumeToken hash]; return result; } - (NSString *)description { return [NSString - stringWithFormat:@"<FSTQueryData: query:%@ target:%d purpose:%lu version:%@ resumeToken:%@)>", - self.query, self.targetID, (unsigned long)self.purpose, self.snapshotVersion, - self.resumeToken]; + stringWithFormat:@"<FSTQueryData: query:%@ target:%d purpose:%lu version:%s resumeToken:%@)>", + self.query, self.targetID, (unsigned long)self.purpose, + self.snapshotVersion.timestamp().ToString().c_str(), self.resumeToken]; } -- (instancetype)queryDataByReplacingSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion - resumeToken:(NSData *)resumeToken { +- (instancetype)queryDataByReplacingSnapshotVersion:(SnapshotVersion)snapshotVersion + resumeToken:(NSData *)resumeToken + sequenceNumber:(FSTListenSequenceNumber)sequenceNumber { return [[FSTQueryData alloc] initWithQuery:self.query targetID:self.targetID - listenSequenceNumber:self.sequenceNumber + listenSequenceNumber:sequenceNumber purpose:self.purpose - snapshotVersion:snapshotVersion + snapshotVersion:std::move(snapshotVersion) resumeToken:resumeToken]; } diff --git a/Firestore/Source/Local/FSTReferenceSet.h b/Firestore/Source/Local/FSTReferenceSet.h index 9d842cb..9a90a40 100644 --- a/Firestore/Source/Local/FSTReferenceSet.h +++ b/Firestore/Source/Local/FSTReferenceSet.h @@ -18,7 +18,8 @@ #import "Firestore/Source/Core/FSTTypes.h" #import "Firestore/Source/Local/FSTGarbageCollector.h" -#import "Firestore/Source/Model/FSTDocumentKeySet.h" + +#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" NS_ASSUME_NONNULL_BEGIN @@ -47,13 +48,14 @@ NS_ASSUME_NONNULL_BEGIN - (void)addReferenceToKey:(const firebase::firestore::model::DocumentKey &)key forID:(int)ID; /** Add references to the given document keys for the given ID. */ -- (void)addReferencesToKeys:(FSTDocumentKeySet *)keys forID:(int)ID; +- (void)addReferencesToKeys:(const firebase::firestore::model::DocumentKeySet &)keys forID:(int)ID; /** Removes a reference to the given document key for the given ID. */ - (void)removeReferenceToKey:(const firebase::firestore::model::DocumentKey &)key forID:(int)ID; /** Removes references to the given document keys for the given ID. */ -- (void)removeReferencesToKeys:(FSTDocumentKeySet *)keys forID:(int)ID; +- (void)removeReferencesToKeys:(const firebase::firestore::model::DocumentKeySet &)keys + forID:(int)ID; /** Clears all references with a given ID. Calls -removeReferenceToKey: for each key removed. */ - (void)removeReferencesForID:(int)ID; @@ -62,7 +64,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)removeAllReferences; /** Returns all of the document keys that have had references added for the given ID. */ -- (FSTDocumentKeySet *)referencedKeysForID:(int)ID; +- (firebase::firestore::model::DocumentKeySet)referencedKeysForID:(int)ID; @end diff --git a/Firestore/Source/Local/FSTReferenceSet.mm b/Firestore/Source/Local/FSTReferenceSet.mm index 14f5d47..6b34725 100644 --- a/Firestore/Source/Local/FSTReferenceSet.mm +++ b/Firestore/Source/Local/FSTReferenceSet.mm @@ -17,10 +17,12 @@ #import "Firestore/Source/Local/FSTReferenceSet.h" #import "Firestore/Source/Local/FSTDocumentReference.h" +#import "Firestore/third_party/Immutable/FSTImmutableSortedSet.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::DocumentKeySet; NS_ASSUME_NONNULL_BEGIN @@ -68,20 +70,20 @@ NS_ASSUME_NONNULL_BEGIN self.referencesByID = [self.referencesByID setByAddingObject:reference]; } -- (void)addReferencesToKeys:(FSTDocumentKeySet *)keys forID:(int)ID { - [keys enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) { +- (void)addReferencesToKeys:(const DocumentKeySet &)keys forID:(int)ID { + for (const DocumentKey &key : keys) { [self addReferenceToKey:key forID:ID]; - }]; + } } - (void)removeReferenceToKey:(const DocumentKey &)key forID:(int)ID { [self removeReference:[[FSTDocumentReference alloc] initWithKey:key ID:ID]]; } -- (void)removeReferencesToKeys:(FSTDocumentKeySet *)keys forID:(int)ID { - [keys enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) { +- (void)removeReferencesToKeys:(const DocumentKeySet &)keys forID:(int)ID { + for (const DocumentKey &key : keys) { [self removeReferenceToKey:key forID:ID]; - }]; + } } - (void)removeReferencesForID:(int)ID { @@ -109,17 +111,17 @@ NS_ASSUME_NONNULL_BEGIN [self.garbageCollector addPotentialGarbageKey:reference.key]; } -- (FSTDocumentKeySet *)referencedKeysForID:(int)ID { +- (DocumentKeySet)referencedKeysForID:(int)ID { FSTDocumentReference *start = [[FSTDocumentReference alloc] initWithKey:DocumentKey::Empty() ID:ID]; FSTDocumentReference *end = [[FSTDocumentReference alloc] initWithKey:DocumentKey::Empty() ID:(ID + 1)]; - __block FSTDocumentKeySet *keys = [FSTDocumentKeySet keySet]; + __block DocumentKeySet keys; [self.referencesByID enumerateObjectsFrom:start to:end usingBlock:^(FSTDocumentReference *reference, BOOL *stop) { - keys = [keys setByAddingObject:reference.key]; + keys = keys.insert(reference.key); }]; return keys; } diff --git a/Firestore/Source/Model/FSTDocument.h b/Firestore/Source/Model/FSTDocument.h index 47e4d28..0f8d4b3 100644 --- a/Firestore/Source/Model/FSTDocument.h +++ b/Firestore/Source/Model/FSTDocument.h @@ -18,10 +18,10 @@ #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" @class FSTFieldValue; @class FSTObjectValue; -@class FSTSnapshotVersion; NS_ASSUME_NONNULL_BEGIN @@ -32,14 +32,13 @@ NS_ASSUME_NONNULL_BEGIN @interface FSTMaybeDocument : NSObject <NSCopying> - (id)init __attribute__((unavailable("Abstract base class"))); - (const firebase::firestore::model::DocumentKey &)key; - -@property(nonatomic, readonly) FSTSnapshotVersion *version; +- (const firebase::firestore::model::SnapshotVersion &)version; @end @interface FSTDocument : FSTMaybeDocument + (instancetype)documentWithData:(FSTObjectValue *)data key:(firebase::firestore::model::DocumentKey)key - version:(FSTSnapshotVersion *)version + version:(firebase::firestore::model::SnapshotVersion)version hasLocalMutations:(BOOL)mutations; - (nullable FSTFieldValue *)fieldForPath:(const firebase::firestore::model::FieldPath &)path; @@ -51,7 +50,7 @@ NS_ASSUME_NONNULL_BEGIN @interface FSTDeletedDocument : FSTMaybeDocument + (instancetype)documentWithKey:(firebase::firestore::model::DocumentKey)key - version:(FSTSnapshotVersion *)version; + version:(firebase::firestore::model::SnapshotVersion)version; @end /** An NSComparator suitable for comparing docs using only their keys. */ diff --git a/Firestore/Source/Model/FSTDocument.mm b/Firestore/Source/Model/FSTDocument.mm index 9898c2a..8c4c801 100644 --- a/Firestore/Source/Model/FSTDocument.mm +++ b/Firestore/Source/Model/FSTDocument.mm @@ -18,37 +18,38 @@ #include <utility> -#import "Firestore/Source/Core/FSTSnapshotVersion.h" #import "Firestore/Source/Model/FSTFieldValue.h" #import "Firestore/Source/Util/FSTAssert.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" +#include "Firestore/core/src/firebase/firestore/util/hashing.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldPath; +using firebase::firestore::model::SnapshotVersion; NS_ASSUME_NONNULL_BEGIN @interface FSTMaybeDocument () - (instancetype)initWithKey:(DocumentKey)key - version:(FSTSnapshotVersion *)version NS_DESIGNATED_INITIALIZER; + version:(SnapshotVersion)version NS_DESIGNATED_INITIALIZER; @end @implementation FSTMaybeDocument { DocumentKey _key; + SnapshotVersion _version; } -- (instancetype)initWithKey:(DocumentKey)key version:(FSTSnapshotVersion *)version { - FSTAssert(!!version, @"Version must not be nil."); +- (instancetype)initWithKey:(DocumentKey)key version:(SnapshotVersion)version { self = [super init]; if (self) { _key = std::move(key); - _version = version; + _version = std::move(version); } return self; } @@ -62,25 +63,29 @@ NS_ASSUME_NONNULL_BEGIN return _key; } +- (const SnapshotVersion &)version { + return _version; +} + @end @implementation FSTDocument + (instancetype)documentWithData:(FSTObjectValue *)data key:(DocumentKey)key - version:(FSTSnapshotVersion *)version + version:(SnapshotVersion)version hasLocalMutations:(BOOL)mutations { return [[FSTDocument alloc] initWithData:data key:std::move(key) - version:version + version:std::move(version) hasLocalMutations:mutations]; } - (instancetype)initWithData:(FSTObjectValue *)data key:(DocumentKey)key - version:(FSTSnapshotVersion *)version + version:(SnapshotVersion)version hasLocalMutations:(BOOL)mutations { - self = [super initWithKey:std::move(key) version:version]; + self = [super initWithKey:std::move(key) version:std::move(version)]; if (self) { _data = data; _localMutations = mutations; @@ -97,21 +102,22 @@ NS_ASSUME_NONNULL_BEGIN } FSTDocument *otherDoc = other; - return [self.key isEqual:otherDoc.key] && [self.version isEqual:otherDoc.version] && + return [self.key isEqual:otherDoc.key] && self.version == otherDoc.version && [self.data isEqual:otherDoc.data] && self.hasLocalMutations == otherDoc.hasLocalMutations; } - (NSUInteger)hash { NSUInteger result = [self.key hash]; - result = result * 31 + [self.version hash]; + result = result * 31 + self.version.Hash(); result = result * 31 + [self.data hash]; result = result * 31 + (self.hasLocalMutations ? 1 : 0); return result; } - (NSString *)description { - return [NSString stringWithFormat:@"<FSTDocument: key:%s version:%@ localMutations:%@ data:%@>", - self.key.ToString().c_str(), self.version, + return [NSString stringWithFormat:@"<FSTDocument: key:%s version:%s localMutations:%@ data:%@>", + self.key.ToString().c_str(), + self.version.timestamp().ToString().c_str(), self.localMutations ? @"YES" : @"NO", self.data]; } @@ -123,8 +129,8 @@ NS_ASSUME_NONNULL_BEGIN @implementation FSTDeletedDocument -+ (instancetype)documentWithKey:(DocumentKey)key version:(FSTSnapshotVersion *)version { - return [[FSTDeletedDocument alloc] initWithKey:std::move(key) version:version]; ++ (instancetype)documentWithKey:(DocumentKey)key version:(SnapshotVersion)version { + return [[FSTDeletedDocument alloc] initWithKey:std::move(key) version:std::move(version)]; } - (BOOL)isEqual:(id)other { @@ -136,12 +142,12 @@ NS_ASSUME_NONNULL_BEGIN } FSTDocument *otherDoc = other; - return [self.key isEqual:otherDoc.key] && [self.version isEqual:otherDoc.version]; + return [self.key isEqual:otherDoc.key] && self.version == otherDoc.version; } - (NSUInteger)hash { NSUInteger result = [self.key hash]; - result = result * 31 + [self.version hash]; + result = result * 31 + self.version.Hash(); return result; } diff --git a/Firestore/Source/Model/FSTDocumentKey.mm b/Firestore/Source/Model/FSTDocumentKey.mm index 679d7a6..d29df86 100644 --- a/Firestore/Source/Model/FSTDocumentKey.mm +++ b/Firestore/Source/Model/FSTDocumentKey.mm @@ -23,6 +23,7 @@ #import "Firestore/Source/Util/FSTAssert.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "Firestore/core/src/firebase/firestore/util/hashing.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; @@ -72,7 +73,7 @@ NS_ASSUME_NONNULL_BEGIN } - (NSUInteger)hash { - return _path.Hash(); + return util::Hash(_path); } - (NSString *)description { diff --git a/Firestore/Source/Model/FSTDocumentKeySet.h b/Firestore/Source/Model/FSTDocumentKeySet.h deleted file mode 100644 index 80f6624..0000000 --- a/Firestore/Source/Model/FSTDocumentKeySet.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 <Foundation/Foundation.h> - -#import "Firestore/third_party/Immutable/FSTImmutableSortedSet.h" - -@class FSTDocumentKey; - -NS_ASSUME_NONNULL_BEGIN - -/** Convenience type for a set of keys, since they are so common. */ -typedef FSTImmutableSortedSet<FSTDocumentKey *> FSTDocumentKeySet; - -@interface FSTImmutableSortedSet (FSTDocumentKey) - -/** Returns a new set using the DocumentKeyComparator. */ -+ (FSTDocumentKeySet *)keySet; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Model/FSTDocumentKeySet.mm b/Firestore/Source/Model/FSTDocumentKeySet.mm deleted file mode 100644 index f07b785..0000000 --- a/Firestore/Source/Model/FSTDocumentKeySet.mm +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 "Firestore/Source/Model/FSTDocumentKeySet.h" - -#import "Firestore/Source/Model/FSTDocumentKey.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation FSTImmutableSortedSet (FSTDocumentKey) - -+ (instancetype)keySet { - return [FSTDocumentKeySet setWithComparator:FSTDocumentKeyComparator]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Model/FSTDocumentVersionDictionary.h b/Firestore/Source/Model/FSTDocumentVersionDictionary.h deleted file mode 100644 index 674614e..0000000 --- a/Firestore/Source/Model/FSTDocumentVersionDictionary.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 <Foundation/Foundation.h> - -#import "Firestore/third_party/Immutable/FSTImmutableSortedDictionary.h" - -@class FSTDocumentKey; -@class FSTSnapshotVersion; - -NS_ASSUME_NONNULL_BEGIN - -/** A map of key to version number. */ -typedef FSTImmutableSortedDictionary<FSTDocumentKey *, FSTSnapshotVersion *> - FSTDocumentVersionDictionary; - -/** - * Extension to FSTImmutableSortedDictionary that allows natural construction of - * FSTDocumentVersionDictionary. - */ -@interface FSTImmutableSortedDictionary (FSTDocumentVersionDictionary) - -+ (instancetype)documentVersionDictionary; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Model/FSTDocumentVersionDictionary.mm b/Firestore/Source/Model/FSTDocumentVersionDictionary.mm deleted file mode 100644 index 870e082..0000000 --- a/Firestore/Source/Model/FSTDocumentVersionDictionary.mm +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 "Firestore/Source/Model/FSTDocumentVersionDictionary.h" - -#import "Firestore/Source/Core/FSTSnapshotVersion.h" -#import "Firestore/Source/Model/FSTDocumentKey.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation FSTImmutableSortedDictionary (FSTDocumentVersionDictionary) - -+ (instancetype)documentVersionDictionary { - static FSTDocumentVersionDictionary *singleton; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - singleton = [FSTDocumentVersionDictionary dictionaryWithComparator:FSTDocumentKeyComparator]; - }); - return singleton; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Model/FSTMutation.h b/Firestore/Source/Model/FSTMutation.h index 7261f30..0acec15 100644 --- a/Firestore/Source/Model/FSTMutation.h +++ b/Firestore/Source/Model/FSTMutation.h @@ -24,13 +24,15 @@ #include "Firestore/core/src/firebase/firestore/model/field_path.h" #include "Firestore/core/src/firebase/firestore/model/field_transform.h" #include "Firestore/core/src/firebase/firestore/model/precondition.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" #include "Firestore/core/src/firebase/firestore/model/transform_operations.h" +#include "absl/types/optional.h" + @class FSTDocument; @class FSTFieldValue; @class FSTMaybeDocument; @class FSTObjectValue; -@class FSTSnapshotVersion; @class FIRTimestamp; NS_ASSUME_NONNULL_BEGIN @@ -40,12 +42,12 @@ NS_ASSUME_NONNULL_BEGIN @interface FSTMutationResult : NSObject - (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithVersion:(FSTSnapshotVersion *_Nullable)version +- (instancetype)initWithVersion:(absl::optional<firebase::firestore::model::SnapshotVersion>)version transformResults:(NSArray<FSTFieldValue *> *_Nullable)transformResults NS_DESIGNATED_INITIALIZER; /** The version at which the mutation was committed or null for a delete. */ -@property(nonatomic, strong, readonly, nullable) FSTSnapshotVersion *version; +- (const absl::optional<firebase::firestore::model::SnapshotVersion> &)version; /** * The resulting fields returned from the backend after a FSTTransformMutation has been committed. diff --git a/Firestore/Source/Model/FSTMutation.mm b/Firestore/Source/Model/FSTMutation.mm index 3432a7c..82a535e 100644 --- a/Firestore/Source/Model/FSTMutation.mm +++ b/Firestore/Source/Model/FSTMutation.mm @@ -23,7 +23,6 @@ #import "FIRTimestamp.h" -#import "Firestore/Source/Core/FSTSnapshotVersion.h" #import "Firestore/Source/Model/FSTDocument.h" #import "Firestore/Source/Model/FSTFieldValue.h" #import "Firestore/Source/Util/FSTAssert.h" @@ -36,6 +35,8 @@ #include "Firestore/core/src/firebase/firestore/model/precondition.h" #include "Firestore/core/src/firebase/firestore/model/transform_operations.h" +#include "absl/types/optional.h" + using firebase::firestore::model::ArrayTransform; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldMask; @@ -43,23 +44,30 @@ using firebase::firestore::model::FieldPath; using firebase::firestore::model::FieldTransform; using firebase::firestore::model::Precondition; using firebase::firestore::model::ServerTimestampTransform; +using firebase::firestore::model::SnapshotVersion; using firebase::firestore::model::TransformOperation; NS_ASSUME_NONNULL_BEGIN #pragma mark - FSTMutationResult -@implementation FSTMutationResult +@implementation FSTMutationResult { + absl::optional<SnapshotVersion> _version; +} -- (instancetype)initWithVersion:(nullable FSTSnapshotVersion *)version +- (instancetype)initWithVersion:(absl::optional<SnapshotVersion>)version transformResults:(nullable NSArray<FSTFieldValue *> *)transformResults { if (self = [super init]) { - _version = version; + _version = std::move(version); _transformResults = transformResults; } return self; } +- (const absl::optional<SnapshotVersion> &)version { + return _version; +} + @end #pragma mark - FSTMutation @@ -157,7 +165,7 @@ NS_ASSUME_NONNULL_BEGIN // If the document didn't exist before, create it. return [FSTDocument documentWithData:self.value key:self.key - version:[FSTSnapshotVersion noVersion] + version:SnapshotVersion::None() hasLocalMutations:hasLocalMutations]; } @@ -239,10 +247,10 @@ NS_ASSUME_NONNULL_BEGIN if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) { // Precondition applied, so create the document if necessary const DocumentKey &key = maybeDoc ? maybeDoc.key : self.key; - FSTSnapshotVersion *version = maybeDoc ? maybeDoc.version : [FSTSnapshotVersion noVersion]; + SnapshotVersion version = maybeDoc ? maybeDoc.version : SnapshotVersion::None(); maybeDoc = [FSTDocument documentWithData:[FSTObjectValue objectValue] key:key - version:version + version:std::move(version) hasLocalMutations:hasLocalMutations]; } @@ -384,30 +392,15 @@ serverTransformResultsWithBaseDocument:(nullable FSTMaybeDocument *)baseDocument for (NSUInteger i = 0; i < serverTransformResults.count; i++) { const FieldTransform &fieldTransform = self.fieldTransforms[i]; + const TransformOperation &transform = fieldTransform.transformation(); + FSTFieldValue *previousValue = nil; if ([baseDocument isMemberOfClass:[FSTDocument class]]) { previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()]; } - FSTFieldValue *transformResult; - // The server just sends null as the transform result for array union / remove operations, so - // we have to calculate a result the same as we do for local applications. - if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayUnion) { - transformResult = [self - arrayUnionResultWithElements:ArrayTransform::Elements(fieldTransform.transformation()) - previousValue:previousValue]; - - } else if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayRemove) { - transformResult = [self - arrayRemoveResultWithElements:ArrayTransform::Elements(fieldTransform.transformation()) - previousValue:previousValue]; - - } else { - // Just use the server-supplied result. - transformResult = serverTransformResults[i]; - } - - [transformResults addObject:transformResult]; + [transformResults + addObject:transform.ApplyToRemoteDocument(previousValue, serverTransformResults[i])]; } return transformResults; } @@ -426,77 +419,18 @@ serverTransformResultsWithBaseDocument:(nullable FSTMaybeDocument *)baseDocument writeTime:(FIRTimestamp *)localWriteTime { NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array]; for (const FieldTransform &fieldTransform : self.fieldTransforms) { + const TransformOperation &transform = fieldTransform.transformation(); + FSTFieldValue *previousValue = nil; if ([baseDocument isMemberOfClass:[FSTDocument class]]) { previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()]; } - FSTFieldValue *transformResult; - if (fieldTransform.transformation().type() == TransformOperation::Type::ServerTimestamp) { - transformResult = - [FSTServerTimestampValue serverTimestampValueWithLocalWriteTime:localWriteTime - previousValue:previousValue]; - - } else if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayUnion) { - transformResult = [self - arrayUnionResultWithElements:ArrayTransform::Elements(fieldTransform.transformation()) - previousValue:previousValue]; - - } else if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayRemove) { - transformResult = [self - arrayRemoveResultWithElements:ArrayTransform::Elements(fieldTransform.transformation()) - previousValue:previousValue]; - - } else { - FSTFail(@"Encountered unknown transform: %d type", fieldTransform.transformation().type()); - } - - [transformResults addObject:transformResult]; + [transformResults addObject:transform.ApplyToLocalView(previousValue, localWriteTime)]; } return transformResults; } -/** - * Transforms the provided `previousValue` via the provided `elements`. Used both for local - * application and after server acknowledgement. - */ -- (FSTFieldValue *)arrayUnionResultWithElements:(const std::vector<FSTFieldValue *> &)elements - previousValue:(FSTFieldValue *)previousValue { - NSMutableArray<FSTFieldValue *> *result = [self coercedFieldValuesArray:previousValue]; - for (FSTFieldValue *element : elements) { - if (![result containsObject:element]) { - [result addObject:element]; - } - } - return [[FSTArrayValue alloc] initWithValueNoCopy:result]; -} - -/** - * Transforms the provided `previousValue` via the provided `elements`. Used both for local - * application and after server acknowledgement. - */ -- (FSTFieldValue *)arrayRemoveResultWithElements:(const std::vector<FSTFieldValue *> &)elements - previousValue:(FSTFieldValue *)previousValue { - NSMutableArray<FSTFieldValue *> *result = [self coercedFieldValuesArray:previousValue]; - for (FSTFieldValue *element : elements) { - [result removeObject:element]; - } - return [[FSTArrayValue alloc] initWithValueNoCopy:result]; -} - -/** - * Inspects the provided value, returning a mutable copy of the internal array if it's an - * FSTArrayValue and an empty mutable array if it's nil or any other type of FSTFieldValue. - */ -- (NSMutableArray<FSTFieldValue *> *)coercedFieldValuesArray:(nullable FSTFieldValue *)value { - if ([value isMemberOfClass:[FSTArrayValue class]]) { - return [NSMutableArray arrayWithArray:((FSTArrayValue *)value).internalValue]; - } else { - // coerce to empty array. - return [NSMutableArray array]; - } -} - - (FSTObjectValue *)transformObject:(FSTObjectValue *)objectValue transformResults:(NSArray<FSTFieldValue *> *)transformResults { FSTAssert(transformResults.count == self.fieldTransforms.size(), @@ -556,7 +490,7 @@ serverTransformResultsWithBaseDocument:(nullable FSTMaybeDocument *)baseDocument FSTAssert([maybeDoc.key isEqual:self.key], @"Can only delete a document with the same key"); } - return [FSTDeletedDocument documentWithKey:self.key version:[FSTSnapshotVersion noVersion]]; + return [FSTDeletedDocument documentWithKey:self.key version:SnapshotVersion::None()]; } @end diff --git a/Firestore/Source/Model/FSTMutationBatch.h b/Firestore/Source/Model/FSTMutationBatch.h index 3c82338..761a885 100644 --- a/Firestore/Source/Model/FSTMutationBatch.h +++ b/Firestore/Source/Model/FSTMutationBatch.h @@ -16,17 +16,29 @@ #import <Foundation/Foundation.h> +#include <unordered_map> + #import "Firestore/Source/Core/FSTTypes.h" -#import "Firestore/Source/Model/FSTDocumentKeySet.h" -#import "Firestore/Source/Model/FSTDocumentVersionDictionary.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" @class FSTMutation; @class FIRTimestamp; @class FSTMutationResult; @class FSTMutationBatchResult; -@class FSTSnapshotVersion; + +namespace firebase { +namespace firestore { +namespace model { + +// TODO(wilhuff): make this type a member of MutationBatchResult once that's a C++ class. +using DocumentVersionMap = std::unordered_map<DocumentKey, SnapshotVersion, DocumentKeyHash>; + +} // namespace model +} // namespace firestore +} // namespace firebase NS_ASSUME_NONNULL_BEGIN @@ -85,7 +97,7 @@ extern const FSTBatchID kFSTBatchIDUnknown; - (FSTMutationBatch *)toTombstone; /** Returns the set of unique keys referenced by all mutations in the batch. */ -- (FSTDocumentKeySet *)keys; +- (firebase::firestore::model::DocumentKeySet)keys; @property(nonatomic, assign, readonly) FSTBatchID batchID; @property(nonatomic, strong, readonly) FIRTimestamp *localWriteTime; @@ -106,15 +118,17 @@ extern const FSTBatchID kFSTBatchIDUnknown; * (as docVersions). */ + (instancetype)resultWithBatch:(FSTMutationBatch *)batch - commitVersion:(FSTSnapshotVersion *)commitVersion + commitVersion:(firebase::firestore::model::SnapshotVersion)commitVersion mutationResults:(NSArray<FSTMutationResult *> *)mutationResults streamToken:(nullable NSData *)streamToken; +- (const firebase::firestore::model::SnapshotVersion &)commitVersion; + @property(nonatomic, strong, readonly) FSTMutationBatch *batch; -@property(nonatomic, strong, readonly) FSTSnapshotVersion *commitVersion; @property(nonatomic, strong, readonly) NSArray<FSTMutationResult *> *mutationResults; @property(nonatomic, strong, readonly, nullable) NSData *streamToken; -@property(nonatomic, strong, readonly) FSTDocumentVersionDictionary *docVersions; + +- (const firebase::firestore::model::DocumentVersionMap &)docVersions; @end diff --git a/Firestore/Source/Model/FSTMutationBatch.mm b/Firestore/Source/Model/FSTMutationBatch.mm index e62a72c..1e9189c 100644 --- a/Firestore/Source/Model/FSTMutationBatch.mm +++ b/Firestore/Source/Model/FSTMutationBatch.mm @@ -16,16 +16,19 @@ #import "Firestore/Source/Model/FSTMutationBatch.h" +#include <utility> + #import "FIRTimestamp.h" -#import "Firestore/Source/Core/FSTSnapshotVersion.h" #import "Firestore/Source/Model/FSTDocument.h" #import "Firestore/Source/Model/FSTMutation.h" #import "Firestore/Source/Util/FSTAssert.h" -#include "Firestore/core/src/firebase/firestore/model/document_key.h" - using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::DocumentKeyHash; +using firebase::firestore::model::SnapshotVersion; +using firebase::firestore::model::DocumentKeySet; +using firebase::firestore::model::DocumentVersionMap; NS_ASSUME_NONNULL_BEGIN @@ -113,10 +116,10 @@ const FSTBatchID kFSTBatchIDUnknown = -1; } // TODO(klimt): This could use NSMutableDictionary instead. -- (FSTDocumentKeySet *)keys { - FSTDocumentKeySet *set = [FSTDocumentKeySet keySet]; +- (DocumentKeySet)keys { + DocumentKeySet set; for (FSTMutation *mutation in self.mutations) { - set = [set setByAddingObject:mutation.key]; + set = set.insert(mutation.key); } return set; } @@ -127,56 +130,66 @@ const FSTBatchID kFSTBatchIDUnknown = -1; @interface FSTMutationBatchResult () - (instancetype)initWithBatch:(FSTMutationBatch *)batch - commitVersion:(FSTSnapshotVersion *)commitVersion + commitVersion:(SnapshotVersion)commitVersion mutationResults:(NSArray<FSTMutationResult *> *)mutationResults streamToken:(nullable NSData *)streamToken - docVersions:(FSTDocumentVersionDictionary *)docVersions NS_DESIGNATED_INITIALIZER; + docVersions:(DocumentVersionMap)docVersions NS_DESIGNATED_INITIALIZER; @end -@implementation FSTMutationBatchResult +@implementation FSTMutationBatchResult { + SnapshotVersion _commitVersion; + DocumentVersionMap _docVersions; +} - (instancetype)initWithBatch:(FSTMutationBatch *)batch - commitVersion:(FSTSnapshotVersion *)commitVersion + commitVersion:(SnapshotVersion)commitVersion mutationResults:(NSArray<FSTMutationResult *> *)mutationResults streamToken:(nullable NSData *)streamToken - docVersions:(FSTDocumentVersionDictionary *)docVersions { + docVersions:(DocumentVersionMap)docVersions { if (self = [super init]) { _batch = batch; - _commitVersion = commitVersion; + _commitVersion = std::move(commitVersion); _mutationResults = mutationResults; _streamToken = streamToken; - _docVersions = docVersions; + _docVersions = std::move(docVersions); } return self; } +- (const SnapshotVersion &)commitVersion { + return _commitVersion; +} + +- (const DocumentVersionMap &)docVersions { + return _docVersions; +} + + (instancetype)resultWithBatch:(FSTMutationBatch *)batch - commitVersion:(FSTSnapshotVersion *)commitVersion + commitVersion:(SnapshotVersion)commitVersion mutationResults:(NSArray<FSTMutationResult *> *)mutationResults streamToken:(nullable NSData *)streamToken { FSTAssert(batch.mutations.count == mutationResults.count, @"Mutations sent %lu must equal results received %lu", (unsigned long)batch.mutations.count, (unsigned long)mutationResults.count); - FSTDocumentVersionDictionary *docVersions = - [FSTDocumentVersionDictionary documentVersionDictionary]; + DocumentVersionMap docVersions; NSArray<FSTMutation *> *mutations = batch.mutations; for (NSUInteger i = 0; i < mutations.count; i++) { - FSTSnapshotVersion *_Nullable version = mutationResults[i].version; + absl::optional<SnapshotVersion> version = mutationResults[i].version; if (!version) { // deletes don't have a version, so we substitute the commitVersion // of the entire batch. version = commitVersion; } - docVersions = [docVersions dictionaryBySettingObject:version forKey:mutations[i].key]; + docVersions[mutations[i].key] = version.value(); } return [[FSTMutationBatchResult alloc] initWithBatch:batch - commitVersion:commitVersion + commitVersion:std::move(commitVersion) mutationResults:mutationResults streamToken:streamToken - docVersions:docVersions]; + docVersions:std::move(docVersions)]; } @end diff --git a/Firestore/Source/Public/FIRDocumentReference.h b/Firestore/Source/Public/FIRDocumentReference.h index 4aa8c45..7baa30a 100644 --- a/Firestore/Source/Public/FIRDocumentReference.h +++ b/Firestore/Source/Public/FIRDocumentReference.h @@ -92,6 +92,23 @@ NS_SWIFT_NAME(DocumentReference) - (void)setData:(NSDictionary<NSString *, id> *)documentData merge:(BOOL)merge; /** + * Writes to the document referred to by `document` and only replace the fields + * specified under `mergeFields`. Any field that is not specified in `mergeFields` + * is ignored and remains untouched. If the document doesn't yet exist, + * this method creates it and then sets the data. + * + * It is an error to include a field in `mergeFields` that does not have a corresponding + * value in the `data` dictionary. + * + * @param documentData An `NSDictionary` containing the fields that make up the document + * to be written. + * @param mergeFields An `NSArray` that contains a list of `NSString` or `FIRFieldPath` elements + * specifying which fields to merge. Fields can contain dots to reference nested fields within + * the document. + */ +- (void)setData:(NSDictionary<NSString *, id> *)documentData mergeFields:(NSArray<id> *)mergeFields; + +/** * Overwrites the document referred to by this `FIRDocumentReference`. If no document exists, it * is created. If a document already exists, it is overwritten. * @@ -121,6 +138,28 @@ NS_SWIFT_NAME(DocumentReference) completion:(nullable void (^)(NSError *_Nullable error))completion; /** + * Writes to the document referred to by `document` and only replace the fields + * specified under `mergeFields`. Any field that is not specified in `mergeFields` + * is ignored and remains untouched. If the document doesn't yet exist, + * this method creates it and then sets the data. + * + * It is an error to include a field in `mergeFields` that does not have a corresponding + * value in the `data` dictionary. + * + * @param documentData An `NSDictionary` containing the fields that make up the document + * to be written. + * @param mergeFields An `NSArray` that contains a list of `NSString` or `FIRFieldPath` elements + * specifying which fields to merge. Fields can contain dots to reference nested fields within + * the document. + * @param completion A block to execute once the document has been successfully written to the + * server. This block will not be called while the client is offline, though local + * changes will be visible immediately. + */ +- (void)setData:(NSDictionary<NSString *, id> *)documentData + mergeFields:(NSArray<id> *)mergeFields + completion:(nullable void (^)(NSError *_Nullable error))completion; + +/** * Updates fields in the document referred to by this `FIRDocumentReference`. * If the document does not exist, the update fails (specify a completion block to be notified). * diff --git a/Firestore/Source/Public/FIRTransaction.h b/Firestore/Source/Public/FIRTransaction.h index 2fa4430..e53414d 100644 --- a/Firestore/Source/Public/FIRTransaction.h +++ b/Firestore/Source/Public/FIRTransaction.h @@ -65,6 +65,30 @@ NS_SWIFT_NAME(Transaction) // clang-format on /** + * Writes to the document referred to by `document` and only replace the fields + * specified under `mergeFields`. Any field that is not specified in `mergeFields` + * is ignored and remains untouched. If the document doesn't yet exist, + * this method creates it and then sets the data. + * + * It is an error to include a field in `mergeFields` that does not have a corresponding + * value in the `data` dictionary. + * + * @param data An `NSDictionary` containing the fields that make up the document + * to be written. + * @param document A reference to the document whose data should be overwritten. + * @param mergeFields An `NSArray` that contains a list of `NSString` or `FIRFieldPath` elements + * specifying which fields to merge. Fields can contain dots to reference nested fields within + * the document. + * @return This `FIRTransaction` instance. Used for chaining method calls. + */ +// clang-format off +- (FIRTransaction *)setData:(NSDictionary<NSString *, id> *)data + forDocument:(FIRDocumentReference *)document + mergeFields:(NSArray<id> *)mergeFields + NS_SWIFT_NAME(setData(_:forDocument:mergeFields:)); +// clang-format on + +/** * Updates fields in the document referred to by `document`. * If the document does not exist, the transaction will fail. * diff --git a/Firestore/Source/Public/FIRWriteBatch.h b/Firestore/Source/Public/FIRWriteBatch.h index 1568723..22d1b16 100644 --- a/Firestore/Source/Public/FIRWriteBatch.h +++ b/Firestore/Source/Public/FIRWriteBatch.h @@ -68,6 +68,29 @@ NS_SWIFT_NAME(WriteBatch) // clang-format on /** + * Writes to the document referred to by `document` and only replace the fields + * specified under `mergeFields`. Any field that is not specified in `mergeFields` + * is ignored and remains untouched. If the document doesn't yet exist, + * this method creates it and then sets the data. + * + * It is an error to include a field in `mergeFields` that does not have a corresponding + * value in the `data` dictionary. + * + * @param data An `NSDictionary` that contains the fields and data to write to the document. + * @param document A reference to the document whose data should be overwritten. + * @param mergeFields An `NSArray` that contains a list of `NSString` or `FIRFieldPath` elements + * specifying which fields to merge. Fields can contain dots to reference nested fields within + * the document. + * @return This `FIRWriteBatch` instance. Used for chaining method calls. + */ +// clang-format off +- (FIRWriteBatch *)setData:(NSDictionary<NSString *, id> *)data + forDocument:(FIRDocumentReference *)document + mergeFields:(NSArray<id> *)mergeFields + NS_SWIFT_NAME(setData(_:forDocument:mergeFields:)); +// clang-format on + +/** * Updates fields in the document referred to by `document`. * If document does not exist, the write batch will fail. * diff --git a/Firestore/Source/Remote/FSTDatastore.h b/Firestore/Source/Remote/FSTDatastore.h index b3ba46c..da14b6e 100644 --- a/Firestore/Source/Remote/FSTDatastore.h +++ b/Firestore/Source/Remote/FSTDatastore.h @@ -31,7 +31,6 @@ @class FSTMutationResult; @class FSTQueryData; @class FSTSerializerBeta; -@class FSTSnapshotVersion; @class FSTWatchChange; @class FSTWatchStream; @class FSTWriteStream; diff --git a/Firestore/Source/Remote/FSTRemoteEvent.h b/Firestore/Source/Remote/FSTRemoteEvent.h index 0f6b6c7..c84e34d 100644 --- a/Firestore/Source/Remote/FSTRemoteEvent.h +++ b/Firestore/Source/Remote/FSTRemoteEvent.h @@ -20,14 +20,14 @@ #import "Firestore/Source/Core/FSTTypes.h" #import "Firestore/Source/Model/FSTDocumentDictionary.h" -#import "Firestore/Source/Model/FSTDocumentKeySet.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/document_key_set.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" @class FSTDocument; @class FSTExistenceFilter; @class FSTMaybeDocument; -@class FSTSnapshotVersion; @class FSTWatchChange; @class FSTQueryData; @@ -43,6 +43,15 @@ NS_ASSUME_NONNULL_BEGIN * base class. */ @interface FSTTargetMapping : NSObject + +/** + * Strips out mapping changes that aren't actually changes. That is, if the document already + * existed in the target, and is being added in the target, and this is not a reset, we can + * skip doing the work to associate the document with the target because it has already been done. + */ +- (void)filterUpdatesUsingExistingKeys: + (const firebase::firestore::model::DocumentKeySet &)existingKeys; + @end #pragma mark - FSTResetMapping @@ -57,7 +66,7 @@ NS_ASSUME_NONNULL_BEGIN + (FSTResetMapping *)mappingWithDocuments:(NSArray<FSTDocument *> *)documents; /** The new set of documents for the target. */ -@property(nonatomic, strong, readonly) FSTDocumentKeySet *documents; +- (const firebase::firestore::model::DocumentKeySet &)documents; @end #pragma mark - FSTUpdateMapping @@ -74,12 +83,13 @@ NS_ASSUME_NONNULL_BEGIN + (FSTUpdateMapping *)mappingWithAddedDocuments:(NSArray<FSTDocument *> *)added removedDocuments:(NSArray<FSTDocument *> *)removed; -- (FSTDocumentKeySet *)applyTo:(FSTDocumentKeySet *)keys; +- (firebase::firestore::model::DocumentKeySet)applyTo: + (const firebase::firestore::model::DocumentKeySet &)keys; /** The documents added to the target. */ -@property(nonatomic, strong, readonly) FSTDocumentKeySet *addedDocuments; +- (const firebase::firestore::model::DocumentKeySet &)addedDocuments; /** The documents removed from the target. */ -@property(nonatomic, strong, readonly) FSTDocumentKeySet *removedDocuments; +- (const firebase::firestore::model::DocumentKeySet &)removedDocuments; @end #pragma mark - FSTTargetChange @@ -107,6 +117,12 @@ typedef NS_ENUM(NSUInteger, FSTCurrentStatusUpdate) { @interface FSTTargetChange : NSObject /** + * Creates a new target change with the given SnapshotVersion. + */ +- (instancetype)initWithSnapshotVersion: + (firebase::firestore::model::SnapshotVersion)snapshotVersion; + +/** * Creates a new target change with the given documents. Instances of FSTDocument are considered * added. Instance of FSTDeletedDocument are considered removed. This is intended primarily for * testing. @@ -115,6 +131,12 @@ typedef NS_ENUM(NSUInteger, FSTCurrentStatusUpdate) { currentStatusUpdate:(FSTCurrentStatusUpdate)currentStatusUpdate; /** + * The snapshot version representing the last state at which this target received a consistent + * snapshot from the backend. + */ +- (const firebase::firestore::model::SnapshotVersion &)snapshotVersion; + +/** * The new "current" (synced) status of this target. Set to CurrentStatusUpdateNone if the status * should not be updated. Note "current" has special meaning for in the RPC protocol that implies * that a target is both up-to-date and consistent with the rest of the watch stream. @@ -125,12 +147,6 @@ typedef NS_ENUM(NSUInteger, FSTCurrentStatusUpdate) { @property(nonatomic, strong, readonly) FSTTargetMapping *mapping; /** - * The snapshot version representing the last state at which this target received a consistent - * snapshot from the backend. - */ -@property(nonatomic, strong, readonly) FSTSnapshotVersion *snapshotVersion; - -/** * An opaque, server-assigned token that allows watching a query to be resumed after disconnecting * without retransmitting all the data that matches the query. The resume token essentially * identifies a point in time from which the server should resume sending results. @@ -147,14 +163,15 @@ typedef NS_ENUM(NSUInteger, FSTCurrentStatusUpdate) { */ @interface FSTRemoteEvent : NSObject -+ (instancetype) -eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion - targetChanges:(NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges - documentUpdates: - (std::map<firebase::firestore::model::DocumentKey, FSTMaybeDocument *>)documentUpdates; +- (instancetype) +initWithSnapshotVersion:(firebase::firestore::model::SnapshotVersion)snapshotVersion + targetChanges:(NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges + documentUpdates: + (std::map<firebase::firestore::model::DocumentKey, FSTMaybeDocument *>)documentUpdates + limboDocuments:(firebase::firestore::model::DocumentKeySet)limboDocuments; /** The snapshot version this event brings us up to. */ -@property(nonatomic, strong, readonly) FSTSnapshotVersion *snapshotVersion; +- (const firebase::firestore::model::SnapshotVersion &)snapshotVersion; /** A map from target to changes to the target. See TargetChange. */ @property(nonatomic, strong, readonly) @@ -166,6 +183,8 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion */ - (const std::map<firebase::firestore::model::DocumentKey, FSTMaybeDocument *> &)documentUpdates; +- (const firebase::firestore::model::DocumentKeySet &)limboDocumentChanges; + /** Adds a document update to this remote event */ - (void)addDocumentUpdate:(FSTMaybeDocument *)document; @@ -175,14 +194,6 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion - (void)synthesizeDeleteForLimboTargetChange:(FSTTargetChange *)targetChange key:(const firebase::firestore::model::DocumentKey &)key; -/** - * Strips out mapping changes that aren't actually changes. That is, if the document already - * existed in the target, and is being added in the target, and this is not a reset, we can - * skip doing the work to associate the document with the target because it has already been done. - */ -- (void)filterUpdatesFromTargetChange:(FSTTargetChange *)targetChange - existingDocuments:(FSTDocumentKeySet *)existingDocuments; - @end #pragma mark - FSTWatchChangeAggregator @@ -194,7 +205,7 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion @interface FSTWatchChangeAggregator : NSObject - (instancetype) -initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion +initWithSnapshotVersion:(firebase::firestore::model::SnapshotVersion)snapshotVersion listenTargets:(NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)listenTargets pendingTargetResponses:(NSDictionary<FSTBoxedTargetID *, NSNumber *> *)pendingTargetResponses NS_DESIGNATED_INITIALIZER; diff --git a/Firestore/Source/Remote/FSTRemoteEvent.mm b/Firestore/Source/Remote/FSTRemoteEvent.mm index 30aa0d3..438072e 100644 --- a/Firestore/Source/Remote/FSTRemoteEvent.mm +++ b/Firestore/Source/Remote/FSTRemoteEvent.mm @@ -19,16 +19,19 @@ #include <map> #include <utility> -#import "Firestore/Source/Core/FSTSnapshotVersion.h" +#import "Firestore/Source/Local/FSTQueryData.h" #import "Firestore/Source/Model/FSTDocument.h" #import "Firestore/Source/Remote/FSTWatchChange.h" #import "Firestore/Source/Util/FSTAssert.h" #import "Firestore/Source/Util/FSTClasses.h" #import "Firestore/Source/Util/FSTLogger.h" - #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/util/hashing.h" using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::SnapshotVersion; +using firebase::firestore::util::Hash; +using firebase::firestore::model::DocumentKeySet; NS_ASSUME_NONNULL_BEGIN @@ -54,32 +57,38 @@ NS_ASSUME_NONNULL_BEGIN @throw FSTAbstractMethodException(); // NOLINT } +- (void)filterUpdatesUsingExistingKeys:(const DocumentKeySet &)existingKeys { + @throw FSTAbstractMethodException(); // NOLINT +} + @end #pragma mark - FSTResetMapping -@interface FSTResetMapping () -@property(nonatomic, strong) FSTDocumentKeySet *documents; -@end - -@implementation FSTResetMapping +@implementation FSTResetMapping { + DocumentKeySet _documents; +} + (instancetype)mappingWithDocuments:(NSArray<FSTDocument *> *)documents { - FSTResetMapping *mapping = [[FSTResetMapping alloc] init]; + DocumentKeySet keys; for (FSTDocument *doc in documents) { - mapping.documents = [mapping.documents setByAddingObject:doc.key]; + keys = keys.insert(doc.key); } - return mapping; + return [[FSTResetMapping alloc] initWithDocuments:std::move(keys)]; } -- (instancetype)init { +- (instancetype)initWithDocuments:(DocumentKeySet)documents { self = [super init]; if (self) { - _documents = [FSTDocumentKeySet keySet]; + _documents = std::move(documents); } return self; } +- (const DocumentKeySet &)documents { + return _documents; +} + - (BOOL)isEqual:(id)other { if (other == self) { return YES; @@ -89,53 +98,66 @@ NS_ASSUME_NONNULL_BEGIN } FSTResetMapping *otherMapping = (FSTResetMapping *)other; - return [self.documents isEqual:otherMapping.documents]; + return _documents == otherMapping.documents; } - (NSUInteger)hash { - return self.documents.hash; + return Hash(_documents); } - (void)addDocumentKey:(const DocumentKey &)documentKey { - self.documents = [self.documents setByAddingObject:documentKey]; + _documents = _documents.insert(documentKey); } - (void)removeDocumentKey:(const DocumentKey &)documentKey { - self.documents = [self.documents setByRemovingObject:documentKey]; + _documents = _documents.erase(documentKey); +} + +- (void)filterUpdatesUsingExistingKeys:(const DocumentKeySet &)existingKeys { + // No-op. Resets are not filtered. } @end #pragma mark - FSTUpdateMapping -@interface FSTUpdateMapping () -@property(nonatomic, strong) FSTDocumentKeySet *addedDocuments; -@property(nonatomic, strong) FSTDocumentKeySet *removedDocuments; -@end - -@implementation FSTUpdateMapping +@implementation FSTUpdateMapping { + DocumentKeySet _addedDocuments; + DocumentKeySet _removedDocuments; +} + (FSTUpdateMapping *)mappingWithAddedDocuments:(NSArray<FSTDocument *> *)added removedDocuments:(NSArray<FSTDocument *> *)removed { - FSTUpdateMapping *mapping = [[FSTUpdateMapping alloc] init]; + DocumentKeySet addedDocuments; + DocumentKeySet removedDocuments; for (FSTDocument *doc in added) { - mapping.addedDocuments = [mapping.addedDocuments setByAddingObject:doc.key]; + addedDocuments = addedDocuments.insert(doc.key); } for (FSTDocument *doc in removed) { - mapping.removedDocuments = [mapping.removedDocuments setByAddingObject:doc.key]; + removedDocuments = removedDocuments.insert(doc.key); } - return mapping; + return [[FSTUpdateMapping alloc] initWithAddedDocuments:std::move(addedDocuments) + removedDocuments:std::move(removedDocuments)]; } -- (instancetype)init { +- (instancetype)initWithAddedDocuments:(DocumentKeySet)addedDocuments + removedDocuments:(DocumentKeySet)removedDocuments { self = [super init]; if (self) { - _addedDocuments = [FSTDocumentKeySet keySet]; - _removedDocuments = [FSTDocumentKeySet keySet]; + _addedDocuments = std::move(addedDocuments); + _removedDocuments = std::move(removedDocuments); } return self; } +- (const DocumentKeySet &)addedDocuments { + return _addedDocuments; +} + +- (const DocumentKeySet &)removedDocuments { + return _removedDocuments; +} + - (BOOL)isEqual:(id)other { if (other == self) { return YES; @@ -145,33 +167,43 @@ NS_ASSUME_NONNULL_BEGIN } FSTUpdateMapping *otherMapping = (FSTUpdateMapping *)other; - return [self.addedDocuments isEqual:otherMapping.addedDocuments] && - [self.removedDocuments isEqual:otherMapping.removedDocuments]; + return _addedDocuments == otherMapping.addedDocuments && + _removedDocuments == otherMapping.removedDocuments; } - (NSUInteger)hash { - return self.addedDocuments.hash * 31 + self.removedDocuments.hash; + return Hash(_addedDocuments, _removedDocuments); } -- (FSTDocumentKeySet *)applyTo:(FSTDocumentKeySet *)keys { - __block FSTDocumentKeySet *result = keys; - [self.addedDocuments enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) { - result = [result setByAddingObject:key]; - }]; - [self.removedDocuments enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) { - result = [result setByRemovingObject:key]; - }]; +- (DocumentKeySet)applyTo:(const DocumentKeySet &)keys { + DocumentKeySet result = keys; + for (const DocumentKey &key : _addedDocuments) { + result = result.insert(key); + } + for (const DocumentKey &key : _removedDocuments) { + result = result.erase(key); + } return result; } - (void)addDocumentKey:(const DocumentKey &)documentKey { - self.addedDocuments = [self.addedDocuments setByAddingObject:documentKey]; - self.removedDocuments = [self.removedDocuments setByRemovingObject:documentKey]; + _addedDocuments = _addedDocuments.insert(documentKey); + _removedDocuments = _removedDocuments.erase(documentKey); } - (void)removeDocumentKey:(const DocumentKey &)documentKey { - self.addedDocuments = [self.addedDocuments setByRemovingObject:documentKey]; - self.removedDocuments = [self.removedDocuments setByAddingObject:documentKey]; + _addedDocuments = _addedDocuments.erase(documentKey); + _removedDocuments = _removedDocuments.insert(documentKey); +} + +- (void)filterUpdatesUsingExistingKeys:(const DocumentKeySet &)existingKeys { + DocumentKeySet result = _addedDocuments; + for (const DocumentKey &key : _addedDocuments) { + if (existingKeys.contains(key)) { + result = result.erase(key); + } + } + _addedDocuments = result; } @end @@ -181,11 +213,12 @@ NS_ASSUME_NONNULL_BEGIN @interface FSTTargetChange () @property(nonatomic, assign) FSTCurrentStatusUpdate currentStatusUpdate; @property(nonatomic, strong, nullable) FSTTargetMapping *mapping; -@property(nonatomic, strong) FSTSnapshotVersion *snapshotVersion; @property(nonatomic, strong) NSData *resumeToken; @end -@implementation FSTTargetChange +@implementation FSTTargetChange { + SnapshotVersion _snapshotVersion; +} - (instancetype)init { if (self = [super init]) { @@ -195,16 +228,32 @@ NS_ASSUME_NONNULL_BEGIN return self; } +- (instancetype)initWithSnapshotVersion:(SnapshotVersion)snapshotVersion { + if (self = [self init]) { + _snapshotVersion = std::move(snapshotVersion); + } + return self; +} + +- (const SnapshotVersion &)snapshotVersion { + return _snapshotVersion; +} + + (instancetype)changeWithDocuments:(NSArray<FSTMaybeDocument *> *)docs currentStatusUpdate:(FSTCurrentStatusUpdate)currentStatusUpdate { - FSTUpdateMapping *mapping = [[FSTUpdateMapping alloc] init]; + DocumentKeySet addedDocuments; + DocumentKeySet removedDocuments; for (FSTMaybeDocument *doc in docs) { if ([doc isKindOfClass:[FSTDeletedDocument class]]) { - mapping.removedDocuments = [mapping.removedDocuments setByAddingObject:doc.key]; + removedDocuments = removedDocuments.insert(doc.key); } else { - mapping.addedDocuments = [mapping.addedDocuments setByAddingObject:doc.key]; + addedDocuments = addedDocuments.insert(doc.key); } } + FSTUpdateMapping *mapping = + [[FSTUpdateMapping alloc] initWithAddedDocuments:std::move(addedDocuments) + removedDocuments:std::move(removedDocuments)]; + FSTTargetChange *change = [[FSTTargetChange alloc] init]; change.mapping = mapping; change.currentStatusUpdate = currentStatusUpdate; @@ -212,11 +261,11 @@ NS_ASSUME_NONNULL_BEGIN } + (instancetype)changeWithMapping:(FSTTargetMapping *)mapping - snapshotVersion:(FSTSnapshotVersion *)snapshotVersion + snapshotVersion:(SnapshotVersion)snapshotVersion currentStatusUpdate:(FSTCurrentStatusUpdate)currentStatusUpdate { FSTTargetChange *change = [[FSTTargetChange alloc] init]; change.mapping = mapping; - change.snapshotVersion = snapshotVersion; + change->_snapshotVersion = std::move(snapshotVersion); change.currentStatusUpdate = currentStatusUpdate; return change; } @@ -243,57 +292,42 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - FSTRemoteEvent -@interface FSTRemoteEvent () { - NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *_targetChanges; -} - -- (instancetype) -initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion - targetChanges:(NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *)targetChanges - documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates; - -@property(nonatomic, strong) FSTSnapshotVersion *snapshotVersion; - -@end - @implementation FSTRemoteEvent { + SnapshotVersion _snapshotVersion; + NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *_targetChanges; std::map<DocumentKey, FSTMaybeDocument *> _documentUpdates; -} -+ (instancetype) -eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion - targetChanges:(NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges - documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates { - return [[FSTRemoteEvent alloc] initWithSnapshotVersion:snapshotVersion - targetChanges:targetChanges - documentUpdates:std::move(documentUpdates)]; + DocumentKeySet _limboDocumentChanges; } -- (instancetype)initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion +- (instancetype)initWithSnapshotVersion:(SnapshotVersion)snapshotVersion targetChanges: (NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges - documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates { + documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates + limboDocuments:(DocumentKeySet)limboDocuments { self = [super init]; if (self) { - _snapshotVersion = snapshotVersion; + _snapshotVersion = std::move(snapshotVersion); _targetChanges = targetChanges; _documentUpdates = std::move(documentUpdates); + _limboDocumentChanges = std::move(limboDocuments); } return self; } -- (void)filterUpdatesFromTargetChange:(FSTTargetChange *)targetChange - existingDocuments:(FSTDocumentKeySet *)existingDocuments { - if ([targetChange.mapping isKindOfClass:[FSTUpdateMapping class]]) { - FSTUpdateMapping *update = (FSTUpdateMapping *)targetChange.mapping; - FSTDocumentKeySet *added = update.addedDocuments; - __block FSTDocumentKeySet *result = added; - [added enumerateObjectsUsingBlock:^(FSTDocumentKey *docKey, BOOL *stop) { - if ([existingDocuments containsObject:docKey]) { - result = [result setByRemovingObject:docKey]; - } - }]; - update.addedDocuments = result; - } +- (NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *)targetChanges { + return _targetChanges; +} + +- (const DocumentKeySet &)limboDocumentChanges { + return _limboDocumentChanges; +} + +- (const std::map<DocumentKey, FSTMaybeDocument *> &)documentUpdates { + return _documentUpdates; +} + +- (const SnapshotVersion &)snapshotVersion { + return _snapshotVersion; } - (void)synthesizeDeleteForLimboTargetChange:(FSTTargetChange *)targetChange @@ -314,22 +348,15 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion // However, if the document doesn't exist and the current marker arrives, the document is // not present in the snapshot and our normal view handling would consider the document to // remain in limbo indefinitely because there are no updates to the document. To avoid this, - // we specially handle this just this case here: synthesizing a delete. + // we specially handle this case here: synthesizing a delete. // // TODO(dimond): Ideally we would have an explicit lookup query instead resulting in an // explicit delete message and we could remove this special logic. _documentUpdates[key] = [FSTDeletedDocument documentWithKey:key version:_snapshotVersion]; + _limboDocumentChanges = _limboDocumentChanges.insert(key); } } -- (NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *)targetChanges { - return static_cast<NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *>(_targetChanges); -} - -- (const std::map<DocumentKey, FSTMaybeDocument *> &)documentUpdates { - return _documentUpdates; -} - /** Adds a document update to this remote event */ - (void)addDocumentUpdate:(FSTMaybeDocument *)document { _documentUpdates[document.key] = document; @@ -350,7 +377,7 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion // TODO(dimond): keep track of reset targets not to raise. FSTTargetChange *targetChange = [FSTTargetChange changeWithMapping:[[FSTResetMapping alloc] init] - snapshotVersion:[FSTSnapshotVersion noVersion] + snapshotVersion:SnapshotVersion::None() currentStatusUpdate:FSTCurrentStatusUpdateMarkNotCurrent]; _targetChanges[targetID] = targetChange; } @@ -361,9 +388,6 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion @interface FSTWatchChangeAggregator () -/** The snapshot version for every target change this creates. */ -@property(nonatomic, strong, readonly) FSTSnapshotVersion *snapshotVersion; - /** Keeps track of the current target mappings */ @property(nonatomic, strong, readonly) NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *targetChanges; @@ -381,35 +405,38 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion NSMutableDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *_existenceFilters; /** Keeps track of document to update */ std::map<DocumentKey, FSTMaybeDocument *> _documentUpdates; + + DocumentKeySet _limboDocuments; + /** The snapshot version for every target change this creates. */ + SnapshotVersion _snapshotVersion; } - (instancetype) -initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion +initWithSnapshotVersion:(SnapshotVersion)snapshotVersion listenTargets:(NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)listenTargets pendingTargetResponses:(NSDictionary<FSTBoxedTargetID *, NSNumber *> *)pendingTargetResponses { self = [super init]; if (self) { - _snapshotVersion = snapshotVersion; + _snapshotVersion = std::move(snapshotVersion); _frozen = NO; _targetChanges = [NSMutableDictionary dictionary]; _listenTargets = listenTargets; _pendingTargetResponses = [NSMutableDictionary dictionaryWithDictionary:pendingTargetResponses]; - + _limboDocuments = DocumentKeySet{}; _existenceFilters = [NSMutableDictionary dictionary]; } return self; } - (NSDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *)existenceFilters { - return static_cast<NSDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *>(_existenceFilters); + return _existenceFilters; } - (FSTTargetChange *)targetChangeForTargetID:(FSTBoxedTargetID *)targetID { FSTTargetChange *change = self.targetChanges[targetID]; if (!change) { - change = [[FSTTargetChange alloc] init]; - change.snapshotVersion = self.snapshotVersion; + change = [[FSTTargetChange alloc] initWithSnapshotVersion:_snapshotVersion]; self.targetChanges[targetID] = change; } return change; @@ -435,20 +462,66 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion } } +/** + * Updates limbo document tracking for a given target-document mapping change. If the target is a + * limbo target, and the change for the document has only seen limbo targets so far, and we are not + * already tracking a change for this document, then consider this document a limbo document update. + * Otherwise, ensure that we don't consider this document a limbo document. Returns true if the + * change still has only seen limbo resolution changes. + */ +- (BOOL)updateLimboDocumentsForKey:(const DocumentKey &)documentKey + queryData:(FSTQueryData *)queryData + isOnlyLimbo:(BOOL)isOnlyLimbo { + if (!isOnlyLimbo) { + // It wasn't a limbo doc before, so it definitely isn't now. + return NO; + } + if (_documentUpdates.find(documentKey) == _documentUpdates.end()) { + // We haven't seen a document update for this key yet. + if (queryData.purpose == FSTQueryPurposeLimboResolution) { + // We haven't seen this document before, and this target is a limbo target. + _limboDocuments = _limboDocuments.insert(documentKey); + return YES; + } else { + // We haven't seen the document before, but this is a non-limbo target. + // Since we haven't seen it, we know it's not in our set of limbo docs. Return NO to ensure + // that this key is marked as non-limbo. + return NO; + } + } else if (queryData.purpose == FSTQueryPurposeLimboResolution) { + // We have only seen limbo targets so far for this document, and this is another limbo target. + return YES; + } else { + // We haven't marked this as non-limbo yet, but this target is not a limbo target. + // Mark the key as non-limbo and make sure it isn't in our set. + _limboDocuments = _limboDocuments.erase(documentKey); + return NO; + } +} + - (void)addDocumentChange:(FSTDocumentWatchChange *)docChange { BOOL relevant = NO; + BOOL isOnlyLimbo = YES; for (FSTBoxedTargetID *targetID in docChange.updatedTargetIDs) { - if ([self isActiveTarget:targetID]) { + FSTQueryData *queryData = [self queryDataForActiveTarget:targetID]; + if (queryData) { FSTTargetChange *change = [self targetChangeForTargetID:targetID]; + isOnlyLimbo = [self updateLimboDocumentsForKey:docChange.documentKey + queryData:queryData + isOnlyLimbo:isOnlyLimbo]; [change.mapping addDocumentKey:docChange.documentKey]; relevant = YES; } } for (FSTBoxedTargetID *targetID in docChange.removedTargetIDs) { - if ([self isActiveTarget:targetID]) { + FSTQueryData *queryData = [self queryDataForActiveTarget:targetID]; + if (queryData) { FSTTargetChange *change = [self targetChangeForTargetID:targetID]; + isOnlyLimbo = [self updateLimboDocumentsForKey:docChange.documentKey + queryData:queryData + isOnlyLimbo:isOnlyLimbo]; [change.mapping removeDocumentKey:docChange.documentKey]; relevant = YES; } @@ -473,7 +546,7 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion break; case FSTWatchTargetChangeStateAdded: [self recordResponseForTargetID:targetID]; - if (![self.pendingTargetResponses objectForKey:targetID]) { + if (!self.pendingTargetResponses[targetID]) { // We have a freshly added target, so we need to reset any state that we had previously // This can happen e.g. when remove and add back a target for existence filter // mismatches. @@ -514,12 +587,12 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion * responses that we have. */ - (void)recordResponseForTargetID:(FSTBoxedTargetID *)targetID { - NSNumber *count = [self.pendingTargetResponses objectForKey:targetID]; + NSNumber *count = self.pendingTargetResponses[targetID]; int newCount = count ? [count intValue] - 1 : -1; if (newCount == 0) { [self.pendingTargetResponses removeObjectForKey:targetID]; } else { - [self.pendingTargetResponses setObject:[NSNumber numberWithInt:newCount] forKey:targetID]; + self.pendingTargetResponses[targetID] = @(newCount); } } @@ -532,8 +605,12 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion * yet acknowledged the intended change in state. */ - (BOOL)isActiveTarget:(FSTBoxedTargetID *)targetID { - return [self.listenTargets objectForKey:targetID] && - ![self.pendingTargetResponses objectForKey:targetID]; + return [self queryDataForActiveTarget:targetID] != nil; +} + +- (FSTQueryData *_Nullable)queryDataForActiveTarget:(FSTBoxedTargetID *)targetID { + FSTQueryData *queryData = self.listenTargets[targetID]; + return (queryData && !self.pendingTargetResponses[targetID]) ? queryData : nil; } - (void)addExistenceFilterChange:(FSTExistenceFilterWatchChange *)existenceFilterChange { @@ -559,9 +636,10 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion // Mark this aggregator as frozen so no further modifications are made. self.frozen = YES; - return [FSTRemoteEvent eventWithSnapshotVersion:self.snapshotVersion - targetChanges:targetChanges - documentUpdates:_documentUpdates]; + return [[FSTRemoteEvent alloc] initWithSnapshotVersion:_snapshotVersion + targetChanges:targetChanges + documentUpdates:_documentUpdates + limboDocuments:_limboDocuments]; } @end diff --git a/Firestore/Source/Remote/FSTRemoteStore.h b/Firestore/Source/Remote/FSTRemoteStore.h index 09e1d32..9b01ce4 100644 --- a/Firestore/Source/Remote/FSTRemoteStore.h +++ b/Firestore/Source/Remote/FSTRemoteStore.h @@ -17,7 +17,6 @@ #import <Foundation/Foundation.h> #import "Firestore/Source/Core/FSTTypes.h" -#import "Firestore/Source/Model/FSTDocumentVersionDictionary.h" #include "Firestore/core/src/firebase/firestore/auth/user.h" diff --git a/Firestore/Source/Remote/FSTRemoteStore.mm b/Firestore/Source/Remote/FSTRemoteStore.mm index 39d285a..0ea4887 100644 --- a/Firestore/Source/Remote/FSTRemoteStore.mm +++ b/Firestore/Source/Remote/FSTRemoteStore.mm @@ -19,7 +19,6 @@ #include <cinttypes> #import "Firestore/Source/Core/FSTQuery.h" -#import "Firestore/Source/Core/FSTSnapshotVersion.h" #import "Firestore/Source/Core/FSTTransaction.h" #import "Firestore/Source/Local/FSTLocalStore.h" #import "Firestore/Source/Local/FSTQueryData.h" @@ -37,11 +36,14 @@ #include "Firestore/core/src/firebase/firestore/auth/user.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; using firebase::firestore::auth::User; using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::SnapshotVersion; +using firebase::firestore::model::DocumentKeySet; NS_ASSUME_NONNULL_BEGIN @@ -299,7 +301,7 @@ static const int kMaxPendingWrites = 10; } - (void)watchStreamDidChange:(FSTWatchChange *)change - snapshotVersion:(FSTSnapshotVersion *)snapshotVersion { + snapshotVersion:(const SnapshotVersion &)snapshotVersion { // Mark the connection as Online because we got a message from the server. [self.onlineStateTracker updateState:FSTOnlineStateOnline]; @@ -315,10 +317,8 @@ static const int kMaxPendingWrites = 10; // older than a previous snapshot we've processed (can happen after we resume a target // using a resume token). [self.accumulatedChanges addObject:change]; - FSTAssert(snapshotVersion, @"snapshotVersion must not be nil."); - if ([snapshotVersion isEqual:[FSTSnapshotVersion noVersion]] || - [snapshotVersion compare:[self.localStore lastRemoteSnapshotVersion]] == - NSOrderedAscending) { + if (snapshotVersion == SnapshotVersion::None() || + snapshotVersion < [self.localStore lastRemoteSnapshotVersion]) { return; } @@ -354,7 +354,7 @@ static const int kMaxPendingWrites = 10; * on to the SyncEngine. */ - (void)processBatchedWatchChanges:(NSArray<FSTWatchChange *> *)changes - snapshotVersion:(FSTSnapshotVersion *)snapshotVersion { + snapshotVersion:(const SnapshotVersion &)snapshotVersion { FSTWatchChangeAggregator *aggregator = [[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:snapshotVersion listenTargets:self.listenTargets @@ -394,7 +394,7 @@ static const int kMaxPendingWrites = 10; } else { // Not a document query. - FSTDocumentKeySet *trackedRemote = [self.localStore remoteDocumentKeysForTarget:targetID]; + DocumentKeySet trackedRemote = [self.localStore remoteDocumentKeysForTarget:targetID]; FSTTargetMapping *mapping = remoteEvent.targetChanges[target].mapping; if (mapping) { if ([mapping isKindOfClass:[FSTUpdateMapping class]]) { @@ -407,7 +407,7 @@ static const int kMaxPendingWrites = 10; } } - if (trackedRemote.count != (NSUInteger)filter.count) { + if (trackedRemote.size() != static_cast<size_t>(filter.count)) { FSTLog(@"Existence filter mismatch, resetting mapping"); // Make sure the mismatch is exposed in the remote event @@ -449,7 +449,8 @@ static const int kMaxPendingWrites = 10; if (queryData) { self->_listenTargets[target] = [queryData queryDataByReplacingSnapshotVersion:change.snapshotVersion - resumeToken:resumeToken]; + resumeToken:resumeToken + sequenceNumber:queryData.sequenceNumber]; } } }]; @@ -566,7 +567,7 @@ static const int kMaxPendingWrites = 10; } /** Handles a successful StreamingWriteResponse from the server that contains a mutation result. */ -- (void)writeStreamDidReceiveResponseWithVersion:(FSTSnapshotVersion *)commitVersion +- (void)writeStreamDidReceiveResponseWithVersion:(const SnapshotVersion &)commitVersion mutationResults:(NSArray<FSTMutationResult *> *)results { // This is a response to a write containing mutations and should be correlated to the first // pending write. diff --git a/Firestore/Source/Remote/FSTSerializerBeta.h b/Firestore/Source/Remote/FSTSerializerBeta.h index d96dbeb..cdf5d1f 100644 --- a/Firestore/Source/Remote/FSTSerializerBeta.h +++ b/Firestore/Source/Remote/FSTSerializerBeta.h @@ -16,8 +16,10 @@ #import <Foundation/Foundation.h> +#include "Firestore/core/include/firebase/firestore/timestamp.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" @class FSTFieldValue; @class FSTMaybeDocument; @@ -27,8 +29,6 @@ @class FSTObjectValue; @class FSTQuery; @class FSTQueryData; -@class FSTSnapshotVersion; -@class FIRTimestamp; @class FSTWatchChange; @class GCFSBatchGetDocumentsResponse; @@ -61,15 +61,19 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithDatabaseID:(const firebase::firestore::model::DatabaseId *)databaseID NS_DESIGNATED_INITIALIZER; -- (GPBTimestamp *)encodedTimestamp:(FIRTimestamp *)timestamp; -- (FIRTimestamp *)decodedTimestamp:(GPBTimestamp *)timestamp; +- (GPBTimestamp *)encodedTimestamp:(const firebase::Timestamp &)timestamp; +- (firebase::Timestamp)decodedTimestamp:(GPBTimestamp *)timestamp; -- (GPBTimestamp *)encodedVersion:(FSTSnapshotVersion *)version; -- (FSTSnapshotVersion *)decodedVersion:(GPBTimestamp *)version; +- (GPBTimestamp *)encodedVersion:(const firebase::firestore::model::SnapshotVersion &)version; +- (firebase::firestore::model::SnapshotVersion)decodedVersion:(GPBTimestamp *)version; /** Returns the database ID, such as `projects/{project id}/databases/{database_id}`. */ - (NSString *)encodedDatabaseID; +/** + * Encodes the given document key as a fully qualified name. This includes the + * databaseId associated with this FSTSerializerBeta and the key path. + */ - (NSString *)encodedDocumentKey:(const firebase::firestore::model::DocumentKey &)key; - (firebase::firestore::model::DocumentKey)decodedDocumentKey:(NSString *)key; @@ -93,7 +97,8 @@ NS_ASSUME_NONNULL_BEGIN - (FSTQuery *)decodedQueryFromQueryTarget:(GCFSTarget_QueryTarget *)target; - (FSTWatchChange *)decodedWatchChange:(GCFSListenResponse *)watchChange; -- (FSTSnapshotVersion *)versionFromListenResponse:(GCFSListenResponse *)watchChange; +- (firebase::firestore::model::SnapshotVersion)versionFromListenResponse: + (GCFSListenResponse *)watchChange; - (GCFSDocument *)encodedDocumentWithFields:(FSTObjectValue *)objectValue key:(const firebase::firestore::model::DocumentKey &)key; diff --git a/Firestore/Source/Remote/FSTSerializerBeta.mm b/Firestore/Source/Remote/FSTSerializerBeta.mm index 5cbfecc..f862ec3 100644 --- a/Firestore/Source/Remote/FSTSerializerBeta.mm +++ b/Firestore/Source/Remote/FSTSerializerBeta.mm @@ -33,9 +33,7 @@ #import "FIRFirestoreErrors.h" #import "FIRGeoPoint.h" -#import "FIRTimestamp.h" #import "Firestore/Source/Core/FSTQuery.h" -#import "Firestore/Source/Core/FSTSnapshotVersion.h" #import "Firestore/Source/Local/FSTQueryData.h" #import "Firestore/Source/Model/FSTDocument.h" #import "Firestore/Source/Model/FSTFieldValue.h" @@ -55,8 +53,10 @@ #include "Firestore/core/src/firebase/firestore/model/transform_operations.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" #include "absl/memory/memory.h" +#include "absl/types/optional.h" namespace util = firebase::firestore::util; +using firebase::Timestamp; using firebase::firestore::model::ArrayTransform; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::DocumentKey; @@ -86,25 +86,25 @@ NS_ASSUME_NONNULL_BEGIN return self; } -#pragma mark - FSTSnapshotVersion <=> GPBTimestamp +#pragma mark - SnapshotVersion <=> GPBTimestamp -- (GPBTimestamp *)encodedTimestamp:(FIRTimestamp *)timestamp { +- (GPBTimestamp *)encodedTimestamp:(const Timestamp &)timestamp { GPBTimestamp *result = [GPBTimestamp message]; - result.seconds = timestamp.seconds; - result.nanos = timestamp.nanoseconds; + result.seconds = timestamp.seconds(); + result.nanos = timestamp.nanoseconds(); return result; } -- (FIRTimestamp *)decodedTimestamp:(GPBTimestamp *)timestamp { - return [[FIRTimestamp alloc] initWithSeconds:timestamp.seconds nanoseconds:timestamp.nanos]; +- (Timestamp)decodedTimestamp:(GPBTimestamp *)timestamp { + return Timestamp{timestamp.seconds, timestamp.nanos}; } -- (GPBTimestamp *)encodedVersion:(FSTSnapshotVersion *)version { - return [self encodedTimestamp:version.timestamp]; +- (GPBTimestamp *)encodedVersion:(const SnapshotVersion &)version { + return [self encodedTimestamp:version.timestamp()]; } -- (FSTSnapshotVersion *)decodedVersion:(GPBTimestamp *)version { - return [FSTSnapshotVersion versionWithTimestamp:[self decodedTimestamp:version]]; +- (SnapshotVersion)decodedVersion:(GPBTimestamp *)version { + return SnapshotVersion{[self decodedTimestamp:version]}; } #pragma mark - FIRGeoPoint <=> GTPLatLng @@ -206,8 +206,8 @@ NS_ASSUME_NONNULL_BEGIN return [self encodedString:[fieldValue value]]; } else if (fieldClass == [FSTTimestampValue class]) { - return [self encodedTimestampValue:[fieldValue value]]; - + FIRTimestamp *value = static_cast<FIRTimestamp *>([fieldValue value]); + return [self encodedTimestampValue:Timestamp{value.seconds, value.nanoseconds}]; } else if (fieldClass == [FSTGeoPointValue class]) { return [self encodedGeoPointValue:[fieldValue value]]; @@ -250,8 +250,12 @@ NS_ASSUME_NONNULL_BEGIN case GCFSValue_ValueType_OneOfCase_StringValue: return [FSTStringValue stringValue:valueProto.stringValue]; - case GCFSValue_ValueType_OneOfCase_TimestampValue: - return [FSTTimestampValue timestampValue:[self decodedTimestamp:valueProto.timestampValue]]; + case GCFSValue_ValueType_OneOfCase_TimestampValue: { + Timestamp value = [self decodedTimestamp:valueProto.timestampValue]; + return [FSTTimestampValue + timestampValue:[FIRTimestamp timestampWithSeconds:value.seconds() + nanoseconds:value.nanoseconds()]]; + } case GCFSValue_ValueType_OneOfCase_GeoPointValue: return [FSTGeoPointValue geoPointValue:[self decodedGeoPoint:valueProto.geoPointValue]]; @@ -303,7 +307,7 @@ NS_ASSUME_NONNULL_BEGIN return result; } -- (GCFSValue *)encodedTimestampValue:(FIRTimestamp *)value { +- (GCFSValue *)encodedTimestampValue:(const Timestamp &)value { GCFSValue *result = [GCFSValue message]; result.timestampValue = [self encodedTimestamp:value]; return result; @@ -429,8 +433,8 @@ NS_ASSUME_NONNULL_BEGIN FSTAssert(!!response.found, @"Tried to deserialize a found document from a deleted document."); const DocumentKey key = [self decodedDocumentKey:response.found.name]; FSTObjectValue *value = [self decodedFields:response.found.fields]; - FSTSnapshotVersion *version = [self decodedVersion:response.found.updateTime]; - FSTAssert(![version isEqual:[FSTSnapshotVersion noVersion]], + SnapshotVersion version = [self decodedVersion:response.found.updateTime]; + FSTAssert(version != SnapshotVersion::None(), @"Got a document response with no snapshot version"); return [FSTDocument documentWithData:value key:key version:version hasLocalMutations:NO]; @@ -439,8 +443,8 @@ NS_ASSUME_NONNULL_BEGIN - (FSTDeletedDocument *)decodedDeletedDocument:(GCFSBatchGetDocumentsResponse *)response { FSTAssert(!!response.missing, @"Tried to deserialize a deleted document from a found document."); const DocumentKey key = [self decodedDocumentKey:response.missing]; - FSTSnapshotVersion *version = [self decodedVersion:response.readTime]; - FSTAssert(![version isEqual:[FSTSnapshotVersion noVersion]], + SnapshotVersion version = [self decodedVersion:response.readTime]; + FSTAssert(version != SnapshotVersion::None(), @"Got a no document response with no snapshot version"); return [FSTDeletedDocument documentWithKey:key version:version]; } @@ -668,8 +672,10 @@ NS_ASSUME_NONNULL_BEGIN - (FSTMutationResult *)decodedMutationResult:(GCFSWriteResult *)mutation { // NOTE: Deletes don't have an updateTime. - FSTSnapshotVersion *_Nullable version = - mutation.updateTime ? [self decodedVersion:mutation.updateTime] : nil; + absl::optional<SnapshotVersion> version; + if (mutation.updateTime) { + version = [self decodedVersion:mutation.updateTime]; + } NSMutableArray *_Nullable transformResults = nil; if (mutation.transformResultsArray.count > 0) { transformResults = [NSMutableArray array]; @@ -677,7 +683,8 @@ NS_ASSUME_NONNULL_BEGIN [transformResults addObject:[self decodedFieldValue:result]]; } } - return [[FSTMutationResult alloc] initWithVersion:version transformResults:transformResults]; + return [[FSTMutationResult alloc] initWithVersion:std::move(version) + transformResults:transformResults]; } #pragma mark - FSTQueryData => GCFSTarget proto @@ -1071,15 +1078,15 @@ NS_ASSUME_NONNULL_BEGIN } } -- (FSTSnapshotVersion *)versionFromListenResponse:(GCFSListenResponse *)watchChange { +- (SnapshotVersion)versionFromListenResponse:(GCFSListenResponse *)watchChange { // We have only reached a consistent snapshot for the entire stream if there is a read_time set // and it applies to all targets (i.e. the list of targets is empty). The backend is guaranteed to // send such responses. if (watchChange.responseTypeOneOfCase != GCFSListenResponse_ResponseType_OneOfCase_TargetChange) { - return [FSTSnapshotVersion noVersion]; + return SnapshotVersion::None(); } if (watchChange.targetChange.targetIdsArray.count != 0) { - return [FSTSnapshotVersion noVersion]; + return SnapshotVersion::None(); } return [self decodedVersion:watchChange.targetChange.readTime]; } @@ -1135,9 +1142,8 @@ NS_ASSUME_NONNULL_BEGIN - (FSTDocumentWatchChange *)decodedDocumentChange:(GCFSDocumentChange *)change { FSTObjectValue *value = [self decodedFields:change.document.fields]; const DocumentKey key = [self decodedDocumentKey:change.document.name]; - FSTSnapshotVersion *version = [self decodedVersion:change.document.updateTime]; - FSTAssert(![version isEqual:[FSTSnapshotVersion noVersion]], - @"Got a document change with no snapshot version"); + SnapshotVersion version = [self decodedVersion:change.document.updateTime]; + FSTAssert(version != SnapshotVersion::None(), @"Got a document change with no snapshot version"); FSTMaybeDocument *document = [FSTDocument documentWithData:value key:key version:version hasLocalMutations:NO]; @@ -1152,8 +1158,8 @@ NS_ASSUME_NONNULL_BEGIN - (FSTDocumentWatchChange *)decodedDocumentDelete:(GCFSDocumentDelete *)change { const DocumentKey key = [self decodedDocumentKey:change.document]; - // Note that version might be unset in which case we use [FSTSnapshotVersion noVersion] - FSTSnapshotVersion *version = [self decodedVersion:change.readTime]; + // Note that version might be unset in which case we use SnapshotVersion::None() + SnapshotVersion version = [self decodedVersion:change.readTime]; FSTMaybeDocument *document = [FSTDeletedDocument documentWithKey:key version:version]; NSArray<NSNumber *> *removedTargetIds = [self decodedIntegerArray:change.removedTargetIdsArray]; diff --git a/Firestore/Source/Remote/FSTStream.h b/Firestore/Source/Remote/FSTStream.h index fba79d2..3bd8549 100644 --- a/Firestore/Source/Remote/FSTStream.h +++ b/Firestore/Source/Remote/FSTStream.h @@ -21,13 +21,13 @@ #include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h" #include "Firestore/core/src/firebase/firestore/core/database_info.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" @class FSTDispatchQueue; @class FSTMutation; @class FSTMutationResult; @class FSTQueryData; @class FSTSerializerBeta; -@class FSTSnapshotVersion; @class FSTWatchChange; @class FSTWatchStream; @class FSTWriteStream; @@ -179,7 +179,7 @@ NS_ASSUME_NONNULL_BEGIN * WatchChange responses sent back by the server. */ - (void)watchStreamDidChange:(FSTWatchChange *)change - snapshotVersion:(FSTSnapshotVersion *)snapshotVersion; + snapshotVersion:(const firebase::firestore::model::SnapshotVersion &)snapshotVersion; /** * Called by the FSTWatchStream when the underlying streaming RPC is interrupted for whatever @@ -250,7 +250,8 @@ NS_ASSUME_NONNULL_BEGIN * Called by the FSTWriteStream upon receiving a StreamingWriteResponse from the server that * contains mutation results. */ -- (void)writeStreamDidReceiveResponseWithVersion:(FSTSnapshotVersion *)commitVersion +- (void)writeStreamDidReceiveResponseWithVersion: + (const firebase::firestore::model::SnapshotVersion &)commitVersion mutationResults:(NSArray<FSTMutationResult *> *)results; /** diff --git a/Firestore/Source/Remote/FSTStream.mm b/Firestore/Source/Remote/FSTStream.mm index a96feae..f4ec675 100644 --- a/Firestore/Source/Remote/FSTStream.mm +++ b/Firestore/Source/Remote/FSTStream.mm @@ -36,6 +36,7 @@ #include "Firestore/core/src/firebase/firestore/auth/token.h" #include "Firestore/core/src/firebase/firestore/core/database_info.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" #include "Firestore/core/src/firebase/firestore/util/error_apple.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" @@ -44,6 +45,7 @@ using firebase::firestore::auth::CredentialsProvider; using firebase::firestore::auth::Token; using firebase::firestore::core::DatabaseInfo; using firebase::firestore::model::DatabaseId; +using firebase::firestore::model::SnapshotVersion; /** * Initial backoff time in seconds after an error. @@ -691,7 +693,7 @@ static const NSTimeInterval kIdleTimeout = 60.0; [self.backoff reset]; FSTWatchChange *change = [_serializer decodedWatchChange:proto]; - FSTSnapshotVersion *snap = [_serializer versionFromListenResponse:proto]; + SnapshotVersion snap = [_serializer versionFromListenResponse:proto]; [self.delegate watchStreamDidChange:change snapshotVersion:snap]; } @@ -807,7 +809,7 @@ static const NSTimeInterval kIdleTimeout = 60.0; // might be causing an error we want to back off from. [self.backoff reset]; - FSTSnapshotVersion *commitVersion = [_serializer decodedVersion:response.commitTime]; + SnapshotVersion commitVersion = [_serializer decodedVersion:response.commitTime]; NSMutableArray<GCFSWriteResult *> *protos = response.writeResultsArray; NSMutableArray<FSTMutationResult *> *results = [NSMutableArray arrayWithCapacity:protos.count]; for (GCFSWriteResult *proto in protos) { diff --git a/Firestore/Source/Remote/FSTWatchChange.h b/Firestore/Source/Remote/FSTWatchChange.h index 8f730de..ed80e1a 100644 --- a/Firestore/Source/Remote/FSTWatchChange.h +++ b/Firestore/Source/Remote/FSTWatchChange.h @@ -22,7 +22,6 @@ @class FSTExistenceFilter; @class FSTMaybeDocument; -@class FSTSnapshotVersion; NS_ASSUME_NONNULL_BEGIN diff --git a/Firestore/Source/Util/FSTAsyncQueryListener.h b/Firestore/Source/Util/FSTAsyncQueryListener.h index 4888268..06471d8 100644 --- a/Firestore/Source/Util/FSTAsyncQueryListener.h +++ b/Firestore/Source/Util/FSTAsyncQueryListener.h @@ -18,6 +18,8 @@ #import "Firestore/Source/Core/FSTViewSnapshot.h" +#include "Firestore/core/src/firebase/firestore/util/executor.h" + NS_ASSUME_NONNULL_BEGIN @class FSTDispatchQueue; @@ -28,9 +30,8 @@ NS_ASSUME_NONNULL_BEGIN */ @interface FSTAsyncQueryListener : NSObject -- (instancetype)initWithDispatchQueue:(FSTDispatchQueue *)dispatchQueue - snapshotHandler:(FSTViewSnapshotHandler)snapshotHandler - NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithExecutor:(firebase::firestore::util::internal::Executor*)executor + snapshotHandler:(FSTViewSnapshotHandler)snapshotHandler NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; diff --git a/Firestore/Source/Util/FSTAsyncQueryListener.mm b/Firestore/Source/Util/FSTAsyncQueryListener.mm index b72ac57..81dd41f 100644 --- a/Firestore/Source/Util/FSTAsyncQueryListener.mm +++ b/Firestore/Source/Util/FSTAsyncQueryListener.mm @@ -18,16 +18,18 @@ #import "Firestore/Source/Util/FSTDispatchQueue.h" +using firebase::firestore::util::internal::Executor; + @implementation FSTAsyncQueryListener { volatile BOOL _muted; FSTViewSnapshotHandler _snapshotHandler; - FSTDispatchQueue *_dispatchQueue; + Executor *_executor; } -- (instancetype)initWithDispatchQueue:(FSTDispatchQueue *)dispatchQueue - snapshotHandler:(FSTViewSnapshotHandler)snapshotHandler { +- (instancetype)initWithExecutor:(Executor *)executor + snapshotHandler:(FSTViewSnapshotHandler)snapshotHandler { if (self = [super init]) { - _dispatchQueue = dispatchQueue; + _executor = executor; _snapshotHandler = snapshotHandler; } return self; @@ -40,11 +42,11 @@ // users just want to turn on notifications "forever" and don't want to have // to keep track of our handle to keep them going. return ^(FSTViewSnapshot *_Nullable snapshot, NSError *_Nullable error) { - [self->_dispatchQueue dispatchAsync:^{ + self->_executor->Execute([self, snapshot, error] { if (!self->_muted) { self->_snapshotHandler(snapshot, error); } - }]; + }); }; } diff --git a/Firestore/Source/Util/FSTDispatchQueue.mm b/Firestore/Source/Util/FSTDispatchQueue.mm index 0974359..01b2732 100644 --- a/Firestore/Source/Util/FSTDispatchQueue.mm +++ b/Firestore/Source/Util/FSTDispatchQueue.mm @@ -16,154 +16,66 @@ #import <Foundation/Foundation.h> -#include <atomic> +#include <memory> +#include <utility> #import "Firestore/Source/Util/FSTAssert.h" #import "Firestore/Source/Util/FSTDispatchQueue.h" -NS_ASSUME_NONNULL_BEGIN - -/** - * removeDelayedCallback is used by FSTDelayedCallback and so we pre-declare it before the rest of - * the FSTDispatchQueue private interface. - */ -@interface FSTDispatchQueue () -- (void)removeDelayedCallback:(FSTDelayedCallback *)callback; -@end +#include "Firestore/core/src/firebase/firestore/util/async_queue.h" +#include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h" +#include "absl/memory/memory.h" -#pragma mark - FSTDelayedCallback - -/** - * Represents a callback scheduled to be run in the future on an FSTDispatchQueue. - * - * It is created via [FSTDelayedCallback createAndScheduleWithQueue]. - * - * Supports cancellation (via cancel) and early execution (via skipDelay). - */ -@interface FSTDelayedCallback () +using firebase::firestore::util::AsyncQueue; +using firebase::firestore::util::DelayedOperation; +using firebase::firestore::util::TimerId; +using firebase::firestore::util::internal::Executor; +using firebase::firestore::util::internal::ExecutorLibdispatch; -@property(nonatomic, strong, readonly) FSTDispatchQueue *queue; -@property(nonatomic, assign, readonly) FSTTimerID timerID; -@property(nonatomic, assign, readonly) NSTimeInterval targetTime; -@property(nonatomic, copy) void (^callback)(); -/** YES if the callback has been run or canceled. */ -@property(nonatomic, getter=isDone) BOOL done; +NS_ASSUME_NONNULL_BEGIN -/** - * Creates and returns an FSTDelayedCallback that has been scheduled on the provided queue with the - * provided delay. - * - * @param queue The FSTDispatchQueue to run the callback on. - * @param timerID A FSTTimerID identifying the type of the delayed callback. - * @param delay The delay before the callback should be scheduled. - * @param callback The callback block to run. - * @return The created FSTDelayedCallback instance. - */ -+ (instancetype)createAndScheduleWithQueue:(FSTDispatchQueue *)queue - timerID:(FSTTimerID)timerID - delay:(NSTimeInterval)delay - callback:(void (^)(void))callback; +#pragma mark - FSTDelayedCallback -/** - * Queues the callback to run immediately (if it hasn't already been run or canceled). - */ -- (void)skipDelay; +@interface FSTDelayedCallback () { + DelayedOperation _impl; +} @end @implementation FSTDelayedCallback -- (instancetype)initWithQueue:(FSTDispatchQueue *)queue - timerID:(FSTTimerID)timerID - targetTime:(NSTimeInterval)targetTime - callback:(void (^)(void))callback { +- (instancetype)initWithImpl:(DelayedOperation &&)impl { if (self = [super init]) { - _queue = queue; - _timerID = timerID; - _targetTime = targetTime; - _callback = callback; - _done = NO; + _impl = std::move(impl); } return self; } -+ (instancetype)createAndScheduleWithQueue:(FSTDispatchQueue *)queue - timerID:(FSTTimerID)timerID - delay:(NSTimeInterval)delay - callback:(void (^)(void))callback { - NSTimeInterval targetTime = [[NSDate date] timeIntervalSince1970] + delay; - FSTDelayedCallback *delayedCallback = [[FSTDelayedCallback alloc] initWithQueue:queue - timerID:timerID - targetTime:targetTime - callback:callback]; - [delayedCallback startWithDelay:delay]; - return delayedCallback; -} - -/** - * Starts the timer. This is called immediately after construction by createAndScheduleWithQueue. - */ -- (void)startWithDelay:(NSTimeInterval)delay { - dispatch_time_t delayNs = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)); - dispatch_after(delayNs, self.queue.queue, ^{ - [self.queue enterCheckedOperation:^{ - [self delayDidElapse]; - }]; - }); -} - -- (void)skipDelay { - [self.queue dispatchAsyncAllowingSameQueue:^{ - [self delayDidElapse]; - }]; -} - - (void)cancel { - [self.queue verifyIsCurrentQueue]; - if (!self.isDone) { - // PORTING NOTE: There's no way to actually cancel the dispatched callback, but it'll be a no-op - // since we set done to YES. - [self markDone]; - } -} - -- (void)delayDidElapse { - [self.queue verifyIsCurrentQueue]; - if (!self.isDone) { - [self markDone]; - self.callback(); - } -} - -/** - * Marks this delayed callback as done, and notifies the FSTDispatchQueue that it should be removed. - */ -- (void)markDone { - self.done = YES; - [self.queue removeDelayedCallback:self]; + _impl.Cancel(); } @end #pragma mark - FSTDispatchQueue -@interface FSTDispatchQueue () -/** - * Callbacks scheduled to be queued in the future. Callbacks are automatically removed after they - * are run or canceled. - */ -@property(nonatomic, strong, readonly) NSMutableArray<FSTDelayedCallback *> *delayedCallbacks; - -- (instancetype)initWithQueue:(dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER; - -@end - @implementation FSTDispatchQueue { - /** - * Flag set while an FSTDispatchQueue operation is currently executing. Used for assertion - * sanity-checks. - */ - std::atomic<bool> _operationInProgress; + std::unique_ptr<AsyncQueue> _impl; +} + ++ (TimerId)convertTimerId:(FSTTimerID)objcTimerID { + const TimerId converted = static_cast<TimerId>(objcTimerID); + switch (converted) { + case TimerId::All: + case TimerId::ListenStreamIdle: + case TimerId::ListenStreamConnectionBackoff: + case TimerId::WriteStreamIdle: + case TimerId::WriteStreamConnectionBackoff: + case TimerId::OnlineStateTimeout: + return converted; + default: + FSTAssert(false, @"Unknown value of enum FSTTimerID."); + } } + (instancetype)queueWith:(dispatch_queue_t)dispatchQueue { @@ -172,141 +84,50 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithQueue:(dispatch_queue_t)queue { if (self = [super init]) { - _operationInProgress = false; _queue = queue; - _delayedCallbacks = [NSMutableArray array]; + auto executor = absl::make_unique<ExecutorLibdispatch>(queue); + _impl = absl::make_unique<AsyncQueue>(std::move(executor)); } return self; } - (void)verifyIsCurrentQueue { - FSTAssert([self onTargetQueue], - @"We are running on the wrong dispatch queue. Expected '%@' Actual: '%@'", - [self targetQueueLabel], [self currentQueueLabel]); - FSTAssert(_operationInProgress, - @"verifyIsCurrentQueue called outside enterCheckedOperation on queue '%@'", - [self currentQueueLabel]); + _impl->VerifyIsCurrentQueue(); } - (void)enterCheckedOperation:(void (^)(void))block { - FSTAssert(!_operationInProgress, - @"enterCheckedOperation may not be called when an operation is in progress"); - @try { - _operationInProgress = true; - [self verifyIsCurrentQueue]; - block(); - } @finally { - _operationInProgress = false; - } + _impl->ExecuteBlocking([block] { block(); }); } - (void)dispatchAsync:(void (^)(void))block { - FSTAssert(![self onTargetQueue] || !_operationInProgress, - @"dispatchAsync called when we are already running on target dispatch queue '%@'", - [self targetQueueLabel]); - - dispatch_async(self.queue, ^{ - [self enterCheckedOperation:block]; - }); + _impl->Enqueue([block] { block(); }); } - (void)dispatchAsyncAllowingSameQueue:(void (^)(void))block { - dispatch_async(self.queue, ^{ - [self enterCheckedOperation:block]; - }); + _impl->EnqueueRelaxed([block] { block(); }); } - (void)dispatchSync:(void (^)(void))block { - FSTAssert(![self onTargetQueue] || !_operationInProgress, - @"dispatchSync called when we are already running on target dispatch queue '%@'", - [self targetQueueLabel]); - - dispatch_sync(self.queue, ^{ - [self enterCheckedOperation:block]; - }); + _impl->EnqueueBlocking([block] { block(); }); } - (FSTDelayedCallback *)dispatchAfterDelay:(NSTimeInterval)delay timerID:(FSTTimerID)timerID block:(void (^)(void))block { - // While not necessarily harmful, we currently don't expect to have multiple callbacks with the - // same timerID in the queue, so defensively reject them. - FSTAssert(![self containsDelayedCallbackWithTimerID:timerID], - @"Attempted to schedule multiple callbacks with id %ld", (unsigned long)timerID); - FSTDelayedCallback *delayedCallback = [FSTDelayedCallback createAndScheduleWithQueue:self - timerID:timerID - delay:delay - callback:block]; - [self.delayedCallbacks addObject:delayedCallback]; - return delayedCallback; + const AsyncQueue::Milliseconds delayMs = + std::chrono::milliseconds(static_cast<long long>(delay * 1000)); + const TimerId convertedTimerId = [FSTDispatchQueue convertTimerId:timerID]; + DelayedOperation delayed_operation = + _impl->EnqueueAfterDelay(delayMs, convertedTimerId, [block] { block(); }); + return [[FSTDelayedCallback alloc] initWithImpl:std::move(delayed_operation)]; } - (BOOL)containsDelayedCallbackWithTimerID:(FSTTimerID)timerID { - NSUInteger matchIndex = [self.delayedCallbacks - indexOfObjectPassingTest:^BOOL(FSTDelayedCallback *obj, NSUInteger idx, BOOL *stop) { - return obj.timerID == timerID; - }]; - return matchIndex != NSNotFound; + return _impl->IsScheduled([FSTDispatchQueue convertTimerId:timerID]); } - (void)runDelayedCallbacksUntil:(FSTTimerID)lastTimerID { - dispatch_semaphore_t doneSemaphore = dispatch_semaphore_create(0); - - [self dispatchAsync:^{ - FSTAssert(lastTimerID == FSTTimerIDAll || [self containsDelayedCallbackWithTimerID:lastTimerID], - @"Attempted to run callbacks until missing timer ID: %ld", - (unsigned long)lastTimerID); - - [self sortDelayedCallbacks]; - for (FSTDelayedCallback *callback in self.delayedCallbacks) { - [callback skipDelay]; - if (lastTimerID != FSTTimerIDAll && callback.timerID == lastTimerID) { - break; - } - } - - // Now that the callbacks are queued, we want to enqueue an additional item to release the - // 'done' semaphore. - [self dispatchAsyncAllowingSameQueue:^{ - dispatch_semaphore_signal(doneSemaphore); - }]; - }]; - - dispatch_semaphore_wait(doneSemaphore, DISPATCH_TIME_FOREVER); -} - -// NOTE: For performance we could store the callbacks sorted (e.g. using std::priority_queue), -// but this sort only happens in tests (if runDelayedCallbacksUntil: is called), and the size -// is guaranteed to be small since we don't allow duplicate TimerIds (of which there are only 4). -- (void)sortDelayedCallbacks { - // We want to run callbacks in the same order they'd run if they ran naturally. - [self.delayedCallbacks - sortUsingComparator:^NSComparisonResult(FSTDelayedCallback *a, FSTDelayedCallback *b) { - return a.targetTime < b.targetTime - ? NSOrderedAscending - : a.targetTime > b.targetTime ? NSOrderedDescending : NSOrderedSame; - }]; -} - -/** Called by FSTDelayedCallback when a callback is run or canceled. */ -- (void)removeDelayedCallback:(FSTDelayedCallback *)callback { - NSUInteger index = [self.delayedCallbacks indexOfObject:callback]; - FSTAssert(index != NSNotFound, @"Delayed callback not found."); - [self.delayedCallbacks removeObjectAtIndex:index]; -} - -#pragma mark - Private Methods - -- (NSString *)currentQueueLabel { - return [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)]; -} - -- (NSString *)targetQueueLabel { - return [NSString stringWithUTF8String:dispatch_queue_get_label(self.queue)]; -} - -- (BOOL)onTargetQueue { - return [[self currentQueueLabel] isEqualToString:[self targetQueueLabel]]; + _impl->RunScheduledOperationsUntil([FSTDispatchQueue convertTimerId:lastTimerID]); } @end |