From 2ceea9b66eb8ea516702e1ef409e801fec068682 Mon Sep 17 00:00:00 2001 From: zxu Date: Thu, 12 Apr 2018 21:32:19 -0400 Subject: Port `Precondition` to C++ (#1040) * port FieldMask to C++ * address changes * address changes * fix test * address change * Port transform operations (FSTTransformOperation, FSTServerTimestampTransform) to C++ * address changes * address changes * address changes * implement `FieldTransform` in C++ * port `FieldTransform` * make `fieldTransforms` shared inside `context` * Implement Precondition in C++ w/o test yet * add unit test for `Precondition` * port `Precondition` * address changes * address changes * fix bugs for integration test * address changes * fix lint * address changes * address changes --- .../Example/Tests/Integration/FSTDatastoreTests.mm | 4 +- .../Example/Tests/Local/FSTLocalSerializerTests.mm | 15 +- Firestore/Example/Tests/Model/FSTMutationTests.mm | 4 +- .../Example/Tests/Remote/FSTSerializerBetaTests.mm | 14 +- Firestore/Example/Tests/Util/FSTHelpers.mm | 10 +- Firestore/Source/API/FIRDocumentReference.mm | 9 +- Firestore/Source/API/FIRWriteBatch.mm | 15 +- Firestore/Source/API/FSTUserDataConverter.h | 8 +- Firestore/Source/API/FSTUserDataConverter.mm | 6 +- Firestore/Source/Core/FSTTransaction.mm | 32 +++-- Firestore/Source/Model/FSTMutation.h | 64 ++------- Firestore/Source/Model/FSTMutation.mm | 154 ++++---------------- Firestore/Source/Remote/FSTSerializerBeta.mm | 37 ++--- .../src/firebase/firestore/model/CMakeLists.txt | 2 + .../src/firebase/firestore/model/precondition.cc | 67 +++++++++ .../src/firebase/firestore/model/precondition.h | 160 +++++++++++++++++++++ .../firebase/firestore/model/snapshot_version.h | 23 +++ .../test/firebase/firestore/model/CMakeLists.txt | 1 + .../firebase/firestore/model/precondition_test.cc | 77 ++++++++++ .../test/firebase/firestore/testutil/testutil.h | 29 ++++ 20 files changed, 489 insertions(+), 242 deletions(-) create mode 100644 Firestore/core/src/firebase/firestore/model/precondition.cc create mode 100644 Firestore/core/src/firebase/firestore/model/precondition.h create mode 100644 Firestore/core/test/firebase/firestore/model/precondition_test.cc (limited to 'Firestore') diff --git a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm index 430366f..ad911ce 100644 --- a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm +++ b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm @@ -42,12 +42,14 @@ #include "Firestore/core/src/firebase/firestore/auth/empty_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/model/precondition.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" namespace util = firebase::firestore::util; using firebase::firestore::auth::EmptyCredentialsProvider; using firebase::firestore::core::DatabaseInfo; using firebase::firestore::model::DatabaseId; +using firebase::firestore::model::Precondition; using firebase::firestore::model::TargetId; NS_ASSUME_NONNULL_BEGIN @@ -238,7 +240,7 @@ NS_ASSUME_NONNULL_BEGIN initWithKey:[FSTDocumentKey keyWithPathString:@"rooms/eros"] value:[[FSTObjectValue alloc] initWithDictionary:@{@"name" : [FSTStringValue stringValue:@"Eros"]}] - precondition:[FSTPrecondition none]]; + precondition:Precondition::None()]; } @end diff --git a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm index d810aa6..362f46f 100644 --- a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm +++ b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm @@ -42,12 +42,14 @@ #include "Firestore/core/src/firebase/firestore/model/database_id.h" #include "Firestore/core/src/firebase/firestore/model/field_mask.h" +#include "Firestore/core/src/firebase/firestore/model/precondition.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" #include "Firestore/core/test/firebase/firestore/testutil/testutil.h" namespace testutil = firebase::firestore::testutil; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::FieldMask; +using firebase::firestore::model::Precondition; NS_ASSUME_NONNULL_BEGIN @@ -78,13 +80,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)testEncodesMutationBatch { FSTMutation *set = FSTTestSetMutation(@"foo/bar", @{ @"a" : @"b", @"num" : @1 }); - FSTMutation *patch = - [[FSTPatchMutation alloc] initWithKey:FSTTestDocKey(@"bar/baz") - fieldMask:FieldMask{testutil::Field("a")} - value:FSTTestObjectValue( - @{ @"a" : @"b", - @"num" : @1 }) - precondition:[FSTPrecondition preconditionWithExists:YES]]; + FSTMutation *patch = [[FSTPatchMutation alloc] initWithKey:FSTTestDocKey(@"bar/baz") + fieldMask:FieldMask{testutil::Field("a")} + value:FSTTestObjectValue( + @{ @"a" : @"b", + @"num" : @1 }) + precondition:Precondition::Exists(true)]; FSTMutation *del = FSTTestDeleteMutation(@"baz/quux"); FIRTimestamp *writeTime = [FIRTimestamp timestamp]; FSTMutationBatch *model = [[FSTMutationBatch alloc] initWithBatchID:42 diff --git a/Firestore/Example/Tests/Model/FSTMutationTests.mm b/Firestore/Example/Tests/Model/FSTMutationTests.mm index 9024b22..60cb7b8 100644 --- a/Firestore/Example/Tests/Model/FSTMutationTests.mm +++ b/Firestore/Example/Tests/Model/FSTMutationTests.mm @@ -26,11 +26,13 @@ #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/field_mask.h" +#include "Firestore/core/src/firebase/firestore/model/precondition.h" #include "Firestore/core/test/firebase/firestore/testutil/testutil.h" namespace testutil = firebase::firestore::testutil; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldMask; +using firebase::firestore::model::Precondition; @interface FSTMutationTests : XCTestCase @end @@ -74,7 +76,7 @@ using firebase::firestore::model::FieldMask; FSTMutation *patch = [[FSTPatchMutation alloc] initWithKey:key fieldMask:{testutil::Field("foo.bar")} value:[FSTObjectValue objectValue] - precondition:[FSTPrecondition none]]; + precondition:Precondition::None()]; FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; diff --git a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm index c0e9cad..454b108 100644 --- a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm +++ b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm @@ -50,6 +50,7 @@ #include "Firestore/core/src/firebase/firestore/model/database_id.h" #include "Firestore/core/src/firebase/firestore/model/field_mask.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/util/string_apple.h" #include "Firestore/core/test/firebase/firestore/testutil/testutil.h" @@ -58,6 +59,7 @@ namespace util = firebase::firestore::util; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::FieldMask; using firebase::firestore::model::FieldTransform; +using firebase::firestore::model::Precondition; NS_ASSUME_NONNULL_BEGIN @@ -377,12 +379,12 @@ NS_ASSUME_NONNULL_BEGIN } - (void)testEncodesSetMutationWithPrecondition { - FSTSetMutation *mutation = [[FSTSetMutation alloc] - initWithKey:FSTTestDocKey(@"foo/bar") - value:FSTTestObjectValue( - @{ @"a" : @"b", - @"num" : @1 }) - precondition:[FSTPrecondition preconditionWithUpdateTime:FSTTestVersion(4)]]; + FSTSetMutation *mutation = + [[FSTSetMutation alloc] initWithKey:FSTTestDocKey(@"foo/bar") + value:FSTTestObjectValue( + @{ @"a" : @"b", + @"num" : @1 }) + precondition:Precondition::UpdateTime(testutil::Version(4))]; GCFSWrite *proto = [GCFSWrite message]; proto.update = [self.serializer encodedDocumentWithFields:mutation.value key:mutation.key]; proto.currentDocument.updateTime = diff --git a/Firestore/Example/Tests/Util/FSTHelpers.mm b/Firestore/Example/Tests/Util/FSTHelpers.mm index 2e1082e..78b41a4 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.mm +++ b/Firestore/Example/Tests/Util/FSTHelpers.mm @@ -48,6 +48,7 @@ #include "Firestore/core/src/firebase/firestore/model/field_mask.h" #include "Firestore/core/src/firebase/firestore/model/field_transform.h" #include "Firestore/core/src/firebase/firestore/model/field_value.h" +#include "Firestore/core/src/firebase/firestore/model/precondition.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "Firestore/core/src/firebase/firestore/model/transform_operations.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" @@ -62,6 +63,7 @@ using firebase::firestore::model::FieldMask; using firebase::firestore::model::FieldPath; using firebase::firestore::model::FieldTransform; using firebase::firestore::model::FieldValue; +using firebase::firestore::model::Precondition; using firebase::firestore::model::ResourcePath; using firebase::firestore::model::ServerTimestampTransform; using firebase::firestore::model::TransformOperation; @@ -250,7 +252,7 @@ FSTDocumentSet *FSTTestDocSet(NSComparator comp, NSArray *docs) { FSTSetMutation *FSTTestSetMutation(NSString *path, NSDictionary *values) { return [[FSTSetMutation alloc] initWithKey:FSTTestDocKey(path) value:FSTTestObjectValue(values) - precondition:[FSTPrecondition none]]; + precondition:Precondition::None()]; } FSTPatchMutation *FSTTestPatchMutation(const absl::string_view path, @@ -274,7 +276,7 @@ FSTPatchMutation *FSTTestPatchMutation(const absl::string_view path, return [[FSTPatchMutation alloc] initWithKey:key fieldMask:mask value:objectValue - precondition:[FSTPrecondition preconditionWithExists:YES]]; + precondition:Precondition::Exists(true)]; } // For now this only creates TransformMutations with server timestamps. @@ -291,8 +293,8 @@ FSTTransformMutation *FSTTestTransformMutation(NSString *path, } FSTDeleteMutation *FSTTestDeleteMutation(NSString *path) { - return [[FSTDeleteMutation alloc] initWithKey:FSTTestDocKey(path) - precondition:[FSTPrecondition none]]; + return + [[FSTDeleteMutation alloc] initWithKey:FSTTestDocKey(path) precondition:Precondition::None()]; } FSTMaybeDocumentDictionary *FSTTestDocUpdates(NSArray *docs) { diff --git a/Firestore/Source/API/FIRDocumentReference.mm b/Firestore/Source/API/FIRDocumentReference.mm index 67e401d..9fb4541 100644 --- a/Firestore/Source/API/FIRDocumentReference.mm +++ b/Firestore/Source/API/FIRDocumentReference.mm @@ -41,11 +41,13 @@ #import "Firestore/Source/Util/FSTUsageValidation.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/precondition.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.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::Precondition; using firebase::firestore::model::ResourcePath; NS_ASSUME_NONNULL_BEGIN @@ -168,7 +170,7 @@ NS_ASSUME_NONNULL_BEGIN ? [self.firestore.dataConverter parsedMergeData:documentData] : [self.firestore.dataConverter parsedSetData:documentData]; return [self.firestore.client - writeMutations:[parsed mutationsWithKey:self.key precondition:[FSTPrecondition none]] + writeMutations:[parsed mutationsWithKey:self.key precondition:Precondition::None()] completion:completion]; } @@ -180,8 +182,7 @@ NS_ASSUME_NONNULL_BEGIN completion:(nullable void (^)(NSError *_Nullable error))completion { FSTParsedUpdateData *parsed = [self.firestore.dataConverter parsedUpdateData:fields]; return [self.firestore.client - writeMutations:[parsed mutationsWithKey:self.key - precondition:[FSTPrecondition preconditionWithExists:YES]] + writeMutations:[parsed mutationsWithKey:self.key precondition:Precondition::Exists(true)] completion:completion]; } @@ -191,7 +192,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)deleteDocumentWithCompletion:(nullable void (^)(NSError *_Nullable error))completion { FSTDeleteMutation *mutation = - [[FSTDeleteMutation alloc] initWithKey:self.key precondition:[FSTPrecondition none]]; + [[FSTDeleteMutation alloc] initWithKey:self.key precondition:Precondition::None()]; return [self.firestore.client writeMutations:@[ mutation ] completion:completion]; } diff --git a/Firestore/Source/API/FIRWriteBatch.mm b/Firestore/Source/API/FIRWriteBatch.mm index b1cfa09..df6e5e0 100644 --- a/Firestore/Source/API/FIRWriteBatch.mm +++ b/Firestore/Source/API/FIRWriteBatch.mm @@ -24,6 +24,10 @@ #import "Firestore/Source/Model/FSTMutation.h" #import "Firestore/Source/Util/FSTUsageValidation.h" +#include "Firestore/core/src/firebase/firestore/model/precondition.h" + +using firebase::firestore::model::Precondition; + NS_ASSUME_NONNULL_BEGIN #pragma mark - FIRWriteBatch @@ -69,8 +73,8 @@ NS_ASSUME_NONNULL_BEGIN [self validateReference:document]; FSTParsedSetData *parsed = options.isMerge ? [self.firestore.dataConverter parsedMergeData:data] : [self.firestore.dataConverter parsedSetData:data]; - [self.mutations addObjectsFromArray:[parsed mutationsWithKey:document.key - precondition:[FSTPrecondition none]]]; + [self.mutations + addObjectsFromArray:[parsed mutationsWithKey:document.key precondition:Precondition::None()]]; return self; } @@ -79,9 +83,8 @@ NS_ASSUME_NONNULL_BEGIN [self verifyNotCommitted]; [self validateReference:document]; FSTParsedUpdateData *parsed = [self.firestore.dataConverter parsedUpdateData:fields]; - [self.mutations - addObjectsFromArray:[parsed mutationsWithKey:document.key - precondition:[FSTPrecondition preconditionWithExists:YES]]]; + [self.mutations addObjectsFromArray:[parsed mutationsWithKey:document.key + precondition:Precondition::Exists(true)]]; return self; } @@ -89,7 +92,7 @@ NS_ASSUME_NONNULL_BEGIN [self verifyNotCommitted]; [self validateReference:document]; [self.mutations addObject:[[FSTDeleteMutation alloc] initWithKey:document.key - precondition:[FSTPrecondition none]]]; + precondition:Precondition::None()]]; return self; } diff --git a/Firestore/Source/API/FSTUserDataConverter.h b/Firestore/Source/API/FSTUserDataConverter.h index a2f947a..ea20b3e 100644 --- a/Firestore/Source/API/FSTUserDataConverter.h +++ b/Firestore/Source/API/FSTUserDataConverter.h @@ -22,12 +22,12 @@ #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/field_mask.h" #include "Firestore/core/src/firebase/firestore/model/field_transform.h" +#include "Firestore/core/src/firebase/firestore/model/precondition.h" @class FIRSetOptions; @class FSTObjectValue; @class FSTFieldValue; @class FSTMutation; -@class FSTPrecondition; @class FSTSnapshotVersion; NS_ASSUME_NONNULL_BEGIN @@ -58,7 +58,8 @@ NS_ASSUME_NONNULL_BEGIN * field transforms) using the specified document key and precondition. */ - (NSArray *)mutationsWithKey:(const firebase::firestore::model::DocumentKey &)key - precondition:(FSTPrecondition *)precondition; + precondition: + (const firebase::firestore::model::Precondition &)precondition; @end @@ -83,7 +84,8 @@ NS_ASSUME_NONNULL_BEGIN * field transforms) using the specified document key and precondition. */ - (NSArray *)mutationsWithKey:(const firebase::firestore::model::DocumentKey &)key - precondition:(FSTPrecondition *)precondition; + precondition: + (const firebase::firestore::model::Precondition &)precondition; @end diff --git a/Firestore/Source/API/FSTUserDataConverter.mm b/Firestore/Source/API/FSTUserDataConverter.mm index 6320e83..dff9235 100644 --- a/Firestore/Source/API/FSTUserDataConverter.mm +++ b/Firestore/Source/API/FSTUserDataConverter.mm @@ -38,6 +38,7 @@ #include "Firestore/core/src/firebase/firestore/model/field_mask.h" #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/transform_operations.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" #include "absl/memory/memory.h" @@ -48,6 +49,7 @@ using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldMask; using firebase::firestore::model::FieldPath; using firebase::firestore::model::FieldTransform; +using firebase::firestore::model::Precondition; using firebase::firestore::model::ServerTimestampTransform; using firebase::firestore::model::TransformOperation; @@ -91,7 +93,7 @@ static NSString *const RESERVED_FIELD_DESIGNATOR = @"__"; } - (NSArray *)mutationsWithKey:(const DocumentKey &)key - precondition:(FSTPrecondition *)precondition { + precondition:(const Precondition &)precondition { NSMutableArray *mutations = [NSMutableArray array]; if (self.isPatch) { [mutations addObject:[[FSTPatchMutation alloc] initWithKey:key @@ -132,7 +134,7 @@ static NSString *const RESERVED_FIELD_DESIGNATOR = @"__"; } - (NSArray *)mutationsWithKey:(const DocumentKey &)key - precondition:(FSTPrecondition *)precondition { + precondition:(const Precondition &)precondition { NSMutableArray *mutations = [NSMutableArray array]; [mutations addObject:[[FSTPatchMutation alloc] initWithKey:key fieldMask:self.fieldMask diff --git a/Firestore/Source/Core/FSTTransaction.mm b/Firestore/Source/Core/FSTTransaction.mm index 71c1f35..681f9ca 100644 --- a/Firestore/Source/Core/FSTTransaction.mm +++ b/Firestore/Source/Core/FSTTransaction.mm @@ -33,8 +33,10 @@ #import "Firestore/Source/Util/FSTUsageValidation.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/precondition.h" using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::Precondition; NS_ASSUME_NONNULL_BEGIN @@ -136,25 +138,26 @@ NS_ASSUME_NONNULL_BEGIN * Returns version of this doc when it was read in this transaction as a precondition, or no * precondition if it was not read. */ -- (FSTPrecondition *)preconditionForDocumentKey:(const DocumentKey &)key { +- (Precondition)preconditionForDocumentKey:(const DocumentKey &)key { const auto iter = _readVersions.find(key); if (iter == _readVersions.end()) { - return [FSTPrecondition none]; + return Precondition::None(); } else { - return [FSTPrecondition preconditionWithUpdateTime:iter->second]; + return Precondition::UpdateTime(iter->second); } } /** * Returns the precondition for a document if the operation is an update, based on the provided - * UpdateOptions. Will return nil if an error occurred, in which case it sets the error parameter. + * UpdateOptions. Will return none precondition if an error occurred, in which case it sets the + * error parameter. */ -- (nullable FSTPrecondition *)preconditionForUpdateWithDocumentKey:(const DocumentKey &)key - error:(NSError **)error { +- (Precondition)preconditionForUpdateWithDocumentKey:(const DocumentKey &)key + error:(NSError **)error { const auto iter = _readVersions.find(key); if (iter == _readVersions.end()) { // Document was not read, so we just use the preconditions for an update. - return [FSTPrecondition preconditionWithExists:YES]; + return Precondition::Exists(true); } FSTSnapshotVersion *version = iter->second; @@ -169,10 +172,10 @@ NS_ASSUME_NONNULL_BEGIN NSLocalizedDescriptionKey : @"Can't update a document that doesn't exist." }]; } - return nil; + return Precondition::None(); } else { // Document exists, just base precondition on document update time. - return [FSTPrecondition preconditionWithUpdateTime:version]; + return Precondition::UpdateTime(version); } } @@ -183,13 +186,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)updateData:(FSTParsedUpdateData *)data forDocument:(const DocumentKey &)key { NSError *error = nil; - FSTPrecondition *_Nullable precondition = - [self preconditionForUpdateWithDocumentKey:key error:&error]; - if (precondition) { - [self writeMutations:[data mutationsWithKey:key precondition:precondition]]; - } else { + const Precondition precondition = [self preconditionForUpdateWithDocumentKey:key error:&error]; + if (precondition.IsNone()) { FSTAssert(error, @"Got nil precondition, but error was not set"); self.lastWriteError = error; + } else { + [self writeMutations:[data mutationsWithKey:key precondition:precondition]]; } } @@ -198,7 +200,7 @@ NS_ASSUME_NONNULL_BEGIN initWithKey:key 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: false. + // precondition for the next write will be exists without timestamp. _readVersions[key] = [FSTSnapshotVersion noVersion]; } diff --git a/Firestore/Source/Model/FSTMutation.h b/Firestore/Source/Model/FSTMutation.h index 38c35bf..7261f30 100644 --- a/Firestore/Source/Model/FSTMutation.h +++ b/Firestore/Source/Model/FSTMutation.h @@ -23,6 +23,7 @@ #include "Firestore/core/src/firebase/firestore/model/field_mask.h" #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/transform_operations.h" @class FSTDocument; @@ -34,49 +35,6 @@ NS_ASSUME_NONNULL_BEGIN -#pragma mark - FSTPrecondition - -typedef NS_ENUM(NSUInteger, FSTPreconditionExists) { - FSTPreconditionExistsNotSet, - FSTPreconditionExistsYes, - FSTPreconditionExistsNo, -}; - -/** - * Encodes a precondition for a mutation. This follows the model that the backend accepts with the - * special case of an explicit "empty" precondition (meaning no precondition). - */ -@interface FSTPrecondition : NSObject - -/** Creates a new FSTPrecondition with an exists flag. */ -+ (FSTPrecondition *)preconditionWithExists:(BOOL)exists; - -/** Creates a new FSTPrecondition based on a time the document exists at. */ -+ (FSTPrecondition *)preconditionWithUpdateTime:(FSTSnapshotVersion *)updateTime; - -/** Returns a precondition representing no precondition. */ -+ (FSTPrecondition *)none; - -/** - * Returns true if the preconditions is valid for the given document (or null if no document is - * available). - */ -- (BOOL)isValidForDocument:(FSTMaybeDocument *_Nullable)maybeDoc; - -/** Returns whether this Precondition represents no precondition. */ -- (BOOL)isNone; - -/** If set, preconditions a mutation based on the last updateTime. */ -@property(nonatomic, strong, readonly, nullable) FSTSnapshotVersion *updateTime; - -/** - * If set, preconditions a mutation based on whether the document exists. - * Uses FSTPreconditionExistsNotSet to mark as unset. - */ -@property(nonatomic, assign, readonly) FSTPreconditionExists exists; - -@end - #pragma mark - FSTMutationResult @interface FSTMutationResult : NSObject @@ -115,7 +73,8 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) { - (id)init NS_UNAVAILABLE; - (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key - precondition:(FSTPrecondition *)precondition NS_DESIGNATED_INITIALIZER; + precondition:(firebase::firestore::model::Precondition)precondition + NS_DESIGNATED_INITIALIZER; /** * Applies this mutation to the given FSTDocument, FSTDeletedDocument or nil, if we don't have @@ -176,8 +135,7 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) { - (const firebase::firestore::model::DocumentKey &)key; -/** The precondition for this mutation. */ -@property(nonatomic, strong, readonly) FSTPrecondition *precondition; +- (const firebase::firestore::model::Precondition &)precondition; @end @@ -190,7 +148,7 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) { @interface FSTSetMutation : FSTMutation - (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key - precondition:(FSTPrecondition *)precondition NS_UNAVAILABLE; + precondition:(firebase::firestore::model::Precondition)precondition NS_UNAVAILABLE; /** * Initializes the set mutation. @@ -202,7 +160,8 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) { */ - (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key value:(FSTObjectValue *)value - precondition:(FSTPrecondition *)precondition NS_DESIGNATED_INITIALIZER; + precondition:(firebase::firestore::model::Precondition)precondition + NS_DESIGNATED_INITIALIZER; /** The object value to use when setting the document. */ @property(nonatomic, strong, readonly) FSTObjectValue *value; @@ -221,9 +180,9 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) { */ @interface FSTPatchMutation : FSTMutation -/** Returns the precondition for the given FSTPrecondition. */ +/** Returns the precondition for the given Precondition. */ - (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key - precondition:(FSTPrecondition *)precondition NS_UNAVAILABLE; + precondition:(firebase::firestore::model::Precondition)precondition NS_UNAVAILABLE; /** * Initializes a new patch mutation with an explicit FieldMask and FSTObjectValue representing @@ -239,7 +198,8 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) { - (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key fieldMask:(firebase::firestore::model::FieldMask)fieldMask value:(FSTObjectValue *)value - precondition:(FSTPrecondition *)precondition NS_DESIGNATED_INITIALIZER; + precondition:(firebase::firestore::model::Precondition)precondition + NS_DESIGNATED_INITIALIZER; /** * A mask to apply to |value|, where only fields that are in both the fieldMask and the value @@ -266,7 +226,7 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) { @interface FSTTransformMutation : FSTMutation - (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key - precondition:(FSTPrecondition *)precondition NS_UNAVAILABLE; + precondition:(firebase::firestore::model::Precondition)precondition NS_UNAVAILABLE; /** * Initializes a new transform mutation with the specified field transforms. diff --git a/Firestore/Source/Model/FSTMutation.mm b/Firestore/Source/Model/FSTMutation.mm index dd649a0..99d2e51 100644 --- a/Firestore/Source/Model/FSTMutation.mm +++ b/Firestore/Source/Model/FSTMutation.mm @@ -33,115 +33,19 @@ #include "Firestore/core/src/firebase/firestore/model/field_mask.h" #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/transform_operations.h" using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldMask; using firebase::firestore::model::FieldPath; using firebase::firestore::model::FieldTransform; +using firebase::firestore::model::Precondition; using firebase::firestore::model::ServerTimestampTransform; using firebase::firestore::model::TransformOperation; NS_ASSUME_NONNULL_BEGIN -#pragma mark - FSTPrecondition - -@implementation FSTPrecondition - -+ (FSTPrecondition *)preconditionWithExists:(BOOL)exists { - FSTPreconditionExists existsEnum = exists ? FSTPreconditionExistsYes : FSTPreconditionExistsNo; - return [[FSTPrecondition alloc] initWithUpdateTime:nil exists:existsEnum]; -} - -+ (FSTPrecondition *)preconditionWithUpdateTime:(FSTSnapshotVersion *)updateTime { - return [[FSTPrecondition alloc] initWithUpdateTime:updateTime exists:FSTPreconditionExistsNotSet]; -} - -+ (FSTPrecondition *)none { - static dispatch_once_t onceToken; - static FSTPrecondition *noPrecondition; - dispatch_once(&onceToken, ^{ - noPrecondition = - [[FSTPrecondition alloc] initWithUpdateTime:nil exists:FSTPreconditionExistsNotSet]; - }); - return noPrecondition; -} - -- (instancetype)initWithUpdateTime:(FSTSnapshotVersion *_Nullable)updateTime - exists:(FSTPreconditionExists)exists { - if (self = [super init]) { - _updateTime = updateTime; - _exists = exists; - } - return self; -} - -- (BOOL)isValidForDocument:(FSTMaybeDocument *_Nullable)maybeDoc { - if (self.updateTime) { - return - [maybeDoc isKindOfClass:[FSTDocument class]] && [maybeDoc.version isEqual:self.updateTime]; - } else if (self.exists != FSTPreconditionExistsNotSet) { - if (self.exists == FSTPreconditionExistsYes) { - return [maybeDoc isKindOfClass:[FSTDocument class]]; - } else { - FSTAssert(self.exists == FSTPreconditionExistsNo, @"Invalid precondition"); - return maybeDoc == nil || [maybeDoc isKindOfClass:[FSTDeletedDocument class]]; - } - } else { - FSTAssert(self.isNone, @"Precondition should be empty"); - return YES; - } -} - -- (BOOL)isNone { - return self.updateTime == nil && self.exists == FSTPreconditionExistsNotSet; -} - -- (BOOL)isEqual:(id)other { - if (self == other) { - return YES; - } - - if (![other isKindOfClass:[FSTPrecondition class]]) { - return NO; - } - - FSTPrecondition *otherPrecondition = (FSTPrecondition *)other; - // Compare references to cover nil equality - return (self.updateTime == otherPrecondition.updateTime || - [self.updateTime isEqual:otherPrecondition.updateTime]) && - self.exists == otherPrecondition.exists; -} - -- (NSUInteger)hash { - NSUInteger hash = [self.updateTime hash]; - hash = hash * 31 + self.exists; - return hash; -} - -- (NSString *)description { - if (self.isNone) { - return @">"; - } else { - NSString *existsString; - switch (self.exists) { - case FSTPreconditionExistsYes: - existsString = @"yes"; - break; - case FSTPreconditionExistsNo: - existsString = @"no"; - break; - default: - existsString = @""; - break; - } - return [NSString stringWithFormat:@"", self.updateTime, - existsString]; - } -} - -@end - #pragma mark - FSTMutationResult @implementation FSTMutationResult @@ -161,12 +65,13 @@ NS_ASSUME_NONNULL_BEGIN @implementation FSTMutation { DocumentKey _key; + Precondition _precondition; } -- (instancetype)initWithKey:(DocumentKey)key precondition:(FSTPrecondition *)precondition { +- (instancetype)initWithKey:(DocumentKey)key precondition:(Precondition)precondition { if (self = [super init]) { _key = std::move(key); - _precondition = precondition; + _precondition = std::move(precondition); } return self; } @@ -189,6 +94,10 @@ NS_ASSUME_NONNULL_BEGIN return _key; } +- (const firebase::firestore::model::Precondition &)precondition { + return _precondition; +} + @end #pragma mark - FSTSetMutation @@ -197,8 +106,8 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithKey:(DocumentKey)key value:(FSTObjectValue *)value - precondition:(FSTPrecondition *)precondition { - if (self = [super initWithKey:std::move(key) precondition:precondition]) { + precondition:(Precondition)precondition { + if (self = [super initWithKey:std::move(key) precondition:std::move(precondition)]) { _value = value; } return self; @@ -206,7 +115,8 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)description { return [NSString stringWithFormat:@"", - self.key.ToString().c_str(), self.value, self.precondition]; + self.key.ToString().c_str(), self.value, + self.precondition.description()]; } - (BOOL)isEqual:(id)other { @@ -219,12 +129,12 @@ NS_ASSUME_NONNULL_BEGIN FSTSetMutation *otherMutation = (FSTSetMutation *)other; return [self.key isEqual:otherMutation.key] && [self.value isEqual:otherMutation.value] && - [self.precondition isEqual:otherMutation.precondition]; + self.precondition == otherMutation.precondition; } - (NSUInteger)hash { NSUInteger result = [self.key hash]; - result = 31 * result + [self.precondition hash]; + result = 31 * result + self.precondition.Hash(); result = 31 * result + [self.value hash]; return result; } @@ -237,7 +147,7 @@ NS_ASSUME_NONNULL_BEGIN FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTSetMutation."); } - if (![self.precondition isValidForDocument:maybeDoc]) { + if (!self.precondition.IsValidFor(maybeDoc)) { return maybeDoc; } @@ -271,8 +181,8 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithKey:(DocumentKey)key fieldMask:(FieldMask)fieldMask value:(FSTObjectValue *)value - precondition:(FSTPrecondition *)precondition { - self = [super initWithKey:std::move(key) precondition:precondition]; + precondition:(Precondition)precondition { + self = [super initWithKey:std::move(key) precondition:std::move(precondition)]; if (self) { _fieldMask = std::move(fieldMask); _value = value; @@ -295,12 +205,12 @@ NS_ASSUME_NONNULL_BEGIN FSTPatchMutation *otherMutation = (FSTPatchMutation *)other; return [self.key isEqual:otherMutation.key] && self.fieldMask == otherMutation.fieldMask && [self.value isEqual:otherMutation.value] && - [self.precondition isEqual:otherMutation.precondition]; + self.precondition == otherMutation.precondition; } - (NSUInteger)hash { NSUInteger result = [self.key hash]; - result = 31 * result + [self.precondition hash]; + result = 31 * result + self.precondition.Hash(); result = 31 * result + self.fieldMask.Hash(); result = 31 * result + [self.value hash]; return result; @@ -309,7 +219,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)description { return [NSString stringWithFormat:@"", self.key.ToString().c_str(), self.fieldMask.ToString().c_str(), - self.value, self.precondition]; + self.value, self.precondition.description()]; } - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc @@ -320,7 +230,7 @@ NS_ASSUME_NONNULL_BEGIN FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTPatchMutation."); } - if (![self.precondition isValidForDocument:maybeDoc]) { + if (!self.precondition.IsValidFor(maybeDoc)) { return maybeDoc; } @@ -373,8 +283,7 @@ NS_ASSUME_NONNULL_BEGIN // NOTE: We set a precondition of exists: true as a safety-check, since we always combine // FSTTransformMutations with a FSTSetMutation or FSTPatchMutation which (if successful) should // end up with an existing document. - if (self = [super initWithKey:std::move(key) - precondition:[FSTPrecondition preconditionWithExists:YES]]) { + if (self = [super initWithKey:std::move(key) precondition:Precondition::Exists(true)]) { _fieldTransforms = std::move(fieldTransforms); } return self; @@ -395,12 +304,12 @@ NS_ASSUME_NONNULL_BEGIN FSTTransformMutation *otherMutation = (FSTTransformMutation *)other; return [self.key isEqual:otherMutation.key] && self.fieldTransforms == otherMutation.fieldTransforms && - [self.precondition isEqual:otherMutation.precondition]; + self.precondition == otherMutation.precondition; } - (NSUInteger)hash { NSUInteger result = [self.key hash]; - result = 31 * result + [self.precondition hash]; + result = 31 * result + self.precondition.Hash(); for (const auto &transform : self.fieldTransforms) { result = 31 * result + transform.Hash(); } @@ -414,7 +323,7 @@ NS_ASSUME_NONNULL_BEGIN } return [NSString stringWithFormat:@"", self.key.ToString().c_str(), fieldTransforms.c_str(), - self.precondition]; + self.precondition.description()]; } - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc @@ -426,7 +335,7 @@ NS_ASSUME_NONNULL_BEGIN @"Transform results missing for FSTTransformMutation."); } - if (![self.precondition isValidForDocument:maybeDoc]) { + if (!self.precondition.IsValidFor(maybeDoc)) { return maybeDoc; } @@ -514,19 +423,18 @@ NS_ASSUME_NONNULL_BEGIN } FSTDeleteMutation *otherMutation = (FSTDeleteMutation *)other; - return [self.key isEqual:otherMutation.key] && - [self.precondition isEqual:otherMutation.precondition]; + return [self.key isEqual:otherMutation.key] && self.precondition == otherMutation.precondition; } - (NSUInteger)hash { NSUInteger result = [self.key hash]; - result = 31 * result + [self.precondition hash]; + result = 31 * result + self.precondition.Hash(); return result; } - (NSString *)description { return [NSString stringWithFormat:@"", - self.key.ToString().c_str(), self.precondition]; + self.key.ToString().c_str(), self.precondition.description()]; } - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc @@ -538,7 +446,7 @@ NS_ASSUME_NONNULL_BEGIN @"Transform results received by FSTDeleteMutation."); } - if (![self.precondition isValidForDocument:maybeDoc]) { + if (!self.precondition.IsValidFor(maybeDoc)) { return maybeDoc; } diff --git a/Firestore/Source/Remote/FSTSerializerBeta.mm b/Firestore/Source/Remote/FSTSerializerBeta.mm index 1d9ad30..c8b0fa4 100644 --- a/Firestore/Source/Remote/FSTSerializerBeta.mm +++ b/Firestore/Source/Remote/FSTSerializerBeta.mm @@ -50,6 +50,7 @@ #include "Firestore/core/src/firebase/firestore/model/field_mask.h" #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/resource_path.h" #include "Firestore/core/src/firebase/firestore/model/transform_operations.h" #include "Firestore/core/src/firebase/firestore/util/string_apple.h" @@ -61,8 +62,10 @@ using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldMask; using firebase::firestore::model::FieldPath; using firebase::firestore::model::FieldTransform; +using firebase::firestore::model::Precondition; using firebase::firestore::model::ResourcePath; using firebase::firestore::model::ServerTimestampTransform; +using firebase::firestore::model::SnapshotVersion; using firebase::firestore::model::TransformOperation; NS_ASSUME_NONNULL_BEGIN @@ -475,7 +478,7 @@ NS_ASSUME_NONNULL_BEGIN FSTFail(@"Unknown mutation type %@", NSStringFromClass(mutationClass)); } - if (!mutation.precondition.isNone) { + if (!mutation.precondition.IsNone()) { proto.currentDocument = [self encodedPrecondition:mutation.precondition]; } @@ -483,9 +486,9 @@ NS_ASSUME_NONNULL_BEGIN } - (FSTMutation *)decodedMutation:(GCFSWrite *)mutation { - FSTPrecondition *precondition = [mutation hasCurrentDocument] - ? [self decodedPrecondition:mutation.currentDocument] - : [FSTPrecondition none]; + Precondition precondition = [mutation hasCurrentDocument] + ? [self decodedPrecondition:mutation.currentDocument] + : Precondition::None(); switch (mutation.operationOneOfCase) { case GCFSWrite_Operation_OneOfCase_Update: @@ -505,8 +508,7 @@ NS_ASSUME_NONNULL_BEGIN precondition:precondition]; case GCFSWrite_Operation_OneOfCase_Transform: { - FSTPreconditionExists exists = precondition.exists; - FSTAssert(exists == FSTPreconditionExistsYes, + FSTAssert(precondition == Precondition::Exists(true), @"Transforms must have precondition \"exists == true\""); return [[FSTTransformMutation alloc] @@ -520,30 +522,29 @@ NS_ASSUME_NONNULL_BEGIN } } -- (GCFSPrecondition *)encodedPrecondition:(FSTPrecondition *)precondition { - FSTAssert(!precondition.isNone, @"Can't serialize an empty precondition"); +- (GCFSPrecondition *)encodedPrecondition:(const Precondition &)precondition { + FSTAssert(!precondition.IsNone(), @"Can't serialize an empty precondition"); GCFSPrecondition *message = [GCFSPrecondition message]; - if (precondition.updateTime) { - message.updateTime = [self encodedVersion:precondition.updateTime]; - } else if (precondition.exists != FSTPreconditionExistsNotSet) { - message.exists = precondition.exists == FSTPreconditionExistsYes; + if (precondition.type() == Precondition::Type::UpdateTime) { + message.updateTime = [self encodedVersion:precondition.update_time()]; + } else if (precondition.type() == Precondition::Type::Exists) { + message.exists = precondition == Precondition::Exists(true); } else { - FSTFail(@"Unknown precondition: %@", precondition); + FSTFail(@"Unknown precondition: %@", precondition.description()); } return message; } -- (FSTPrecondition *)decodedPrecondition:(GCFSPrecondition *)precondition { +- (Precondition)decodedPrecondition:(GCFSPrecondition *)precondition { switch (precondition.conditionTypeOneOfCase) { case GCFSPrecondition_ConditionType_OneOfCase_GPBUnsetOneOfCase: - return [FSTPrecondition none]; + return Precondition::None(); case GCFSPrecondition_ConditionType_OneOfCase_Exists: - return [FSTPrecondition preconditionWithExists:precondition.exists]; + return Precondition::Exists(precondition.exists); case GCFSPrecondition_ConditionType_OneOfCase_UpdateTime: - return [FSTPrecondition - preconditionWithUpdateTime:[self decodedVersion:precondition.updateTime]]; + return Precondition::UpdateTime([self decodedVersion:precondition.updateTime]); default: FSTFail(@"Unrecognized Precondition one-of case %@", precondition); diff --git a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt index de783ad..02affdb 100644 --- a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt @@ -32,6 +32,8 @@ cc_library( maybe_document.h no_document.cc no_document.h + precondition.cc + precondition.h resource_path.cc resource_path.h snapshot_version.cc diff --git a/Firestore/core/src/firebase/firestore/model/precondition.cc b/Firestore/core/src/firebase/firestore/model/precondition.cc new file mode 100644 index 0000000..423d5a2 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/precondition.cc @@ -0,0 +1,67 @@ +/* + * Copyright 2018 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. + */ + +#include "Firestore/core/src/firebase/firestore/model/precondition.h" + +#include "Firestore/core/src/firebase/firestore/model/maybe_document.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +namespace firebase { +namespace firestore { +namespace model { + +Precondition::Precondition(Type type, SnapshotVersion update_time, bool exists) + : type_(type), update_time_(std::move(update_time)), exists_(exists) { +} + +/* static */ +Precondition Precondition::Exists(bool exists) { + return Precondition{Type::Exists, SnapshotVersion::None(), exists}; +} + +/* static */ +Precondition Precondition::UpdateTime(SnapshotVersion update_time) { + // update_time could be SnapshotVersion::None() in particular for locally + // deleted documents. + return Precondition{Type::UpdateTime, std::move(update_time), false}; +} + +/* static */ +Precondition Precondition::None() { + return Precondition{Type::None, SnapshotVersion::None(), false}; +} + +bool Precondition::IsValidFor(const MaybeDocument& maybe_doc) const { + switch (type_) { + case Type::UpdateTime: + return maybe_doc.type() == MaybeDocument::Type::Document && + maybe_doc.version() == update_time_; + case Type::Exists: + if (exists_) { + return maybe_doc.type() == MaybeDocument::Type::Document; + } else { + return maybe_doc.type() == MaybeDocument::Type::NoDocument; + } + case Type::None: + return true; + } + FIREBASE_UNREACHABLE(); +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/precondition.h b/Firestore/core/src/firebase/firestore/model/precondition.h new file mode 100644 index 0000000..4ab03c2 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/precondition.h @@ -0,0 +1,160 @@ +/* + * Copyright 2018 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. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_PRECONDITION_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_PRECONDITION_H_ + +#include + +#if defined(__OBJC__) +#import "FIRTimestamp.h" +#import "Firestore/Source/Core/FSTSnapshotVersion.h" +#import "Firestore/Source/Model/FSTDocument.h" +#include "Firestore/core/include/firebase/firestore/timestamp.h" +#endif // defined(__OBJC__) + +#include "Firestore/core/src/firebase/firestore/model/maybe_document.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +namespace firebase { +namespace firestore { +namespace model { + +/** + * Encodes a precondition for a mutation. This follows the model that the + * backend accepts with the special case of an explicit "empty" precondition + * (meaning no precondition). + */ +class Precondition { + public: + enum class Type { + None, + Exists, + UpdateTime, + }; + + /** Creates a new Precondition with an exists flag. */ + static Precondition Exists(bool exists); + + /** Creates a new Precondition based on a time the document exists at. */ + static Precondition UpdateTime(SnapshotVersion update_time); + + /** Returns a precondition representing no precondition. */ + static Precondition None(); + + /** + * Returns true if the precondition is valid for the given document (and the + * document is available). + */ + bool IsValidFor(const MaybeDocument& maybe_doc) const; + + /** Returns whether this Precondition represents no precondition. */ + bool IsNone() const { + return type_ == Type::None; + } + + Type type() const { + return type_; + } + + const SnapshotVersion& update_time() const { + return update_time_; + } + + bool operator==(const Precondition& other) const { + return type_ == other.type_ && update_time_ == other.update_time_ && + exists_ == other.exists_; + } + +#if defined(__OBJC__) + // Objective-C requires a default constructor. + Precondition() + : type_(Type::None), + update_time_(SnapshotVersion::None()), + exists_(false) { + } + + // MaybeDocument is not fully ported yet. So we suppose this addition helper. + bool IsValidFor(FSTMaybeDocument* maybe_doc) const { + switch (type_) { + case Type::UpdateTime: + return [maybe_doc isKindOfClass:[FSTDocument class]] && + firebase::firestore::model::SnapshotVersion(maybe_doc.version) == + update_time_; + case Type::Exists: + if (exists_) { + return [maybe_doc isKindOfClass:[FSTDocument class]]; + } else { + return maybe_doc == nil || + [maybe_doc isKindOfClass:[FSTDeletedDocument class]]; + } + case Type::None: + return true; + } + FIREBASE_UNREACHABLE(); + } + + // For Objective-C++ hash; to be removed after migration. + // Do NOT use in C++ code. + NSUInteger Hash() const { + NSUInteger hash = std::hash()(update_time_.timestamp()); + hash = hash * 31 + exists_; + hash = hash * 31 + static_cast(type_); + return hash; + } + + NSString* description() const { + switch (type_) { + case Type::None: + return @">"; + case Type::Exists: + if (exists_) { + return @""; + } else { + return @""; + } + case Type::UpdateTime: + return [NSString + stringWithFormat:@"", + update_time_.timestamp().ToString().c_str()]; + } + // We only raise dev assertion here. This function is mainly used in + // logging. + FIREBASE_DEV_ASSERT_MESSAGE(false, "precondition invalid"); + return @""; + } +#endif // defined(__OBJC__) + + private: + Precondition(Type type, SnapshotVersion update_time, bool exists); + + // The actual time of this precondition. + Type type_ = Type::None; + + // For UpdateTime type, preconditions a mutation based on the last updateTime. + SnapshotVersion update_time_; + + // For Exists type, preconditions a mutation based on whether the document + // exists. + bool exists_; +}; + +} // namespace model +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_PRECONDITION_H_ diff --git a/Firestore/core/src/firebase/firestore/model/snapshot_version.h b/Firestore/core/src/firebase/firestore/model/snapshot_version.h index 56e8c50..1fbba1c 100644 --- a/Firestore/core/src/firebase/firestore/model/snapshot_version.h +++ b/Firestore/core/src/firebase/firestore/model/snapshot_version.h @@ -19,6 +19,11 @@ #include "Firestore/core/include/firebase/firestore/timestamp.h" +#if defined(__OBJC__) +#import "FIRTimestamp.h" +#import "Firestore/Source/Core/FSTSnapshotVersion.h" +#endif // defined(__OBJC__) + namespace firebase { namespace firestore { namespace model { @@ -38,6 +43,24 @@ class SnapshotVersion { /** Creates a new version that is smaller than all other versions. */ static const SnapshotVersion& None(); +#if defined(__OBJC__) + SnapshotVersion(FSTSnapshotVersion* version) // NOLINT(runtime/explicit) + : timestamp_{version.timestamp.seconds, version.timestamp.nanoseconds} { + } + + operator FSTSnapshotVersion*() const { + if (timestamp_ == Timestamp{}) { + return [FSTSnapshotVersion noVersion]; + } else { + return [FSTSnapshotVersion + versionWithTimestamp:[FIRTimestamp + timestampWithSeconds:timestamp_.seconds() + nanoseconds:timestamp_ + .nanoseconds()]]; + } + } +#endif // defined(__OBJC__) + private: Timestamp timestamp_; }; diff --git a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt index 3bac89d..9c94677 100644 --- a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt @@ -24,6 +24,7 @@ cc_test( field_value_test.cc maybe_document_test.cc no_document_test.cc + precondition_test.cc resource_path_test.cc snapshot_version_test.cc transform_operations_test.cc diff --git a/Firestore/core/test/firebase/firestore/model/precondition_test.cc b/Firestore/core/test/firebase/firestore/model/precondition_test.cc new file mode 100644 index 0000000..3ddb2ba --- /dev/null +++ b/Firestore/core/test/firebase/firestore/model/precondition_test.cc @@ -0,0 +1,77 @@ +/* + * Copyright 2018 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. + */ + +#include "Firestore/core/src/firebase/firestore/model/precondition.h" + +#include "Firestore/core/src/firebase/firestore/model/document.h" +#include "Firestore/core/src/firebase/firestore/model/no_document.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" +#include "Firestore/core/test/firebase/firestore/testutil/testutil.h" + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace model { + +TEST(Precondition, None) { + const Precondition none = Precondition::None(); + EXPECT_EQ(Precondition::Type::None, none.type()); + EXPECT_TRUE(none.IsNone()); + EXPECT_EQ(SnapshotVersion::None(), none.update_time()); + + const NoDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567); + const Document doc = testutil::Doc("bar/doc", 7654321); + EXPECT_TRUE(none.IsValidFor(deleted_doc)); + EXPECT_TRUE(none.IsValidFor(doc)); +} + +TEST(Precondition, Exists) { + const Precondition exists = Precondition::Exists(true); + const Precondition no_exists = Precondition::Exists(false); + EXPECT_EQ(Precondition::Type::Exists, exists.type()); + EXPECT_EQ(Precondition::Type::Exists, no_exists.type()); + EXPECT_FALSE(exists.IsNone()); + EXPECT_FALSE(no_exists.IsNone()); + EXPECT_EQ(SnapshotVersion::None(), exists.update_time()); + EXPECT_EQ(SnapshotVersion::None(), no_exists.update_time()); + + const NoDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567); + const Document doc = testutil::Doc("bar/doc", 7654321); + EXPECT_FALSE(exists.IsValidFor(deleted_doc)); + EXPECT_TRUE(exists.IsValidFor(doc)); + EXPECT_TRUE(no_exists.IsValidFor(deleted_doc)); + EXPECT_FALSE(no_exists.IsValidFor(doc)); +} + +TEST(Precondition, UpdateTime) { + const Precondition update_time = + Precondition::UpdateTime(testutil::Version(1234567)); + EXPECT_EQ(Precondition::Type::UpdateTime, update_time.type()); + EXPECT_FALSE(update_time.IsNone()); + EXPECT_EQ(testutil::Version(1234567), update_time.update_time()); + + const NoDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567); + const Document not_match = testutil::Doc("bar/doc", 7654321); + const Document match = testutil::Doc("baz/doc", 1234567); + EXPECT_FALSE(update_time.IsValidFor(deleted_doc)); + EXPECT_FALSE(update_time.IsValidFor(not_match)); + EXPECT_TRUE(update_time.IsValidFor(match)); +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/testutil/testutil.h b/Firestore/core/test/firebase/firestore/testutil/testutil.h index 9c69784..9a875f4 100644 --- a/Firestore/core/test/firebase/firestore/testutil/testutil.h +++ b/Firestore/core/test/firebase/firestore/testutil/testutil.h @@ -17,9 +17,16 @@ #ifndef FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_TESTUTIL_TESTUTIL_H_ #define FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_TESTUTIL_TESTUTIL_H_ +#include // NOLINT(build/c++11) +#include + +#include "Firestore/core/include/firebase/firestore/timestamp.h" +#include "Firestore/core/src/firebase/firestore/model/document.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/model/no_document.h" #include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h" #include "absl/strings/string_view.h" namespace firebase { @@ -40,6 +47,28 @@ inline model::ResourcePath Resource(absl::string_view field) { return model::ResourcePath::FromString(field); } +/** + * Creates a snapshot version from the given version timestamp. + * + * @param version a timestamp in microseconds since the epoch. + */ +inline model::SnapshotVersion Version(int64_t version) { + namespace chr = std::chrono; + auto timepoint = + chr::time_point(chr::microseconds(version)); + return model::SnapshotVersion{Timestamp::FromTimePoint(timepoint)}; +} + +inline model::Document Doc(absl::string_view key, int64_t version) { + return model::Document{model::FieldValue::ObjectValueFromMap({}), Key(key), + Version(version), + /* has_local_mutations= */ false}; +} + +inline model::NoDocument DeletedDoc(absl::string_view key, int64_t version) { + return model::NoDocument{Key(key), Version(version)}; +} + // Add a non-inline function to make this library buildable. // TODO(zxu123): remove once there is non-inline function. void dummy(); -- cgit v1.2.3