From a8948ced263e7d3ce870dff47cf7c29b12806247 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Mon, 4 Dec 2017 16:42:43 -0800 Subject: Adding SnapshotOptions to deal with pending ServerTimestamps --- .../Example/Firestore.xcodeproj/project.pbxproj | 4 +- .../Integration/API/FIRServerTimestampTests.m | 160 ++++++++++++++++++++- .../Tests/Integration/API/FIRWriteBatchTests.m | 8 +- .../Example/Tests/Integration/FSTSmokeTests.m | 6 +- Firestore/Example/Tests/Model/FSTFieldValueTests.m | 15 +- Firestore/Example/Tests/Model/FSTMutationTests.m | 38 +++-- Firestore/Example/Tests/Util/FSTEventAccumulator.h | 7 +- Firestore/Example/Tests/Util/FSTEventAccumulator.m | 14 +- Firestore/Source/API/FIRDocumentSnapshot.m | 63 +++++--- Firestore/Source/API/FIRSetOptions.m | 1 - Firestore/Source/API/FIRSnapshotOptions+Internal.h | 38 +++++ Firestore/Source/API/FIRSnapshotOptions.m | 83 +++++++++++ Firestore/Source/Model/FSTFieldValue.h | 66 +++++++-- Firestore/Source/Model/FSTFieldValue.m | 80 ++++++++--- Firestore/Source/Model/FSTMutation.h | 8 +- Firestore/Source/Model/FSTMutation.m | 29 +++- Firestore/Source/Model/FSTMutationBatch.m | 2 + Firestore/Source/Public/FIRDocumentSnapshot.h | 88 ++++++++++++ 18 files changed, 611 insertions(+), 99 deletions(-) create mode 100644 Firestore/Source/API/FIRSnapshotOptions+Internal.h create mode 100644 Firestore/Source/API/FIRSnapshotOptions.m diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 437b661..48bb327 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -917,8 +917,7 @@ inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-SwiftBuildTest/Pods-SwiftBuildTest-frameworks.sh", "${BUILT_PRODUCTS_DIR}/BoringSSL/openssl.framework", - "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework", - "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-f0850809/GoogleToolboxForMac.framework", + "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-Defines-NSData+zlib/GoogleToolboxForMac.framework", "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", "${BUILT_PRODUCTS_DIR}/gRPC/GRPCClient.framework", "${BUILT_PRODUCTS_DIR}/gRPC-Core/grpc.framework", @@ -930,7 +929,6 @@ name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GRPCClient.framework", diff --git a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m index 2ee3966..5b210d7 100644 --- a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m +++ b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m @@ -16,9 +16,13 @@ @import FirebaseFirestore; +#import +#import +#import +#import #import -#import "Firestore/Source/Core/FSTFirestoreClient.h" +#import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Example/Tests/Util/FSTEventAccumulator.h" #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h" @@ -42,11 +46,20 @@ // Listener registration for a listener maintained during the course of the test. id _listenerRegistration; + + // Snapshot options that return the previous value for pending server timestamps. + FIRSnapshotOptions *_previousValue; + FIRSnapshotOptions *_estimateValue; } - (void)setUp { [super setUp]; + _previousValue = + [FIRSnapshotOptions setServerTimestampBehavior:FIRServerTimestampBehaviorPrevious]; + _estimateValue = + [FIRSnapshotOptions setServerTimestampBehavior:FIRServerTimestampBehaviorEstimate]; + // Data written in tests via set. _setData = @{ @"a" : @42, @@ -63,7 +76,7 @@ _docRef = [self documentRef]; _accumulator = [FSTEventAccumulator accumulatorForTest:self]; - _listenerRegistration = [_docRef addSnapshotListener:_accumulator.handler]; + _listenerRegistration = [_docRef addSnapshotListener:_accumulator.valueEventHandler]; // Wait for initial nil snapshot to avoid potential races. FIRDocumentSnapshot *initialSnapshot = [_accumulator awaitEventWithName:@"initial event"]; @@ -76,7 +89,9 @@ [super tearDown]; } -// Returns the expected data, with an arbitrary timestamp substituted in. +#pragma mark - Test Helpers + +/** Returns the expected data, with the specified timestamp substituted in. */ - (NSDictionary *)expectedDataWithTimestamp:(id _Nullable)timestamp { return @{ @"a" : @42, @"when" : timestamp, @"deep" : @{@"when" : timestamp} }; } @@ -88,14 +103,46 @@ XCTAssertEqualObjects(initialDataSnap.data, _initialData); } +/** Waits for a snapshot with local writes. */ +- (FIRDocumentSnapshot *)waitForLocalEvent { + return [_accumulator awaitEventWithName:@"Local event."]; +} + /** Waits for a snapshot containing _setData but with NSNull for the timestamps. */ -- (void)waitForLocalEvent { - FIRDocumentSnapshot *localSnap = [_accumulator awaitEventWithName:@"Local event."]; +- (FIRDocumentSnapshot *)waitForLocalEventWithNullTimestamps { + FIRDocumentSnapshot *localSnap = [self waitForLocalEvent]; XCTAssertEqualObjects(localSnap.data, [self expectedDataWithTimestamp:[NSNull null]]); + return localSnap; +} + +/** Waits for a snapshot containing _setData but with a local estimate for the timestamps. */ +- (FIRDocumentSnapshot *)waitForLocalEventWithEstimatedTimestamps { + FIRDocumentSnapshot *localSnap = [self waitForLocalEvent]; + id timestamp = [localSnap valueForField:@"when" options:_estimateValue]; + XCTAssertTrue([timestamp isKindOfClass:[NSDate class]]); + XCTAssertEqualObjects([localSnap dataWithOptions:_estimateValue], + [self expectedDataWithTimestamp:timestamp]); + return localSnap; +} + +/** Waits for a snapshot containing _setData but using the previous field value for the timestamps. */ +- (FIRDocumentSnapshot *)waitForLocalEventWithPreviousDataFromSnapshot: + (FIRDocumentSnapshot *_Nullable)previousSnapshot { + FIRDocumentSnapshot *localSnap = [self waitForLocalEvent]; + + if (previousSnapshot == nil) { + XCTAssertEqualObjects([localSnap dataWithOptions:_previousValue], + [self expectedDataWithTimestamp:[NSNull null]]); + } else { + XCTAssertEqualObjects([localSnap dataWithOptions:_previousValue], + [self expectedDataWithTimestamp:previousSnapshot[@"when"]]); + } + + return localSnap; } /** Waits for a snapshot containing _setData but with resolved server timestamps. */ -- (void)waitForRemoteEvent { +- (FIRDocumentSnapshot *)waitForRemoteEvent { // server event should have a resolved timestamp; verify it. FIRDocumentSnapshot *remoteSnap = [_accumulator awaitEventWithName:@"Remote event"]; XCTAssertTrue(remoteSnap.exists); @@ -106,8 +153,10 @@ // Validate the rest of the document. XCTAssertEqualObjects(remoteSnap.data, [self expectedDataWithTimestamp:when]); + return remoteSnap; } +/** Runs a transaction block. */ - (void)runTransactionBlock:(void (^)(FIRTransaction *transaction))transactionBlock { XCTestExpectation *expectation = [self expectationWithDescription:@"transaction complete"]; [_docRef.firestore runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **pError) { @@ -121,17 +170,114 @@ [self awaitExpectations]; } +/** Disables the network synchronously. */ +- (void)disableNetwork { + [_docRef.firestore.client disableNetworkWithCompletion:_accumulator.errorEventHandler]; + [_accumulator awaitEventWithName:@"Disconnect event."]; +} + +/** Enables the network synchronously. */ +- (void)enableNetwork { + [_docRef.firestore.client enableNetworkWithCompletion:_accumulator.errorEventHandler]; + [_accumulator awaitEventWithName:@"Reconnect event."]; +} + +#pragma mark - Test Cases + - (void)testServerTimestampsWorkViaSet { [self writeDocumentRef:_docRef data:_setData]; - [self waitForLocalEvent]; + [self waitForLocalEventWithNullTimestamps]; [self waitForRemoteEvent]; } - (void)testServerTimestampsWorkViaUpdate { [self writeInitialData]; [self updateDocumentRef:_docRef data:_updateData]; + [self waitForLocalEventWithNullTimestamps]; + [self waitForRemoteEvent]; +} + +- (void)testServerTimestampsWithEstimatedValue { + [self writeDocumentRef:_docRef data:_setData]; + [self waitForLocalEventWithEstimatedTimestamps]; + [self waitForRemoteEvent]; +} + +- (void)testServerTimestampsWithPreviousValue { + [self writeDocumentRef:_docRef data:_setData]; + [self waitForLocalEventWithPreviousDataFromSnapshot:nil]; + FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent]; + + [_docRef updateData:_updateData]; + [self waitForLocalEventWithPreviousDataFromSnapshot:remoteSnapshot]; + + [self waitForRemoteEvent]; +} + +- (void)testServerTimestampsWithPreviousValueOfDifferentType { + [self writeDocumentRef:_docRef data:_setData]; + [self waitForLocalEvent]; + [self waitForRemoteEvent]; + + [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}]; + FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent]; + XCTAssertEqualObjects([localSnapshot valueForField:@"a"], [NSNull null]); + XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_previousValue], @42); + XCTAssertTrue( + [[localSnapshot valueForField:@"a" options:_estimateValue] isKindOfClass:[NSDate class]]); + + FIRDocumentSnapshot *remoteSnapshot = [_accumulator awaitEventWithName:@"Remote event"]; + XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[NSDate class]]); + XCTAssertTrue( + [[remoteSnapshot valueForField:@"a" options:_previousValue] isKindOfClass:[NSDate class]]); + XCTAssertTrue( + [[remoteSnapshot valueForField:@"a" options:_estimateValue] isKindOfClass:[NSDate class]]); +} + +- (void)testServerTimestampsWithConsecutiveUpdates { + [self writeDocumentRef:_docRef data:_setData]; [self waitForLocalEvent]; [self waitForRemoteEvent]; + + [self disableNetwork]; + + [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}]; + FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent]; + XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_previousValue], @42); + + [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}]; + localSnapshot = [self waitForLocalEvent]; + XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_previousValue], @42); + + [self enableNetwork]; + + FIRDocumentSnapshot *remoteSnapshot = [_accumulator awaitEventWithName:@"Remote event"]; + XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[NSDate class]]); +} + +- (void)testServerTimestampsPreviousValueFromLocalMutation { + [self writeDocumentRef:_docRef data:_setData]; + [self waitForLocalEvent]; + [self waitForRemoteEvent]; + + [self disableNetwork]; + + [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}]; + FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent]; + XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_previousValue], @42); + + [_docRef updateData:@{ @"a" : @1337 }]; + localSnapshot = [self waitForLocalEvent]; + XCTAssertEqualObjects([localSnapshot valueForField:@"a"], @1337); + + [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}]; + localSnapshot = [self waitForLocalEvent]; + XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_previousValue], @1337); + + [self enableNetwork]; + + FIRDocumentSnapshot *remoteSnapshot = [_accumulator awaitEventWithName:@"Remote event"]; + XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[NSDate class]]); } - (void)testServerTimestampsWorkViaTransactionSet { diff --git a/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m b/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m index 562c29f..7dd9d4a 100644 --- a/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m +++ b/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m @@ -131,7 +131,7 @@ FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self]; [collection addSnapshotListenerWithOptions:[[FIRQueryListenOptions options] includeQueryMetadataChanges:YES] - listener:accumulator.handler]; + listener:accumulator.valueEventHandler]; FIRQuerySnapshot *initialSnap = [accumulator awaitEventWithName:@"initial event"]; XCTAssertEqual(initialSnap.count, 0); @@ -161,7 +161,7 @@ FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self]; [collection addSnapshotListenerWithOptions:[[FIRQueryListenOptions options] includeQueryMetadataChanges:YES] - listener:accumulator.handler]; + listener:accumulator.valueEventHandler]; FIRQuerySnapshot *initialSnap = [accumulator awaitEventWithName:@"initial event"]; XCTAssertEqual(initialSnap.count, 0); @@ -195,7 +195,7 @@ FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self]; [collection addSnapshotListenerWithOptions:[[FIRQueryListenOptions options] includeQueryMetadataChanges:YES] - listener:accumulator.handler]; + listener:accumulator.valueEventHandler]; FIRQuerySnapshot *initialSnap = [accumulator awaitEventWithName:@"initial event"]; XCTAssertEqual(initialSnap.count, 0); @@ -227,7 +227,7 @@ FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self]; [doc addSnapshotListenerWithOptions:[[FIRDocumentListenOptions options] includeMetadataChanges:YES] - listener:accumulator.handler]; + listener:accumulator.valueEventHandler]; FIRDocumentSnapshot *initialSnap = [accumulator awaitEventWithName:@"initial event"]; XCTAssertFalse(initialSnap.exists); diff --git a/Firestore/Example/Tests/Integration/FSTSmokeTests.m b/Firestore/Example/Tests/Integration/FSTSmokeTests.m index 847474a..ad75e50 100644 --- a/Firestore/Example/Tests/Integration/FSTSmokeTests.m +++ b/Firestore/Example/Tests/Integration/FSTSmokeTests.m @@ -48,7 +48,7 @@ [self writeDocumentRef:writerRef data:data]; id listenerRegistration = - [readerRef addSnapshotListener:self.eventAccumulator.handler]; + [readerRef addSnapshotListener:self.eventAccumulator.valueEventHandler]; FIRDocumentSnapshot *doc = [self.eventAccumulator awaitEventWithName:@"snapshot"]; XCTAssertEqual([doc class], [FIRDocumentSnapshot class]); @@ -62,7 +62,7 @@ [self readerAndWriterOnDocumentRef:^(NSString *path, FIRDocumentReference *readerRef, FIRDocumentReference *writerRef) { id listenerRegistration = - [readerRef addSnapshotListener:self.eventAccumulator.handler]; + [readerRef addSnapshotListener:self.eventAccumulator.valueEventHandler]; FIRDocumentSnapshot *doc1 = [self.eventAccumulator awaitEventWithName:@"null snapshot"]; XCTAssertFalse(doc1.exists); @@ -82,7 +82,7 @@ - (void)testWillFireValueEventsForEmptyCollections { FIRCollectionReference *collection = [self.db collectionWithPath:@"empty-collection"]; id listenerRegistration = - [collection addSnapshotListener:self.eventAccumulator.handler]; + [collection addSnapshotListener:self.eventAccumulator.valueEventHandler]; FIRQuerySnapshot *snap = [self.eventAccumulator awaitEventWithName:@"empty query snapshot"]; XCTAssertEqual([snap class], [FIRQuerySnapshot class]); diff --git a/Firestore/Example/Tests/Model/FSTFieldValueTests.m b/Firestore/Example/Tests/Model/FSTFieldValueTests.m index acf95f0..54d2ad5 100644 --- a/Firestore/Example/Tests/Model/FSTFieldValueTests.m +++ b/Firestore/Example/Tests/Model/FSTFieldValueTests.m @@ -39,10 +39,12 @@ NSArray *FSTWrapGroups(NSArray *groups) { // strings that can be used instead. if ([value isEqual:@"server-timestamp-1"]) { wrappedValue = [FSTServerTimestampValue - serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 5, 20, 10, 20, 0)]; + serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 5, 20, 10, 20, 0) + previousValue:nil]; } else if ([value isEqual:@"server-timestamp-2"]) { wrappedValue = [FSTServerTimestampValue - serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 10, 21, 15, 32, 0)]; + serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 10, 21, 15, 32, 0) + previousValue:nil]; } else if ([value isKindOfClass:[FSTDocumentKeyReference class]]) { // We directly convert these here so that the databaseIDs can be different. FSTDocumentKeyReference *reference = (FSTDocumentKeyReference *)value; @@ -441,12 +443,15 @@ union DoubleBits { @[ // NOTE: ServerTimestampValues can't be parsed via FSTTestFieldValue(). [FSTServerTimestampValue - serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date1]], + serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date1] + previousValue:nil], [FSTServerTimestampValue - serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date1]] + serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date1] + previousValue:nil] ], @[ [FSTServerTimestampValue - serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date2]] ], + serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date2] + previousValue:nil] ], @[ FSTTestFieldValue(FSTTestGeoPoint(0, 1)), [FSTGeoPointValue geoPointValue:FSTTestGeoPoint(0, 1)] diff --git a/Firestore/Example/Tests/Model/FSTMutationTests.m b/Firestore/Example/Tests/Model/FSTMutationTests.m index 678755e..4c5a4ad 100644 --- a/Firestore/Example/Tests/Model/FSTMutationTests.m +++ b/Firestore/Example/Tests/Model/FSTMutationTests.m @@ -42,7 +42,7 @@ FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO); FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"bar" : @"bar-value"}); - FSTMaybeDocument *setDoc = [set applyTo:baseDoc localWriteTime:_timestamp]; + FSTMaybeDocument *setDoc = [set applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; NSDictionary *expectedData = @{@"bar" : @"bar-value"}; XCTAssertEqualObjects(setDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES)); @@ -54,7 +54,7 @@ FSTMutation *patch = FSTTestPatchMutation(@"collection/key", @{@"foo.bar" : @"new-bar-value"}, nil); - FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc localWriteTime:_timestamp]; + FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; NSDictionary *expectedData = @{ @"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value" }; XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES)); @@ -70,7 +70,7 @@ fieldMask:mask value:[FSTObjectValue objectValue] precondition:[FSTPrecondition none]]; - FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc localWriteTime:_timestamp]; + FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; NSDictionary *expectedData = @{ @"foo" : @{@"baz" : @"baz-value"} }; XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES)); @@ -82,7 +82,7 @@ FSTMutation *patch = FSTTestPatchMutation(@"collection/key", @{@"foo.bar" : @"new-bar-value"}, nil); - FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc localWriteTime:_timestamp]; + FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; NSDictionary *expectedData = @{ @"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value" }; XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES)); @@ -91,7 +91,7 @@ - (void)testPatchingDeletedDocumentsDoesNothing { FSTMaybeDocument *baseDoc = FSTTestDeletedDoc(@"collection/key", 0); FSTMutation *patch = FSTTestPatchMutation(@"collection/key", @{@"foo" : @"bar"}, nil); - FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc localWriteTime:_timestamp]; + FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; XCTAssertEqualObjects(patchedDoc, baseDoc); } @@ -100,7 +100,8 @@ FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO); FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @[ @"foo.bar" ]); - FSTMaybeDocument *transformedDoc = [transform applyTo:baseDoc localWriteTime:_timestamp]; + FSTMaybeDocument *transformedDoc = + [transform applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; // Server timestamps aren't parsed, so we manually insert it. FSTObjectValue *expectedData = FSTTestObjectValue( @@ -108,7 +109,8 @@ @"baz" : @"baz-value" }); expectedData = [expectedData objectBySettingValue:[FSTServerTimestampValue - serverTimestampValueWithLocalWriteTime:_timestamp] + serverTimestampValueWithLocalWriteTime:_timestamp + previousValue:nil] forPath:FSTTestFieldPath(@"foo.bar")]; FSTDocument *expectedDoc = [FSTDocument documentWithData:expectedData @@ -129,8 +131,10 @@ initWithVersion:FSTTestVersion(1) transformResults:@[ [FSTTimestampValue timestampValue:_timestamp] ]]; - FSTMaybeDocument *transformedDoc = - [transform applyTo:baseDoc localWriteTime:_timestamp mutationResult:mutationResult]; + FSTMaybeDocument *transformedDoc = [transform applyTo:baseDoc + baseDoc:baseDoc + localWriteTime:_timestamp + mutationResult:mutationResult]; NSDictionary *expectedData = @{ @"foo" : @{@"bar" : _timestamp.approximateDateValue}, @@ -143,7 +147,7 @@ FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO); FSTMutation *mutation = FSTTestDeleteMutation(@"collection/key"); - FSTMaybeDocument *result = [mutation applyTo:baseDoc localWriteTime:_timestamp]; + FSTMaybeDocument *result = [mutation applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; XCTAssertEqualObjects(result, FSTTestDeletedDoc(@"collection/key", 0)); } @@ -155,7 +159,7 @@ FSTMutationResult *mutationResult = [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(4) transformResults:nil]; FSTMaybeDocument *setDoc = - [set applyTo:baseDoc localWriteTime:_timestamp mutationResult:mutationResult]; + [set applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp mutationResult:mutationResult]; NSDictionary *expectedData = @{@"foo" : @"new-bar"}; XCTAssertEqualObjects(setDoc, FSTTestDoc(@"collection/key", 0, expectedData, NO)); @@ -168,8 +172,10 @@ FSTMutation *patch = FSTTestPatchMutation(@"collection/key", @{@"foo" : @"new-bar"}, nil); FSTMutationResult *mutationResult = [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(4) transformResults:nil]; - FSTMaybeDocument *patchedDoc = - [patch applyTo:baseDoc localWriteTime:_timestamp mutationResult:mutationResult]; + FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc + baseDoc:baseDoc + localWriteTime:_timestamp + mutationResult:mutationResult]; NSDictionary *expectedData = @{@"foo" : @"new-bar"}; XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, NO)); @@ -179,8 +185,10 @@ do { \ FSTMutationResult *mutationResult = \ [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(0) transformResults:nil]; \ - FSTMaybeDocument *actual = \ - [mutation applyTo:base localWriteTime:_timestamp mutationResult:mutationResult]; \ + FSTMaybeDocument *actual = [mutation applyTo:base \ + baseDoc:base \ + localWriteTime:_timestamp \ + mutationResult:mutationResult]; \ XCTAssertEqualObjects(actual, expected); \ } while (0); diff --git a/Firestore/Example/Tests/Util/FSTEventAccumulator.h b/Firestore/Example/Tests/Util/FSTEventAccumulator.h index ae5392c..e32f08b 100644 --- a/Firestore/Example/Tests/Util/FSTEventAccumulator.h +++ b/Firestore/Example/Tests/Util/FSTEventAccumulator.h @@ -23,7 +23,8 @@ NS_ASSUME_NONNULL_BEGIN -typedef void (^FSTGenericEventHandler)(id _Nullable, NSError *error); +typedef void (^FSTValueEventHandler)(id _Nullable, NSError *error); +typedef void (^FSTErrorEventHandler)(NSError *error); @interface FSTEventAccumulator : NSObject @@ -35,7 +36,9 @@ typedef void (^FSTGenericEventHandler)(id _Nullable, NSError *error); - (NSArray *)awaitEvents:(NSUInteger)events name:(NSString *)name; -@property(nonatomic, strong, readonly) FSTGenericEventHandler handler; +@property(nonatomic, strong, readonly) FSTValueEventHandler valueEventHandler; +@property(nonatomic, strong, readonly) FSTErrorEventHandler errorEventHandler; + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Example/Tests/Util/FSTEventAccumulator.m b/Firestore/Example/Tests/Util/FSTEventAccumulator.m index b44ec67..9e48ce7 100644 --- a/Firestore/Example/Tests/Util/FSTEventAccumulator.m +++ b/Firestore/Example/Tests/Util/FSTEventAccumulator.m @@ -68,8 +68,8 @@ NS_ASSUME_NONNULL_BEGIN return events[0]; } -// Overrides the handler property -- (void (^)(id _Nullable, NSError *))handler { +// Override the valueEventHandler property +- (void (^)(id _Nullable, NSError *))valueEventHandler { return ^void(id _Nullable value, NSError *error) { // We can't store nil in the _events array, but these are still interesting to tests so store // NSNull instead. @@ -82,6 +82,16 @@ NS_ASSUME_NONNULL_BEGIN }; } +// Override the errorEventHandler property +- (void (^)(NSError *))errorEventHandler { + return ^void(NSError *error) { + @synchronized(self) { + [_events addObject:[NSNull null]]; + [self checkFulfilled]; + } + }; +} + - (void)checkFulfilled { if (_events.count >= self.maxEvents) { [self.expectation fulfill]; diff --git a/Firestore/Source/API/FIRDocumentSnapshot.m b/Firestore/Source/API/FIRDocumentSnapshot.m index b78472e..0d60033 100644 --- a/Firestore/Source/API/FIRDocumentSnapshot.m +++ b/Firestore/Source/API/FIRDocumentSnapshot.m @@ -20,6 +20,7 @@ #import "Firestore/Source/API/FIRFieldPath+Internal.h" #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" +#import "Firestore/Source/API/FIRSnapshotOptions+Internal.h" #import "Firestore/Source/Model/FSTDatabaseID.h" #import "Firestore/Source/Model/FSTDocument.h" #import "Firestore/Source/Model/FSTDocumentKey.h" @@ -100,6 +101,10 @@ NS_ASSUME_NONNULL_BEGIN } - (NSDictionary *)data { + return [self dataWithOptions:[FIRSnapshotOptions defaultOptions]]; +} + +- (NSDictionary *)dataWithOptions:(FIRSnapshotOptions *)options { FSTDocument *document = self.internalDocument; if (!document) { @@ -110,29 +115,38 @@ NS_ASSUME_NONNULL_BEGIN self.internalKey); } - return [self convertedObject:[self.internalDocument data]]; + return [self convertedObject:[self.internalDocument data] + options:[self convertedSnapshotOptions:options]]; } -- (nullable id)objectForKeyedSubscript:(id)key { +- (nullable id)valueForField:(id)field { + return [self valueForField:field options:[FIRSnapshotOptions defaultOptions]]; +} + +- (nullable id)valueForField:(id)field options:(FIRSnapshotOptions *)options { FIRFieldPath *fieldPath; - if ([key isKindOfClass:[NSString class]]) { - fieldPath = [FIRFieldPath pathWithDotSeparatedString:key]; - } else if ([key isKindOfClass:[FIRFieldPath class]]) { - fieldPath = key; + if ([field isKindOfClass:[NSString class]]) { + fieldPath = [FIRFieldPath pathWithDotSeparatedString:field]; + } else if ([field isKindOfClass:[FIRFieldPath class]]) { + fieldPath = field; } else { FSTThrowInvalidArgument(@"Subscript key must be an NSString or FIRFieldPath."); } FSTFieldValue *fieldValue = [[self.internalDocument data] valueForPath:fieldPath.internalValue]; - return [self convertedValue:fieldValue]; + return [self convertedValue:fieldValue options:[self convertedSnapshotOptions:options]]; } -- (id)convertedValue:(FSTFieldValue *)value { +- (nullable id)objectForKeyedSubscript:(id)key { + return [self valueForField:key]; +} + +- (id)convertedValue:(FSTFieldValue *)value options:(FSTFieldValueOptions *)options { if ([value isKindOfClass:[FSTObjectValue class]]) { - return [self convertedObject:(FSTObjectValue *)value]; + return [self convertedObject:(FSTObjectValue *)value options:options]; } else if ([value isKindOfClass:[FSTArrayValue class]]) { - return [self convertedArray:(FSTArrayValue *)value]; + return [self convertedArray:(FSTArrayValue *)value options:options]; } else if ([value isKindOfClass:[FSTReferenceValue class]]) { FSTReferenceValue *ref = (FSTReferenceValue *)value; FSTDatabaseID *refDatabase = ref.databaseID; @@ -146,30 +160,47 @@ NS_ASSUME_NONNULL_BEGIN self.reference.path, refDatabase.projectID, refDatabase.databaseID, database.projectID, database.databaseID); } - return [FIRDocumentReference referenceWithKey:ref.value firestore:self.firestore]; + return [FIRDocumentReference referenceWithKey:[ref valueWithOptions:options] + firestore:self.firestore]; } else { - return value.value; + return [value valueWithOptions:options]; } } -- (NSDictionary *)convertedObject:(FSTObjectValue *)objectValue { +- (NSDictionary *)convertedObject:(FSTObjectValue *)objectValue + options:(FSTFieldValueOptions *)options { NSMutableDictionary *result = [NSMutableDictionary dictionary]; [objectValue.internalValue enumerateKeysAndObjectsUsingBlock:^(NSString *key, FSTFieldValue *value, BOOL *stop) { - result[key] = [self convertedValue:value]; + result[key] = [self convertedValue:value options:options]; }]; return result; } -- (NSArray *)convertedArray:(FSTArrayValue *)arrayValue { +- (NSArray *)convertedArray:(FSTArrayValue *)arrayValue + options:(FSTFieldValueOptions *)options { NSArray *internalValue = arrayValue.internalValue; NSMutableArray *result = [NSMutableArray arrayWithCapacity:internalValue.count]; [internalValue enumerateObjectsUsingBlock:^(id value, NSUInteger idx, BOOL *stop) { - [result addObject:[self convertedValue:value]]; + [result addObject:[self convertedValue:value options:options]]; }]; return result; } +/** Create a field value option from a snapshot option. */ +- (FSTFieldValueOptions *)convertedSnapshotOptions:(FIRSnapshotOptions *)snapshotOptions { + switch (snapshotOptions.serverTimestampBehavior) { + case FIRServerTimestampBehaviorEstimate: + return [[FSTFieldValueOptions alloc] + initWithServerTimestampBehavior:FSTServerTimestampBehaviorEstimate]; + case FIRServerTimestampBehaviorPrevious: + return [[FSTFieldValueOptions alloc] + initWithServerTimestampBehavior:FSTServerTimestampBehaviorPrevious]; + default: + return [FSTFieldValueOptions defaultOptions]; + } +} + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/API/FIRSetOptions.m b/Firestore/Source/API/FIRSetOptions.m index 623deaa..743bcc7 100644 --- a/Firestore/Source/API/FIRSetOptions.m +++ b/Firestore/Source/API/FIRSetOptions.m @@ -15,7 +15,6 @@ */ #import "Firestore/Source/API/FIRSetOptions+Internal.h" -#import "Firestore/Source/Model/FSTMutation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/Firestore/Source/API/FIRSnapshotOptions+Internal.h b/Firestore/Source/API/FIRSnapshotOptions+Internal.h new file mode 100644 index 0000000..de2aaa7 --- /dev/null +++ b/Firestore/Source/API/FIRSnapshotOptions+Internal.h @@ -0,0 +1,38 @@ +/* + * 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 "FIRDocumentSnapshot.h" + +#import + +@class FIRSnapshotOptions; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRSnapshotOptions (Internal) + +/** Returns a default instance of FIRSnapshotOptions that specifies no options. */ ++ (instancetype)defaultOptions; + +/* Initializes a new instance with the specified server timestamp behavior. */ +- (instancetype)initWithServerTimestampBehavior:(int)serverTimestampBehavior; + +/* Returns the server timestamp behavior. Returns -1 if no behavior is specified. */ +- (int)serverTimestampBehavior; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/API/FIRSnapshotOptions.m b/Firestore/Source/API/FIRSnapshotOptions.m new file mode 100644 index 0000000..adc4a65 --- /dev/null +++ b/Firestore/Source/API/FIRSnapshotOptions.m @@ -0,0 +1,83 @@ +/* + * 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 "FIRDocumentSnapshot.h" + +#import "FIRDocumentSnapshot+Internal.h" +#import "FSTAssert.h" +#import "Firestore/Source/API/FIRDocumentReference+Internal.h" +#import "Firestore/Source/API/FIRFieldPath+Internal.h" +#import "Firestore/Source/API/FIRFirestore+Internal.h" +#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" +#import "Firestore/Source/API/FIRSnapshotOptions+Internal.h" +#import "Firestore/Source/Model/FSTDatabaseID.h" +#import "Firestore/Source/Model/FSTDocument.h" +#import "Firestore/Source/Model/FSTDocumentKey.h" +#import "Firestore/Source/Model/FSTFieldValue.h" +#import "Firestore/Source/Model/FSTPath.h" +#import "Firestore/Source/Util/FSTUsageValidation.h" + +NS_ASSUME_NONNULL_BEGIN + +/** The default server timestamp behavior (returning NSNull for pending timestamps). */ +static const int kFIRServerTimestampBehaviorDefault = -1; + +@interface FIRSnapshotOptions () + +@property(nonatomic) int serverTimestampBehavior; + +@end + +@implementation FIRSnapshotOptions + +- (instancetype)initWithServerTimestampBehavior:(int)serverTimestampBehavior { + self = [super init]; + + if (self) { + _serverTimestampBehavior = serverTimestampBehavior; + } + + return self; +} + ++ (instancetype)defaultOptions { + static FIRSnapshotOptions *sharedInstance = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + sharedInstance = [[FIRSnapshotOptions alloc] + initWithServerTimestampBehavior:kFIRServerTimestampBehaviorDefault]; + }); + + return sharedInstance; +} + ++ (instancetype)setServerTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior { + switch (serverTimestampBehavior) { + case FIRServerTimestampBehaviorEstimate: + return [[FIRSnapshotOptions alloc] + initWithServerTimestampBehavior:FIRServerTimestampBehaviorEstimate]; + case FIRServerTimestampBehaviorPrevious: + return [[FIRSnapshotOptions alloc] + initWithServerTimestampBehavior:FIRServerTimestampBehaviorPrevious]; + default: + FSTFail(@"Encountered unknown server timestamp behavior: %d", (int)serverTimestampBehavior); + } +} + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Firestore/Source/Model/FSTFieldValue.h b/Firestore/Source/Model/FSTFieldValue.h index 6de9793..969b3b0 100644 --- a/Firestore/Source/Model/FSTFieldValue.h +++ b/Firestore/Source/Model/FSTFieldValue.h @@ -22,6 +22,7 @@ @class FSTDocumentKey; @class FSTFieldPath; @class FSTTimestamp; +@class FSTFieldValueOptions; @class FIRGeoPoint; NS_ASSUME_NONNULL_BEGIN @@ -40,6 +41,30 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { FSTTypeOrderObject, }; +/** Defines the return value for pending server timestamps. */ +typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { + FSTServerTimestampBehaviorDefault, + FSTServerTimestampBehaviorEstimate, + FSTServerTimestampBehaviorPrevious, +}; + +/** Holds properties that define field value deserialization options. */ +@interface FSTFieldValueOptions : NSObject + +@property(nonatomic, readonly) FSTServerTimestampBehavior serverTimestampBehavior; + +- (instancetype)init NS_UNAVAILABLE; + +/** Creates a FSTFieldValueOptions instance that specifies deserialization behavior for pending + * server timestamps. */ +- (instancetype)initWithServerTimestampBehavior:(FSTServerTimestampBehavior)serverTimestampBehavior + NS_DESIGNATED_INITIALIZER; + +/** Returns the default deserialization options. */ ++ (instancetype)defaultOptions; + +@end + /** * Abstract base class representing an immutable data value as stored in Firestore. FSTFieldValue * represents all the different kinds of values that can be stored in fields in a document. @@ -71,6 +96,14 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { */ - (id)value; +/** + * Converts an FSTFieldValue into the value that users will see in document snapshots. + * + * Options can be provided to configure the deserialization of some field values (such as server + * timestamps). + */ +- (id)valueWithOptions:(FSTFieldValueOptions *)options; + /** Compares against another FSTFieldValue. */ - (NSComparisonResult)compare:(FSTFieldValue *)other; @@ -81,7 +114,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { */ @interface FSTNullValue : FSTFieldValue + (instancetype)nullValue; -- (id)value; +- (id)valueWithOptions:(FSTFieldValueOptions *)options; @end /** @@ -91,7 +124,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { + (instancetype)trueValue; + (instancetype)falseValue; + (instancetype)booleanValue:(BOOL)value; -- (NSNumber *)value; +- (NSNumber *)valueWithOptions:(FSTFieldValueOptions *)options; @end /** @@ -106,8 +139,8 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { */ @interface FSTIntegerValue : FSTNumberValue + (instancetype)integerValue:(int64_t)value; -- (NSNumber *)value; - (int64_t)internalValue; +- (NSNumber *)valueWithOptions:(FSTFieldValueOptions *)options; @end /** @@ -116,8 +149,8 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { @interface FSTDoubleValue : FSTNumberValue + (instancetype)doubleValue:(double)value; + (instancetype)nanValue; -- (NSNumber *)value; - (double)internalValue; +- (NSNumber *)valueWithOptions:(FSTFieldValueOptions *)options; @end /** @@ -125,7 +158,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { */ @interface FSTStringValue : FSTFieldValue + (instancetype)stringValue:(NSString *)value; -- (NSString *)value; +- (NSString *)valueWithOptions:(FSTFieldValueOptions *)options; @end /** @@ -133,7 +166,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { */ @interface FSTTimestampValue : FSTFieldValue + (instancetype)timestampValue:(FSTTimestamp *)value; -- (NSDate *)value; +- (NSDate *)valueWithOptions:(FSTFieldValueOptions *)options; - (FSTTimestamp *)internalValue; @end @@ -144,15 +177,18 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { * - FSTServerTimestampValue instances are created as the result of applying an FSTTransformMutation * (see [FSTTransformMutation applyTo]). They can only exist in the local view of a document. * Therefore they do not need to be parsed or serialized. - * - When evaluated locally (e.g. via FSTDocumentSnapshot data), they evaluate to NSNull (at least - * for now, see b/62064202). + * - When evaluated locally (e.g. via FSTDocumentSnapshot data), they by default evaluate to NSNull. + * This behavior can be configured by passing custom FSTFieldValueOptions to `valueWithOptions:`. * - They sort after all FSTTimestampValues. With respect to other FSTServerTimestampValues, they * sort by their localWriteTime. */ @interface FSTServerTimestampValue : FSTFieldValue -+ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime; -- (NSNull *)value; ++ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime + previousValue:(nullable FSTFieldValue *)previousValue; + @property(nonatomic, strong, readonly) FSTTimestamp *localWriteTime; +@property(nonatomic, strong, readonly, nullable) FSTFieldValue *previousValue; + @end /** @@ -160,7 +196,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { */ @interface FSTGeoPointValue : FSTFieldValue + (instancetype)geoPointValue:(FIRGeoPoint *)value; -- (FIRGeoPoint *)value; +- (FIRGeoPoint *)valueWithOptions:(FSTFieldValueOptions *)options; @end /** @@ -168,7 +204,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { */ @interface FSTBlobValue : FSTFieldValue + (instancetype)blobValue:(NSData *)value; -- (NSData *)value; +- (NSData *)valueWithOptions:(FSTFieldValueOptions *)options; @end /** @@ -176,7 +212,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { */ @interface FSTReferenceValue : FSTFieldValue + (instancetype)referenceValue:(FSTDocumentKey *)value databaseID:(FSTDatabaseID *)databaseID; -- (FSTDocumentKey *)value; +- (FSTDocumentKey *)valueWithOptions:(FSTFieldValueOptions *)options; @property(nonatomic, strong, readonly) FSTDatabaseID *databaseID; @end @@ -200,7 +236,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { - (instancetype)init NS_UNAVAILABLE; -- (NSDictionary *)value; +- (NSDictionary *)valueWithOptions:(FSTFieldValueOptions *)options; - (FSTImmutableSortedDictionary *)internalValue; /** Returns the value at the given path if it exists. Returns nil otherwise. */ @@ -234,7 +270,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { - (instancetype)init NS_UNAVAILABLE; -- (NSArray *)value; +- (NSArray *)valueWithOptions:(FSTFieldValueOptions *)options; - (NSArray *)internalValue; @end diff --git a/Firestore/Source/Model/FSTFieldValue.m b/Firestore/Source/Model/FSTFieldValue.m index 95ad306..65478ce 100644 --- a/Firestore/Source/Model/FSTFieldValue.m +++ b/Firestore/Source/Model/FSTFieldValue.m @@ -27,6 +27,34 @@ NS_ASSUME_NONNULL_BEGIN +#pragma mark - FSTFieldValueOptions + +@implementation FSTFieldValueOptions + +- (instancetype)initWithServerTimestampBehavior: + (FSTServerTimestampBehavior)serverTimestampBehavior { + self = [super init]; + + if (self) { + _serverTimestampBehavior = serverTimestampBehavior; + } + return self; +} + ++ (instancetype)defaultOptions { + static FSTFieldValueOptions *sharedInstance = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + sharedInstance = [[FSTFieldValueOptions alloc] + initWithServerTimestampBehavior:FSTServerTimestampBehaviorDefault]; + }); + + return sharedInstance; +} + +@end + #pragma mark - FSTFieldValue @interface FSTFieldValue () @@ -40,6 +68,10 @@ NS_ASSUME_NONNULL_BEGIN } - (id)value { + return [self valueWithOptions:[FSTFieldValueOptions defaultOptions]]; +} + +- (id)valueWithOptions:(FSTFieldValueOptions *)options { @throw FSTAbstractMethodException(); // NOLINT } @@ -89,7 +121,7 @@ NS_ASSUME_NONNULL_BEGIN return FSTTypeOrderNull; } -- (id)value { +- (id)valueWithOptions:(FSTFieldValueOptions *)options { return [NSNull null]; } @@ -155,7 +187,7 @@ NS_ASSUME_NONNULL_BEGIN return FSTTypeOrderBoolean; } -- (id)value { +- (id)valueWithOptions:(FSTFieldValueOptions *)options { return self.internalValue ? @YES : @NO; } @@ -233,7 +265,7 @@ NS_ASSUME_NONNULL_BEGIN return self; } -- (id)value { +- (id)valueWithOptions:(FSTFieldValueOptions *)options { return @(self.internalValue); } @@ -285,7 +317,7 @@ NS_ASSUME_NONNULL_BEGIN return self; } -- (id)value { +- (id)valueWithOptions:(FSTFieldValueOptions *)options { return @(self.internalValue); } @@ -332,7 +364,7 @@ NS_ASSUME_NONNULL_BEGIN return FSTTypeOrderString; } -- (id)value { +- (id)valueWithOptions:(FSTFieldValueOptions *)options { return self.internalValue; } @@ -379,7 +411,7 @@ NS_ASSUME_NONNULL_BEGIN return FSTTypeOrderTimestamp; } -- (id)value { +- (id)valueWithOptions:(FSTFieldValueOptions *)options { // For developers, we expose Timestamps as Dates. return self.internalValue.approximateDateValue; } @@ -410,14 +442,18 @@ NS_ASSUME_NONNULL_BEGIN @implementation FSTServerTimestampValue -+ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime { - return [[FSTServerTimestampValue alloc] initWithLocalWriteTime:localWriteTime]; ++ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime + previousValue:(nullable FSTFieldValue *)previousValue { + return [[FSTServerTimestampValue alloc] initWithLocalWriteTime:localWriteTime + previousValue:previousValue]; } -- (id)initWithLocalWriteTime:(FSTTimestamp *)localWriteTime { +- (id)initWithLocalWriteTime:(FSTTimestamp *)localWriteTime + previousValue:(nullable FSTFieldValue *)previousValue { self = [super init]; if (self) { _localWriteTime = localWriteTime; + _previousValue = previousValue; } return self; } @@ -426,9 +462,17 @@ NS_ASSUME_NONNULL_BEGIN return FSTTypeOrderTimestamp; } -- (NSNull *)value { - // For developers, server timestamps always evaluate to NSNull (for now, at least; b/62064202). - return [NSNull null]; +- (id)valueWithOptions:(FSTFieldValueOptions *)options { + switch (options.serverTimestampBehavior) { + case FSTServerTimestampBehaviorDefault: + return [NSNull null]; + case FSTServerTimestampBehaviorEstimate: + return [self.localWriteTime approximateDateValue]; + case FSTServerTimestampBehaviorPrevious: + return self.previousValue ? [self.previousValue valueWithOptions:options] : [NSNull null]; + default: + FSTFail(@"Unexpected server timestamp option: %d", (int)options.serverTimestampBehavior); + } } - (BOOL)isEqual:(id)other { @@ -481,7 +525,7 @@ NS_ASSUME_NONNULL_BEGIN return FSTTypeOrderGeoPoint; } -- (id)value { +- (id)valueWithOptions:(FSTFieldValueOptions *)options { return self.internalValue; } @@ -529,7 +573,7 @@ NS_ASSUME_NONNULL_BEGIN return FSTTypeOrderBlob; } -- (id)value { +- (id)valueWithOptions:(FSTFieldValueOptions *)options { return self.internalValue; } @@ -573,7 +617,7 @@ NS_ASSUME_NONNULL_BEGIN return self; } -- (id)value { +- (id)valueWithOptions:(FSTFieldValueOptions *)options { return self.key; } @@ -648,11 +692,11 @@ NS_ASSUME_NONNULL_BEGIN return [self initWithImmutableDictionary:dictionary]; } -- (id)value { +- (id)valueWithOptions:(FSTFieldValueOptions *)options { NSMutableDictionary *result = [NSMutableDictionary dictionary]; [self.internalValue enumerateKeysAndObjectsUsingBlock:^(NSString *key, FSTFieldValue *obj, BOOL *stop) { - result[key] = [obj value]; + result[key] = [obj valueWithOptions:options]; }]; return result; } @@ -803,7 +847,7 @@ NS_ASSUME_NONNULL_BEGIN return [self.internalValue hash]; } -- (id)value { +- (id)valueWithOptions:(FSTFieldValueOptions *)options { NSMutableArray *result = [NSMutableArray arrayWithCapacity:_internalValue.count]; [self.internalValue enumerateObjectsUsingBlock:^(FSTFieldValue *obj, NSUInteger idx, BOOL *stop) { [result addObject:[obj value]]; diff --git a/Firestore/Source/Model/FSTMutation.h b/Firestore/Source/Model/FSTMutation.h index ef7f1c8..b2e7f5e 100644 --- a/Firestore/Source/Model/FSTMutation.h +++ b/Firestore/Source/Model/FSTMutation.h @@ -158,8 +158,10 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) { * Applies this mutation to the given FSTDocument, FSTDeletedDocument or nil, if we don't have * information about this document. Both the input and returned documents can be nil. * - * @param maybeDoc The document to mutate. The input document should nil if it does not currently - * exist. + * @param maybeDoc The current state of the document to mutate. The input document should be nil if + * it does not currently exist. + * @param baseDoc The state of the document prior to this mutation batch. The input document should + * be nil if it the document did not exist. * @param localWriteTime A timestamp indicating the local write time of the batch this mutation is * a part of. * @param mutationResult Optional result info from the backend. If omitted, it's assumed that @@ -197,6 +199,7 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) { * FSTSetMutation, but not necessarily for an FSTPatchMutation). */ - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc + baseDoc:(FSTMaybeDocument *_Nullable)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime mutationResult:(FSTMutationResult *_Nullable)mutationResult; @@ -205,6 +208,7 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) { * backend). */ - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc + baseDoc:(FSTMaybeDocument *_Nullable)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime; @property(nonatomic, strong, readonly) FSTDocumentKey *key; diff --git a/Firestore/Source/Model/FSTMutation.m b/Firestore/Source/Model/FSTMutation.m index 5b47280..2685990 100644 --- a/Firestore/Source/Model/FSTMutation.m +++ b/Firestore/Source/Model/FSTMutation.m @@ -237,14 +237,16 @@ NS_ASSUME_NONNULL_BEGIN } - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc + baseDoc:(FSTMaybeDocument *_Nullable)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime mutationResult:(FSTMutationResult *_Nullable)mutationResult { @throw FSTAbstractMethodException(); // NOLINT } - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc + baseDoc:(FSTMaybeDocument *_Nullable)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime { - return [self applyTo:maybeDoc localWriteTime:localWriteTime mutationResult:nil]; + return [self applyTo:maybeDoc baseDoc:baseDoc localWriteTime:localWriteTime mutationResult:nil]; } @end @@ -288,6 +290,7 @@ NS_ASSUME_NONNULL_BEGIN } - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc + baseDoc:(FSTMaybeDocument *_Nullable)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime mutationResult:(FSTMutationResult *_Nullable)mutationResult { if (mutationResult) { @@ -363,6 +366,7 @@ NS_ASSUME_NONNULL_BEGIN } - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc + baseDoc:(FSTMaybeDocument *_Nullable)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime mutationResult:(FSTMutationResult *_Nullable)mutationResult { if (mutationResult) { @@ -452,6 +456,7 @@ NS_ASSUME_NONNULL_BEGIN } - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc + baseDoc:(FSTMaybeDocument *_Nullable)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime mutationResult:(FSTMutationResult *_Nullable)mutationResult { if (mutationResult) { @@ -473,8 +478,9 @@ NS_ASSUME_NONNULL_BEGIN BOOL hasLocalMutations = (mutationResult == nil); NSArray *transformResults = - mutationResult ? mutationResult.transformResults - : [self localTransformResultsWithWriteTime:localWriteTime]; + mutationResult + ? mutationResult.transformResults + : [self localTransformResultsWithPreviousDocument:baseDoc writeTime:localWriteTime]; FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults]; return [FSTDocument documentWithData:newData key:doc.key @@ -486,16 +492,26 @@ NS_ASSUME_NONNULL_BEGIN * Creates an array of "transform results" (a transform result is a field value representing the * result of applying a transform) for use when applying an FSTTransformMutation locally. * + * @param previousDocument The document prior to applying this mutation batch. * @param localWriteTime The local time of the transform mutation (used to generate * FSTServerTimestampValues). * @return The transform results array. */ -- (NSArray *)localTransformResultsWithWriteTime:(FSTTimestamp *)localWriteTime { +- (NSArray *) +localTransformResultsWithPreviousDocument:(FSTMaybeDocument *_Nullable)previousDocument + writeTime:(FSTTimestamp *)localWriteTime { NSMutableArray *transformResults = [NSMutableArray array]; for (FSTFieldTransform *fieldTransform in self.fieldTransforms) { if ([fieldTransform.transform isKindOfClass:[FSTServerTimestampTransform class]]) { - [transformResults addObject:[FSTServerTimestampValue - serverTimestampValueWithLocalWriteTime:localWriteTime]]; + FSTFieldValue *previousValue = nil; + + if (previousDocument && [previousDocument isMemberOfClass:[FSTDocument class]]) { + previousValue = [((FSTDocument *)previousDocument) fieldForPath:fieldTransform.path]; + } + + [transformResults + addObject:[FSTServerTimestampValue serverTimestampValueWithLocalWriteTime:localWriteTime + previousValue:previousValue]]; } else { FSTFail(@"Encountered unknown transform: %@", fieldTransform); } @@ -552,6 +568,7 @@ NS_ASSUME_NONNULL_BEGIN } - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc + baseDoc:(FSTMaybeDocument *_Nullable)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime mutationResult:(FSTMutationResult *_Nullable)mutationResult { if (mutationResult) { diff --git a/Firestore/Source/Model/FSTMutationBatch.m b/Firestore/Source/Model/FSTMutationBatch.m index 3677908..1d16de5 100644 --- a/Firestore/Source/Model/FSTMutationBatch.m +++ b/Firestore/Source/Model/FSTMutationBatch.m @@ -71,6 +71,7 @@ const FSTBatchID kFSTBatchIDUnknown = -1; mutationBatchResult:(FSTMutationBatchResult *_Nullable)mutationBatchResult { FSTAssert(!maybeDoc || [maybeDoc.key isEqualToKey:documentKey], @"applyTo: key %@ doesn't match maybeDoc key %@", documentKey, maybeDoc.key); + FSTMaybeDocument *baseDoc = maybeDoc; if (mutationBatchResult) { FSTAssert(mutationBatchResult.mutationResults.count == self.mutations.count, @"Mismatch between mutations length (%lu) and results length (%lu)", @@ -83,6 +84,7 @@ const FSTBatchID kFSTBatchIDUnknown = -1; FSTMutationResult *_Nullable mutationResult = mutationBatchResult.mutationResults[i]; if ([mutation.key isEqualToKey:documentKey]) { maybeDoc = [mutation applyTo:maybeDoc + baseDoc:baseDoc localWriteTime:self.localWriteTime mutationResult:mutationResult]; } diff --git a/Firestore/Source/Public/FIRDocumentSnapshot.h b/Firestore/Source/Public/FIRDocumentSnapshot.h index 3e67c25..f1ae6d8 100644 --- a/Firestore/Source/Public/FIRDocumentSnapshot.h +++ b/Firestore/Source/Public/FIRDocumentSnapshot.h @@ -21,6 +21,49 @@ NS_ASSUME_NONNULL_BEGIN +/** + * Controls the return value for server timestamps that have not yet been set to + * their final value. + */ +typedef NS_ENUM(NSInteger, FIRServerTimestampBehavior) { + /** + * Return a local estimates for `FieldValue.serverTimestamp()` + * fields that have not yet been set to their final value. This estimate will + * likely differ from the final value and may cause these pending values to + * change once the server result becomes available. + */ + FIRServerTimestampBehaviorEstimate, + + /** + * Return the previous value for `FieldValue.serverTimestamp()` fields that + * have not yet been set to their final value. + */ + FIRServerTimestampBehaviorPrevious, +}; + +/** + * Options that configure how data is retrieved from a `DocumentSnapshot` + * (e.g. the desired behavior for server timestamps that have not yet been set + * to their final value). + */ +NS_SWIFT_NAME(SnapshotOptions) +@interface FIRSnapshotOptions : NSObject + +/** */ +- (instancetype)init __attribute__((unavailable("FIRSnapshotOptions cannot be created directly."))); + +/** + * If set, controls the return value for `FieldValue.serverTimestamp()` + * fields that have not yet been set to their final value. + * + * If omitted, `NSNull` will be returned by default. + * + * @return The created `FIRSnapshotOptions` object. + */ ++ (instancetype)setServerTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior; + +@end + /** * A `FIRDocumentSnapshot` contains data read from a document in your Firestore database. The data * can be extracted with the `data` property or by using subscript syntax to access a specific @@ -48,10 +91,55 @@ NS_SWIFT_NAME(DocumentSnapshot) /** * Retrieves all fields in the document as an `NSDictionary`. * + * Server-provided timestamps that have not yet been set to their final value + * will be returned as `NSNull`. You can use `dataWithOptions()` to configure this + * behavior. + * * @return An `NSDictionary` containing all fields in the document. */ - (NSDictionary *)data; +/** + * Retrieves all fields in the document as a `Dictionary`. + * + * @param options `SnapshotOptions` to configure how data is returned from + * the snapshot (e.g. the desired behavior for server timestamps that have not + * yet been set to their final value). + * @return A `Dictionary` containing all fields in the document. + */ +- (NSDictionary *)dataWithOptions:(FIRSnapshotOptions *)options; + +/** + * Retrieves a specific field from the document. + * + * The timestamps that have not yet been set to their final value + * will be returned as `NSNull`. The can use `get(_:options:)` to + * configure this behavior. + * + * @param field The field to retrieve. + * @return The value contained in the field or `nil` if the field doesn't exist. + */ +- (nullable id)valueForField:(id)field NS_SWIFT_NAME(get(_:)); + +/** + * Retrieves a specific field from the document. + * + * The timestamps that have not yet been set to their final value + * will be returned as `NSNull`. The can use `get(_:options:)` to + * configure this behavior. + * + * @param field The field to retrieve. + * @param options `SnapshotOptions` to configure how data is returned from + * the snapshot (e.g. the desired behavior for server timestamps that have not + * yet been set to their final value). + * @return The value contained in the field or `nil` if the field doesn't exist. + */ +// clang-format off +- (nullable id)valueForField:(id)field + options:(FIRSnapshotOptions *)options + NS_SWIFT_NAME(get(_:options:)); +// clang-format on + /** * Retrieves a specific field from the document. * -- cgit v1.2.3 From 904a141217d6ab84f1759dc504a9329bd74adf68 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Thu, 7 Dec 2017 15:53:54 -0800 Subject: Addressing all review comments --- .../Integration/API/FIRServerTimestampTests.m | 136 ++++++++++----------- Firestore/Example/Tests/Model/FSTMutationTests.m | 31 +++-- Firestore/Source/API/FIRDocumentSnapshot.m | 18 +-- Firestore/Source/API/FIRSnapshotOptions+Internal.h | 6 +- Firestore/Source/API/FIRSnapshotOptions.m | 28 ++--- Firestore/Source/Model/FSTFieldValue.h | 15 ++- Firestore/Source/Model/FSTFieldValue.m | 32 +++-- Firestore/Source/Model/FSTMutation.h | 12 +- Firestore/Source/Model/FSTMutation.m | 53 ++++---- Firestore/Source/Model/FSTMutationBatch.m | 2 +- Firestore/Source/Public/FIRDocumentSnapshot.h | 10 +- 11 files changed, 164 insertions(+), 179 deletions(-) diff --git a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m index 5b210d7..8d931ec 100644 --- a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m +++ b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m @@ -16,16 +16,14 @@ @import FirebaseFirestore; -#import #import -#import #import #import -#import "Firestore/Source/API/FIRFirestore+Internal.h" - #import "Firestore/Example/Tests/Util/FSTEventAccumulator.h" #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h" +#import "Firestore/Source/API/FIRFirestore+Internal.h" +#import "Firestore/Source/Core/FSTFirestoreClient.h" @interface FIRServerTimestampTests : FSTIntegrationTestCase @end @@ -48,17 +46,17 @@ id _listenerRegistration; // Snapshot options that return the previous value for pending server timestamps. - FIRSnapshotOptions *_previousValue; - FIRSnapshotOptions *_estimateValue; + FIRSnapshotOptions *_returnPreviousValue; + FIRSnapshotOptions *_returnEstimatedValue; } - (void)setUp { [super setUp]; - _previousValue = - [FIRSnapshotOptions setServerTimestampBehavior:FIRServerTimestampBehaviorPrevious]; - _estimateValue = - [FIRSnapshotOptions setServerTimestampBehavior:FIRServerTimestampBehaviorEstimate]; + _returnPreviousValue = + [FIRSnapshotOptions serverTimestampBehavior:FIRServerTimestampBehaviorPrevious]; + _returnEstimatedValue = + [FIRSnapshotOptions serverTimestampBehavior:FIRServerTimestampBehaviorEstimate]; // Data written in tests via set. _setData = @{ @@ -92,7 +90,7 @@ #pragma mark - Test Helpers /** Returns the expected data, with the specified timestamp substituted in. */ -- (NSDictionary *)expectedDataWithTimestamp:(id _Nullable)timestamp { +- (NSDictionary *)expectedDataWithTimestamp:(nullable id)timestamp { return @{ @"a" : @42, @"when" : timestamp, @"deep" : @{@"when" : timestamp} }; } @@ -105,55 +103,53 @@ /** Waits for a snapshot with local writes. */ - (FIRDocumentSnapshot *)waitForLocalEvent { - return [_accumulator awaitEventWithName:@"Local event."]; + FIRDocumentSnapshot *snapshot = [_accumulator awaitEventWithName:@"Local event."]; + XCTAssertTrue(snapshot.metadata.hasPendingWrites); + return snapshot; +} + +/** Waits for a snapshot with that has no pending writes */ +- (FIRDocumentSnapshot *)waitForRemoteEvent { + FIRDocumentSnapshot *snapshot = [_accumulator awaitEventWithName:@"Remote event."]; + XCTAssertFalse(snapshot.metadata.hasPendingWrites); + return snapshot; } -/** Waits for a snapshot containing _setData but with NSNull for the timestamps. */ -- (FIRDocumentSnapshot *)waitForLocalEventWithNullTimestamps { - FIRDocumentSnapshot *localSnap = [self waitForLocalEvent]; - XCTAssertEqualObjects(localSnap.data, [self expectedDataWithTimestamp:[NSNull null]]); - return localSnap; +/** Verifies a snapshot containing _setData but with NSNull for the timestamps. */ +- (void)verifySnapshotWithNullTimestamps:(FIRDocumentSnapshot *)snapshot { + XCTAssertEqualObjects(snapshot.data, [self expectedDataWithTimestamp:[NSNull null]]); } -/** Waits for a snapshot containing _setData but with a local estimate for the timestamps. */ -- (FIRDocumentSnapshot *)waitForLocalEventWithEstimatedTimestamps { - FIRDocumentSnapshot *localSnap = [self waitForLocalEvent]; - id timestamp = [localSnap valueForField:@"when" options:_estimateValue]; +/** Verifies a snapshot containing _setData but with a local estimate for the timestamps. */ +- (void)verifySnapshotWithEstimatedTimestamps:(FIRDocumentSnapshot *)snapshot { + id timestamp = [snapshot valueForField:@"when" options:_returnEstimatedValue]; XCTAssertTrue([timestamp isKindOfClass:[NSDate class]]); - XCTAssertEqualObjects([localSnap dataWithOptions:_estimateValue], + XCTAssertEqualObjects([snapshot dataWithOptions:_returnEstimatedValue], [self expectedDataWithTimestamp:timestamp]); - return localSnap; } -/** Waits for a snapshot containing _setData but using the previous field value for the timestamps. */ -- (FIRDocumentSnapshot *)waitForLocalEventWithPreviousDataFromSnapshot: - (FIRDocumentSnapshot *_Nullable)previousSnapshot { - FIRDocumentSnapshot *localSnap = [self waitForLocalEvent]; - +/** Verifies a snapshot containing _setData but using the previous field value for the timestamps. */ +- (void)verifySnapshot:(FIRDocumentSnapshot *)snapshot + withDataFromPreviousSnapshot:(nullable FIRDocumentSnapshot *)previousSnapshot { if (previousSnapshot == nil) { - XCTAssertEqualObjects([localSnap dataWithOptions:_previousValue], + XCTAssertEqualObjects([snapshot dataWithOptions:_returnPreviousValue], [self expectedDataWithTimestamp:[NSNull null]]); } else { - XCTAssertEqualObjects([localSnap dataWithOptions:_previousValue], + XCTAssertEqualObjects([snapshot dataWithOptions:_returnPreviousValue], [self expectedDataWithTimestamp:previousSnapshot[@"when"]]); } - - return localSnap; } /** Waits for a snapshot containing _setData but with resolved server timestamps. */ -- (FIRDocumentSnapshot *)waitForRemoteEvent { - // server event should have a resolved timestamp; verify it. - FIRDocumentSnapshot *remoteSnap = [_accumulator awaitEventWithName:@"Remote event"]; - XCTAssertTrue(remoteSnap.exists); - NSDate *when = remoteSnap[@"when"]; +- (id)verifySnapshotWithResolvedTimestamps:(FIRDocumentSnapshot *)snapshot { + XCTAssertTrue(snapshot.exists); + NSDate *when = snapshot[@"when"]; XCTAssertTrue([when isKindOfClass:[NSDate class]]); // Tolerate up to 10 seconds of clock skew between client and server. XCTAssertEqualWithAccuracy(when.timeIntervalSinceNow, 0, 10); // Validate the rest of the document. - XCTAssertEqualObjects(remoteSnap.data, [self expectedDataWithTimestamp:when]); - return remoteSnap; + XCTAssertEqualObjects(snapshot.data, [self expectedDataWithTimestamp:when]); } /** Runs a transaction block. */ @@ -186,85 +182,85 @@ - (void)testServerTimestampsWorkViaSet { [self writeDocumentRef:_docRef data:_setData]; - [self waitForLocalEventWithNullTimestamps]; - [self waitForRemoteEvent]; + [self verifySnapshotWithNullTimestamps:[self waitForLocalEvent]]; + [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; } - (void)testServerTimestampsWorkViaUpdate { [self writeInitialData]; [self updateDocumentRef:_docRef data:_updateData]; - [self waitForLocalEventWithNullTimestamps]; - [self waitForRemoteEvent]; + [self verifySnapshotWithNullTimestamps:[self waitForLocalEvent]]; + [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; } - (void)testServerTimestampsWithEstimatedValue { [self writeDocumentRef:_docRef data:_setData]; - [self waitForLocalEventWithEstimatedTimestamps]; - [self waitForRemoteEvent]; + [self verifySnapshotWithEstimatedTimestamps:[self waitForLocalEvent]]; + [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; } - (void)testServerTimestampsWithPreviousValue { [self writeDocumentRef:_docRef data:_setData]; - [self waitForLocalEventWithPreviousDataFromSnapshot:nil]; + [self verifySnapshot:[self waitForLocalEvent] withDataFromPreviousSnapshot:nil]; FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent]; [_docRef updateData:_updateData]; - [self waitForLocalEventWithPreviousDataFromSnapshot:remoteSnapshot]; + [self verifySnapshot:[self waitForLocalEvent] withDataFromPreviousSnapshot:remoteSnapshot]; - [self waitForRemoteEvent]; + [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; } - (void)testServerTimestampsWithPreviousValueOfDifferentType { [self writeDocumentRef:_docRef data:_setData]; - [self waitForLocalEvent]; - [self waitForRemoteEvent]; + [self verifySnapshot:[self waitForLocalEvent] withDataFromPreviousSnapshot:nil]; + [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}]; FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent]; XCTAssertEqualObjects([localSnapshot valueForField:@"a"], [NSNull null]); - XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_previousValue], @42); - XCTAssertTrue( - [[localSnapshot valueForField:@"a" options:_estimateValue] isKindOfClass:[NSDate class]]); + XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @42); + XCTAssertTrue([[localSnapshot valueForField:@"a" options:_returnEstimatedValue] + isKindOfClass:[NSDate class]]); - FIRDocumentSnapshot *remoteSnapshot = [_accumulator awaitEventWithName:@"Remote event"]; + FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent]; XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[NSDate class]]); - XCTAssertTrue( - [[remoteSnapshot valueForField:@"a" options:_previousValue] isKindOfClass:[NSDate class]]); - XCTAssertTrue( - [[remoteSnapshot valueForField:@"a" options:_estimateValue] isKindOfClass:[NSDate class]]); + XCTAssertTrue([[remoteSnapshot valueForField:@"a" options:_returnPreviousValue] + isKindOfClass:[NSDate class]]); + XCTAssertTrue([[remoteSnapshot valueForField:@"a" options:_returnEstimatedValue] + isKindOfClass:[NSDate class]]); } - (void)testServerTimestampsWithConsecutiveUpdates { [self writeDocumentRef:_docRef data:_setData]; - [self waitForLocalEvent]; - [self waitForRemoteEvent]; + [self verifySnapshot:[self waitForLocalEvent] withDataFromPreviousSnapshot:nil]; + [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; [self disableNetwork]; [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}]; FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent]; - XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_previousValue], @42); + XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @42); [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}]; localSnapshot = [self waitForLocalEvent]; - XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_previousValue], @42); + XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @42); [self enableNetwork]; - FIRDocumentSnapshot *remoteSnapshot = [_accumulator awaitEventWithName:@"Remote event"]; + FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent]; XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[NSDate class]]); } - (void)testServerTimestampsPreviousValueFromLocalMutation { [self writeDocumentRef:_docRef data:_setData]; - [self waitForLocalEvent]; - [self waitForRemoteEvent]; + [self verifySnapshot:[self waitForLocalEvent] withDataFromPreviousSnapshot:nil]; + [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; [self disableNetwork]; [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}]; FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent]; - XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_previousValue], @42); + XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @42); [_docRef updateData:@{ @"a" : @1337 }]; localSnapshot = [self waitForLocalEvent]; @@ -272,11 +268,11 @@ [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}]; localSnapshot = [self waitForLocalEvent]; - XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_previousValue], @1337); + XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @1337); [self enableNetwork]; - FIRDocumentSnapshot *remoteSnapshot = [_accumulator awaitEventWithName:@"Remote event"]; + FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent]; XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[NSDate class]]); } @@ -285,7 +281,7 @@ [transaction setData:_setData forDocument:_docRef]; }]; - [self waitForRemoteEvent]; + [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; } - (void)testServerTimestampsWorkViaTransactionUpdate { @@ -293,7 +289,7 @@ [self runTransactionBlock:^(FIRTransaction *transaction) { [transaction updateData:_updateData forDocument:_docRef]; }]; - [self waitForRemoteEvent]; + [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; } - (void)testServerTimestampsFailViaUpdateOnNonexistentDocument { diff --git a/Firestore/Example/Tests/Model/FSTMutationTests.m b/Firestore/Example/Tests/Model/FSTMutationTests.m index 4c5a4ad..f5131f5 100644 --- a/Firestore/Example/Tests/Model/FSTMutationTests.m +++ b/Firestore/Example/Tests/Model/FSTMutationTests.m @@ -42,7 +42,7 @@ FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO); FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"bar" : @"bar-value"}); - FSTMaybeDocument *setDoc = [set applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; + FSTMaybeDocument *setDoc = [set applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; NSDictionary *expectedData = @{@"bar" : @"bar-value"}; XCTAssertEqualObjects(setDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES)); @@ -54,7 +54,8 @@ FSTMutation *patch = FSTTestPatchMutation(@"collection/key", @{@"foo.bar" : @"new-bar-value"}, nil); - FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; + FSTMaybeDocument *patchedDoc = + [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; NSDictionary *expectedData = @{ @"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value" }; XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES)); @@ -70,7 +71,8 @@ fieldMask:mask value:[FSTObjectValue objectValue] precondition:[FSTPrecondition none]]; - FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; + FSTMaybeDocument *patchedDoc = + [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; NSDictionary *expectedData = @{ @"foo" : @{@"baz" : @"baz-value"} }; XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES)); @@ -82,7 +84,8 @@ FSTMutation *patch = FSTTestPatchMutation(@"collection/key", @{@"foo.bar" : @"new-bar-value"}, nil); - FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; + FSTMaybeDocument *patchedDoc = + [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; NSDictionary *expectedData = @{ @"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value" }; XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES)); @@ -91,7 +94,8 @@ - (void)testPatchingDeletedDocumentsDoesNothing { FSTMaybeDocument *baseDoc = FSTTestDeletedDoc(@"collection/key", 0); FSTMutation *patch = FSTTestPatchMutation(@"collection/key", @{@"foo" : @"bar"}, nil); - FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; + FSTMaybeDocument *patchedDoc = + [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; XCTAssertEqualObjects(patchedDoc, baseDoc); } @@ -101,7 +105,7 @@ FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @[ @"foo.bar" ]); FSTMaybeDocument *transformedDoc = - [transform applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; + [transform applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; // Server timestamps aren't parsed, so we manually insert it. FSTObjectValue *expectedData = FSTTestObjectValue( @@ -132,7 +136,7 @@ transformResults:@[ [FSTTimestampValue timestampValue:_timestamp] ]]; FSTMaybeDocument *transformedDoc = [transform applyTo:baseDoc - baseDoc:baseDoc + baseDocument:baseDoc localWriteTime:_timestamp mutationResult:mutationResult]; @@ -147,7 +151,8 @@ FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO); FSTMutation *mutation = FSTTestDeleteMutation(@"collection/key"); - FSTMaybeDocument *result = [mutation applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp]; + FSTMaybeDocument *result = + [mutation applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; XCTAssertEqualObjects(result, FSTTestDeletedDoc(@"collection/key", 0)); } @@ -158,8 +163,10 @@ FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"foo" : @"new-bar"}); FSTMutationResult *mutationResult = [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(4) transformResults:nil]; - FSTMaybeDocument *setDoc = - [set applyTo:baseDoc baseDoc:baseDoc localWriteTime:_timestamp mutationResult:mutationResult]; + FSTMaybeDocument *setDoc = [set applyTo:baseDoc + baseDocument:baseDoc + localWriteTime:_timestamp + mutationResult:mutationResult]; NSDictionary *expectedData = @{@"foo" : @"new-bar"}; XCTAssertEqualObjects(setDoc, FSTTestDoc(@"collection/key", 0, expectedData, NO)); @@ -173,7 +180,7 @@ FSTMutationResult *mutationResult = [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(4) transformResults:nil]; FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc - baseDoc:baseDoc + baseDocument:baseDoc localWriteTime:_timestamp mutationResult:mutationResult]; @@ -186,7 +193,7 @@ FSTMutationResult *mutationResult = \ [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(0) transformResults:nil]; \ FSTMaybeDocument *actual = [mutation applyTo:base \ - baseDoc:base \ + baseDocument:base \ localWriteTime:_timestamp \ mutationResult:mutationResult]; \ XCTAssertEqualObjects(actual, expected); \ diff --git a/Firestore/Source/API/FIRDocumentSnapshot.m b/Firestore/Source/API/FIRDocumentSnapshot.m index 0d60033..e4e75c6 100644 --- a/Firestore/Source/API/FIRDocumentSnapshot.m +++ b/Firestore/Source/API/FIRDocumentSnapshot.m @@ -116,7 +116,7 @@ NS_ASSUME_NONNULL_BEGIN } return [self convertedObject:[self.internalDocument data] - options:[self convertedSnapshotOptions:options]]; + options:[FSTFieldValueOptions fieldValueOptions:options]]; } - (nullable id)valueForField:(id)field { @@ -135,7 +135,7 @@ NS_ASSUME_NONNULL_BEGIN } FSTFieldValue *fieldValue = [[self.internalDocument data] valueForPath:fieldPath.internalValue]; - return [self convertedValue:fieldValue options:[self convertedSnapshotOptions:options]]; + return [self convertedValue:fieldValue options:[FSTFieldValueOptions fieldValueOptions:options]]; } - (nullable id)objectForKeyedSubscript:(id)key { @@ -187,20 +187,6 @@ NS_ASSUME_NONNULL_BEGIN return result; } -/** Create a field value option from a snapshot option. */ -- (FSTFieldValueOptions *)convertedSnapshotOptions:(FIRSnapshotOptions *)snapshotOptions { - switch (snapshotOptions.serverTimestampBehavior) { - case FIRServerTimestampBehaviorEstimate: - return [[FSTFieldValueOptions alloc] - initWithServerTimestampBehavior:FSTServerTimestampBehaviorEstimate]; - case FIRServerTimestampBehaviorPrevious: - return [[FSTFieldValueOptions alloc] - initWithServerTimestampBehavior:FSTServerTimestampBehaviorPrevious]; - default: - return [FSTFieldValueOptions defaultOptions]; - } -} - @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/API/FIRSnapshotOptions+Internal.h b/Firestore/Source/API/FIRSnapshotOptions+Internal.h index de2aaa7..64e7dbc 100644 --- a/Firestore/Source/API/FIRSnapshotOptions+Internal.h +++ b/Firestore/Source/API/FIRSnapshotOptions+Internal.h @@ -18,7 +18,7 @@ #import -@class FIRSnapshotOptions; +#import "Firestore/Source/Model/FSTFieldValue.h" NS_ASSUME_NONNULL_BEGIN @@ -28,10 +28,10 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)defaultOptions; /* Initializes a new instance with the specified server timestamp behavior. */ -- (instancetype)initWithServerTimestampBehavior:(int)serverTimestampBehavior; +- (instancetype)initWithServerTimestampBehavior:(FSTServerTimestampBehavior)serverTimestampBehavior; /* Returns the server timestamp behavior. Returns -1 if no behavior is specified. */ -- (int)serverTimestampBehavior; +- (FSTServerTimestampBehavior)serverTimestampBehavior; @end diff --git a/Firestore/Source/API/FIRSnapshotOptions.m b/Firestore/Source/API/FIRSnapshotOptions.m index adc4a65..71d9d51 100644 --- a/Firestore/Source/API/FIRSnapshotOptions.m +++ b/Firestore/Source/API/FIRSnapshotOptions.m @@ -16,34 +16,20 @@ #import "FIRDocumentSnapshot.h" -#import "FIRDocumentSnapshot+Internal.h" -#import "FSTAssert.h" -#import "Firestore/Source/API/FIRDocumentReference+Internal.h" -#import "Firestore/Source/API/FIRFieldPath+Internal.h" -#import "Firestore/Source/API/FIRFirestore+Internal.h" -#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" #import "Firestore/Source/API/FIRSnapshotOptions+Internal.h" -#import "Firestore/Source/Model/FSTDatabaseID.h" -#import "Firestore/Source/Model/FSTDocument.h" -#import "Firestore/Source/Model/FSTDocumentKey.h" -#import "Firestore/Source/Model/FSTFieldValue.h" -#import "Firestore/Source/Model/FSTPath.h" -#import "Firestore/Source/Util/FSTUsageValidation.h" +#import "Firestore/Source/Util/FSTAssert.h" NS_ASSUME_NONNULL_BEGIN -/** The default server timestamp behavior (returning NSNull for pending timestamps). */ -static const int kFIRServerTimestampBehaviorDefault = -1; - @interface FIRSnapshotOptions () -@property(nonatomic) int serverTimestampBehavior; +@property(nonatomic) FSTServerTimestampBehavior serverTimestampBehavior; @end @implementation FIRSnapshotOptions -- (instancetype)initWithServerTimestampBehavior:(int)serverTimestampBehavior { +- (instancetype)initWithServerTimestampBehavior:(FSTServerTimestampBehavior)serverTimestampBehavior { self = [super init]; if (self) { @@ -59,20 +45,20 @@ static const int kFIRServerTimestampBehaviorDefault = -1; dispatch_once(&onceToken, ^{ sharedInstance = [[FIRSnapshotOptions alloc] - initWithServerTimestampBehavior:kFIRServerTimestampBehaviorDefault]; + initWithServerTimestampBehavior:FSTServerTimestampBehaviorDefault]; }); return sharedInstance; } -+ (instancetype)setServerTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior { ++ (instancetype)serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior { switch (serverTimestampBehavior) { case FIRServerTimestampBehaviorEstimate: return [[FIRSnapshotOptions alloc] - initWithServerTimestampBehavior:FIRServerTimestampBehaviorEstimate]; + initWithServerTimestampBehavior:FSTServerTimestampBehaviorEstimate]; case FIRServerTimestampBehaviorPrevious: return [[FIRSnapshotOptions alloc] - initWithServerTimestampBehavior:FIRServerTimestampBehaviorPrevious]; + initWithServerTimestampBehavior:FSTServerTimestampBehaviorPrevious]; default: FSTFail(@"Encountered unknown server timestamp behavior: %d", (int)serverTimestampBehavior); } diff --git a/Firestore/Source/Model/FSTFieldValue.h b/Firestore/Source/Model/FSTFieldValue.h index 969b3b0..f13f93f 100644 --- a/Firestore/Source/Model/FSTFieldValue.h +++ b/Firestore/Source/Model/FSTFieldValue.h @@ -24,6 +24,7 @@ @class FSTTimestamp; @class FSTFieldValueOptions; @class FIRGeoPoint; +@class FIRSnapshotOptions; NS_ASSUME_NONNULL_BEGIN @@ -51,17 +52,19 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { /** Holds properties that define field value deserialization options. */ @interface FSTFieldValueOptions : NSObject -@property(nonatomic, readonly) FSTServerTimestampBehavior serverTimestampBehavior; +@property(nonatomic, readonly, assign) FSTServerTimestampBehavior serverTimestampBehavior; - (instancetype)init NS_UNAVAILABLE; -/** Creates a FSTFieldValueOptions instance that specifies deserialization behavior for pending - * server timestamps. */ +/** + * Creates a FSTFieldValueOptions instance that specifies deserialization behavior for pending + * server timestamps. + */ - (instancetype)initWithServerTimestampBehavior:(FSTServerTimestampBehavior)serverTimestampBehavior NS_DESIGNATED_INITIALIZER; -/** Returns the default deserialization options. */ -+ (instancetype)defaultOptions; +/** Creates FSTFieldValueOptions from FIRSnapshotOptions. */ ++ (instancetype)fieldValueOptions:(FIRSnapshotOptions *)value; @end @@ -114,7 +117,7 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { */ @interface FSTNullValue : FSTFieldValue + (instancetype)nullValue; -- (id)valueWithOptions:(FSTFieldValueOptions *)options; +- (NSNull *)valueWithOptions:(FSTFieldValueOptions *)options; @end /** diff --git a/Firestore/Source/Model/FSTFieldValue.m b/Firestore/Source/Model/FSTFieldValue.m index 65478ce..91d6c43 100644 --- a/Firestore/Source/Model/FSTFieldValue.m +++ b/Firestore/Source/Model/FSTFieldValue.m @@ -17,6 +17,7 @@ #import "Firestore/Source/Model/FSTFieldValue.h" #import "Firestore/Source/API/FIRGeoPoint+Internal.h" +#import "Firestore/Source/API/FIRSnapshotOptions+Internal.h" #import "Firestore/Source/Core/FSTTimestamp.h" #import "Firestore/Source/Model/FSTDatabaseID.h" #import "Firestore/Source/Model/FSTDocumentKey.h" @@ -31,6 +32,22 @@ NS_ASSUME_NONNULL_BEGIN @implementation FSTFieldValueOptions ++ (instancetype)fieldValueOptions:(FIRSnapshotOptions *)options { + if (options.serverTimestampBehavior == FSTServerTimestampBehaviorDefault) { + static FSTFieldValueOptions *defaultInstance = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + defaultInstance = [[FSTFieldValueOptions alloc] + initWithServerTimestampBehavior:FSTServerTimestampBehaviorDefault]; + }); + return defaultInstance; + } else { + return [[FSTFieldValueOptions alloc] + initWithServerTimestampBehavior:options.serverTimestampBehavior]; + } +} + - (instancetype)initWithServerTimestampBehavior: (FSTServerTimestampBehavior)serverTimestampBehavior { self = [super init]; @@ -41,18 +58,6 @@ NS_ASSUME_NONNULL_BEGIN return self; } -+ (instancetype)defaultOptions { - static FSTFieldValueOptions *sharedInstance = nil; - static dispatch_once_t onceToken; - - dispatch_once(&onceToken, ^{ - sharedInstance = [[FSTFieldValueOptions alloc] - initWithServerTimestampBehavior:FSTServerTimestampBehaviorDefault]; - }); - - return sharedInstance; -} - @end #pragma mark - FSTFieldValue @@ -68,7 +73,8 @@ NS_ASSUME_NONNULL_BEGIN } - (id)value { - return [self valueWithOptions:[FSTFieldValueOptions defaultOptions]]; + return [self valueWithOptions:[FSTFieldValueOptions + fieldValueOptions:[FIRSnapshotOptions defaultOptions]]]; } - (id)valueWithOptions:(FSTFieldValueOptions *)options { diff --git a/Firestore/Source/Model/FSTMutation.h b/Firestore/Source/Model/FSTMutation.h index b2e7f5e..7c5f6de 100644 --- a/Firestore/Source/Model/FSTMutation.h +++ b/Firestore/Source/Model/FSTMutation.h @@ -198,18 +198,18 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) { * apply the transform if the prior mutation resulted in an FSTDocument (always true for an * FSTSetMutation, but not necessarily for an FSTPatchMutation). */ -- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc - baseDoc:(FSTMaybeDocument *_Nullable)baseDoc +- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc + baseDocument:(nullable FSTMaybeDocument *)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime - mutationResult:(FSTMutationResult *_Nullable)mutationResult; + mutationResult:(nullable FSTMutationResult *)mutationResult; /** * A helper version of applyTo for applying mutations locally (without a mutation result from the * backend). */ -- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc - baseDoc:(FSTMaybeDocument *_Nullable)baseDoc - localWriteTime:(FSTTimestamp *)localWriteTime; +- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc + baseDocument:(nullable FSTMaybeDocument *)baseDoc + localWriteTime:(nullable FSTTimestamp *)localWriteTime; @property(nonatomic, strong, readonly) FSTDocumentKey *key; diff --git a/Firestore/Source/Model/FSTMutation.m b/Firestore/Source/Model/FSTMutation.m index 2685990..375e289 100644 --- a/Firestore/Source/Model/FSTMutation.m +++ b/Firestore/Source/Model/FSTMutation.m @@ -236,17 +236,18 @@ NS_ASSUME_NONNULL_BEGIN return self; } -- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc - baseDoc:(FSTMaybeDocument *_Nullable)baseDoc +- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc + baseDocument:(nullable FSTMaybeDocument *)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime - mutationResult:(FSTMutationResult *_Nullable)mutationResult { + mutationResult:(nullable FSTMutationResult *)mutationResult { @throw FSTAbstractMethodException(); // NOLINT } -- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc - baseDoc:(FSTMaybeDocument *_Nullable)baseDoc - localWriteTime:(FSTTimestamp *)localWriteTime { - return [self applyTo:maybeDoc baseDoc:baseDoc localWriteTime:localWriteTime mutationResult:nil]; +- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc + baseDocument:(nullable FSTMaybeDocument *)baseDoc + localWriteTime:(nullable FSTTimestamp *)localWriteTime { + return + [self applyTo:maybeDoc baseDocument:baseDoc localWriteTime:localWriteTime mutationResult:nil]; } @end @@ -289,10 +290,10 @@ NS_ASSUME_NONNULL_BEGIN return result; } -- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc - baseDoc:(FSTMaybeDocument *_Nullable)baseDoc +- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc + baseDocument:(nullable FSTMaybeDocument *)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime - mutationResult:(FSTMutationResult *_Nullable)mutationResult { + mutationResult:(nullable FSTMutationResult *)mutationResult { if (mutationResult) { FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTSetMutation."); } @@ -365,10 +366,10 @@ NS_ASSUME_NONNULL_BEGIN self.key, self.fieldMask, self.value, self.precondition]; } -- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc - baseDoc:(FSTMaybeDocument *_Nullable)baseDoc +- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc + baseDocument:(nullable FSTMaybeDocument *)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime - mutationResult:(FSTMutationResult *_Nullable)mutationResult { + mutationResult:(nullable FSTMutationResult *)mutationResult { if (mutationResult) { FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTPatchMutation."); } @@ -455,10 +456,10 @@ NS_ASSUME_NONNULL_BEGIN self.key, self.fieldTransforms, self.precondition]; } -- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc - baseDoc:(FSTMaybeDocument *_Nullable)baseDoc +- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc + baseDocument:(nullable FSTMaybeDocument *)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime - mutationResult:(FSTMutationResult *_Nullable)mutationResult { + mutationResult:(nullable FSTMutationResult *)mutationResult { if (mutationResult) { FSTAssert(mutationResult.transformResults, @"Transform results missing for FSTTransformMutation."); @@ -480,7 +481,7 @@ NS_ASSUME_NONNULL_BEGIN NSArray *transformResults = mutationResult ? mutationResult.transformResults - : [self localTransformResultsWithPreviousDocument:baseDoc writeTime:localWriteTime]; + : [self localTransformResultsWithBaseDocument:baseDoc writeTime:localWriteTime]; FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults]; return [FSTDocument documentWithData:newData key:doc.key @@ -492,21 +493,21 @@ NS_ASSUME_NONNULL_BEGIN * Creates an array of "transform results" (a transform result is a field value representing the * result of applying a transform) for use when applying an FSTTransformMutation locally. * - * @param previousDocument The document prior to applying this mutation batch. + * @param baseDocument The document prior to applying this mutation batch. * @param localWriteTime The local time of the transform mutation (used to generate * FSTServerTimestampValues). * @return The transform results array. */ -- (NSArray *) -localTransformResultsWithPreviousDocument:(FSTMaybeDocument *_Nullable)previousDocument - writeTime:(FSTTimestamp *)localWriteTime { +- (NSArray *)localTransformResultsWithBaseDocument: + (FSTMaybeDocument *_Nullable)baseDocument + writeTime:(FSTTimestamp *)localWriteTime { NSMutableArray *transformResults = [NSMutableArray array]; for (FSTFieldTransform *fieldTransform in self.fieldTransforms) { if ([fieldTransform.transform isKindOfClass:[FSTServerTimestampTransform class]]) { FSTFieldValue *previousValue = nil; - if (previousDocument && [previousDocument isMemberOfClass:[FSTDocument class]]) { - previousValue = [((FSTDocument *)previousDocument) fieldForPath:fieldTransform.path]; + if ([baseDocument isMemberOfClass:[FSTDocument class]]) { + previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path]; } [transformResults @@ -567,10 +568,10 @@ localTransformResultsWithPreviousDocument:(FSTMaybeDocument *_Nullable)previousD stringWithFormat:@"", self.key, self.precondition]; } -- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc - baseDoc:(FSTMaybeDocument *_Nullable)baseDoc +- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc + baseDocument:(nullable FSTMaybeDocument *)baseDoc localWriteTime:(FSTTimestamp *)localWriteTime - mutationResult:(FSTMutationResult *_Nullable)mutationResult { + mutationResult:(nullable FSTMutationResult *)mutationResult { if (mutationResult) { FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTDeleteMutation."); diff --git a/Firestore/Source/Model/FSTMutationBatch.m b/Firestore/Source/Model/FSTMutationBatch.m index 1d16de5..01adca7 100644 --- a/Firestore/Source/Model/FSTMutationBatch.m +++ b/Firestore/Source/Model/FSTMutationBatch.m @@ -84,7 +84,7 @@ const FSTBatchID kFSTBatchIDUnknown = -1; FSTMutationResult *_Nullable mutationResult = mutationBatchResult.mutationResults[i]; if ([mutation.key isEqualToKey:documentKey]) { maybeDoc = [mutation applyTo:maybeDoc - baseDoc:baseDoc + baseDocument:baseDoc localWriteTime:self.localWriteTime mutationResult:mutationResult]; } diff --git a/Firestore/Source/Public/FIRDocumentSnapshot.h b/Firestore/Source/Public/FIRDocumentSnapshot.h index f1ae6d8..0b72fe4 100644 --- a/Firestore/Source/Public/FIRDocumentSnapshot.h +++ b/Firestore/Source/Public/FIRDocumentSnapshot.h @@ -60,7 +60,7 @@ NS_SWIFT_NAME(SnapshotOptions) * * @return The created `FIRSnapshotOptions` object. */ -+ (instancetype)setServerTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior; ++ (instancetype)serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior; @end @@ -103,8 +103,8 @@ NS_SWIFT_NAME(DocumentSnapshot) * Retrieves all fields in the document as a `Dictionary`. * * @param options `SnapshotOptions` to configure how data is returned from - * the snapshot (e.g. the desired behavior for server timestamps that have not - * yet been set to their final value). + * the snapshot (e.g. the desired behavior for server timestamps that have not + * yet been set to their final value). * @return A `Dictionary` containing all fields in the document. */ - (NSDictionary *)dataWithOptions:(FIRSnapshotOptions *)options; @@ -130,8 +130,8 @@ NS_SWIFT_NAME(DocumentSnapshot) * * @param field The field to retrieve. * @param options `SnapshotOptions` to configure how data is returned from - * the snapshot (e.g. the desired behavior for server timestamps that have not - * yet been set to their final value). + * the snapshot (e.g. the desired behavior for server timestamps that have not + * yet been set to their final value). * @return The value contained in the field or `nil` if the field doesn't exist. */ // clang-format off -- cgit v1.2.3 From bf48b0abc18e098c2579ddd3bcd8694269e2db25 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Thu, 7 Dec 2017 19:54:08 -0800 Subject: Method renames --- .../Integration/API/FIRServerTimestampTests.m | 24 +++++++++++----------- Firestore/Example/Tests/Util/FSTEventAccumulator.m | 8 ++++---- Firestore/Source/API/FIRDocumentSnapshot.m | 4 ++-- Firestore/Source/Model/FSTFieldValue.h | 6 +++--- Firestore/Source/Model/FSTFieldValue.m | 4 ++-- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m index 8d931ec..6a53868 100644 --- a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m +++ b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m @@ -116,12 +116,12 @@ } /** Verifies a snapshot containing _setData but with NSNull for the timestamps. */ -- (void)verifySnapshotWithNullTimestamps:(FIRDocumentSnapshot *)snapshot { +- (void)verifyTimestampsAreNullInSnapshot:(FIRDocumentSnapshot *)snapshot { XCTAssertEqualObjects(snapshot.data, [self expectedDataWithTimestamp:[NSNull null]]); } /** Verifies a snapshot containing _setData but with a local estimate for the timestamps. */ -- (void)verifySnapshotWithEstimatedTimestamps:(FIRDocumentSnapshot *)snapshot { +- (void)verifyTimestampsAreEstimatedInSnapshot:(FIRDocumentSnapshot *)snapshot { id timestamp = [snapshot valueForField:@"when" options:_returnEstimatedValue]; XCTAssertTrue([timestamp isKindOfClass:[NSDate class]]); XCTAssertEqualObjects([snapshot dataWithOptions:_returnEstimatedValue], @@ -129,8 +129,8 @@ } /** Verifies a snapshot containing _setData but using the previous field value for the timestamps. */ -- (void)verifySnapshot:(FIRDocumentSnapshot *)snapshot - withDataFromPreviousSnapshot:(nullable FIRDocumentSnapshot *)previousSnapshot { +- (void)verifyTimestampsInSnapshot:(FIRDocumentSnapshot *)snapshot + fromPreviousSnapshot:(nullable FIRDocumentSnapshot *)previousSnapshot { if (previousSnapshot == nil) { XCTAssertEqualObjects([snapshot dataWithOptions:_returnPreviousValue], [self expectedDataWithTimestamp:[NSNull null]]); @@ -182,37 +182,37 @@ - (void)testServerTimestampsWorkViaSet { [self writeDocumentRef:_docRef data:_setData]; - [self verifySnapshotWithNullTimestamps:[self waitForLocalEvent]]; + [self verifyTimestampsAreNullInSnapshot:[self waitForLocalEvent]]; [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; } - (void)testServerTimestampsWorkViaUpdate { [self writeInitialData]; [self updateDocumentRef:_docRef data:_updateData]; - [self verifySnapshotWithNullTimestamps:[self waitForLocalEvent]]; + [self verifyTimestampsAreNullInSnapshot:[self waitForLocalEvent]]; [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; } - (void)testServerTimestampsWithEstimatedValue { [self writeDocumentRef:_docRef data:_setData]; - [self verifySnapshotWithEstimatedTimestamps:[self waitForLocalEvent]]; + [self verifyTimestampsAreEstimatedInSnapshot:[self waitForLocalEvent]]; [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; } - (void)testServerTimestampsWithPreviousValue { [self writeDocumentRef:_docRef data:_setData]; - [self verifySnapshot:[self waitForLocalEvent] withDataFromPreviousSnapshot:nil]; + [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil]; FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent]; [_docRef updateData:_updateData]; - [self verifySnapshot:[self waitForLocalEvent] withDataFromPreviousSnapshot:remoteSnapshot]; + [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:remoteSnapshot]; [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; } - (void)testServerTimestampsWithPreviousValueOfDifferentType { [self writeDocumentRef:_docRef data:_setData]; - [self verifySnapshot:[self waitForLocalEvent] withDataFromPreviousSnapshot:nil]; + [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil]; [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}]; @@ -232,7 +232,7 @@ - (void)testServerTimestampsWithConsecutiveUpdates { [self writeDocumentRef:_docRef data:_setData]; - [self verifySnapshot:[self waitForLocalEvent] withDataFromPreviousSnapshot:nil]; + [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil]; [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; [self disableNetwork]; @@ -253,7 +253,7 @@ - (void)testServerTimestampsPreviousValueFromLocalMutation { [self writeDocumentRef:_docRef data:_setData]; - [self verifySnapshot:[self waitForLocalEvent] withDataFromPreviousSnapshot:nil]; + [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil]; [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]]; [self disableNetwork]; diff --git a/Firestore/Example/Tests/Util/FSTEventAccumulator.m b/Firestore/Example/Tests/Util/FSTEventAccumulator.m index 9e48ce7..1b1e276 100644 --- a/Firestore/Example/Tests/Util/FSTEventAccumulator.m +++ b/Firestore/Example/Tests/Util/FSTEventAccumulator.m @@ -69,8 +69,8 @@ NS_ASSUME_NONNULL_BEGIN } // Override the valueEventHandler property -- (void (^)(id _Nullable, NSError *))valueEventHandler { - return ^void(id _Nullable value, NSError *error) { +- (void (^)(id _Nullable, NSError *_Nullable))valueEventHandler { + return ^void(id _Nullable value, NSError *_Nullable error) { // We can't store nil in the _events array, but these are still interesting to tests so store // NSNull instead. id event = value ? value : [NSNull null]; @@ -83,8 +83,8 @@ NS_ASSUME_NONNULL_BEGIN } // Override the errorEventHandler property -- (void (^)(NSError *))errorEventHandler { - return ^void(NSError *error) { +- (void (^)(NSError *_Nullable))errorEventHandler { + return ^void(NSError *_Nullable error) { @synchronized(self) { [_events addObject:[NSNull null]]; [self checkFulfilled]; diff --git a/Firestore/Source/API/FIRDocumentSnapshot.m b/Firestore/Source/API/FIRDocumentSnapshot.m index e4e75c6..7722927 100644 --- a/Firestore/Source/API/FIRDocumentSnapshot.m +++ b/Firestore/Source/API/FIRDocumentSnapshot.m @@ -116,7 +116,7 @@ NS_ASSUME_NONNULL_BEGIN } return [self convertedObject:[self.internalDocument data] - options:[FSTFieldValueOptions fieldValueOptions:options]]; + options:[FSTFieldValueOptions optionsForSnapshotOptions:options]]; } - (nullable id)valueForField:(id)field { @@ -135,7 +135,7 @@ NS_ASSUME_NONNULL_BEGIN } FSTFieldValue *fieldValue = [[self.internalDocument data] valueForPath:fieldPath.internalValue]; - return [self convertedValue:fieldValue options:[FSTFieldValueOptions fieldValueOptions:options]]; + return [self convertedValue:fieldValue options:[FSTFieldValueOptions optionsForSnapshotOptions:options]]; } - (nullable id)objectForKeyedSubscript:(id)key { diff --git a/Firestore/Source/Model/FSTFieldValue.h b/Firestore/Source/Model/FSTFieldValue.h index f13f93f..991fb67 100644 --- a/Firestore/Source/Model/FSTFieldValue.h +++ b/Firestore/Source/Model/FSTFieldValue.h @@ -57,14 +57,14 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { - (instancetype)init NS_UNAVAILABLE; /** - * Creates a FSTFieldValueOptions instance that specifies deserialization behavior for pending + * Creates an FSTFieldValueOptions instance that specifies deserialization behavior for pending * server timestamps. */ - (instancetype)initWithServerTimestampBehavior:(FSTServerTimestampBehavior)serverTimestampBehavior NS_DESIGNATED_INITIALIZER; -/** Creates FSTFieldValueOptions from FIRSnapshotOptions. */ -+ (instancetype)fieldValueOptions:(FIRSnapshotOptions *)value; +/** Creates an FSTFieldValueOption instance from FIRSnapshotOptions. */ ++ (instancetype)optionsForSnapshotOptions:(FIRSnapshotOptions *)value; @end diff --git a/Firestore/Source/Model/FSTFieldValue.m b/Firestore/Source/Model/FSTFieldValue.m index 91d6c43..e26bc28 100644 --- a/Firestore/Source/Model/FSTFieldValue.m +++ b/Firestore/Source/Model/FSTFieldValue.m @@ -32,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN @implementation FSTFieldValueOptions -+ (instancetype)fieldValueOptions:(FIRSnapshotOptions *)options { ++ (instancetype)optionsForSnapshotOptions:(FIRSnapshotOptions *)options { if (options.serverTimestampBehavior == FSTServerTimestampBehaviorDefault) { static FSTFieldValueOptions *defaultInstance = nil; static dispatch_once_t onceToken; @@ -74,7 +74,7 @@ NS_ASSUME_NONNULL_BEGIN - (id)value { return [self valueWithOptions:[FSTFieldValueOptions - fieldValueOptions:[FIRSnapshotOptions defaultOptions]]]; + optionsForSnapshotOptions:[FIRSnapshotOptions defaultOptions]]]; } - (id)valueWithOptions:(FSTFieldValueOptions *)options { -- cgit v1.2.3 From 84e7f27a3f1885cfe986b154283bcade52f4408d Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 8 Dec 2017 14:01:13 -0800 Subject: Adding default option --- .../Tests/Integration/API/FIRServerTimestampTests.m | 3 ++- Firestore/Source/API/FIRDocumentSnapshot.m | 3 ++- Firestore/Source/API/FIRSnapshotOptions.m | 9 ++++++--- Firestore/Source/Model/FSTFieldValue.h | 2 +- Firestore/Source/Model/FSTFieldValue.m | 8 ++++---- Firestore/Source/Public/FIRDocumentSnapshot.h | 14 ++++++++++---- 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m index 6a53868..94dcee0 100644 --- a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m +++ b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m @@ -128,7 +128,8 @@ [self expectedDataWithTimestamp:timestamp]); } -/** Verifies a snapshot containing _setData but using the previous field value for the timestamps. */ +/** Verifies a snapshot containing _setData but using the previous field value for the timestamps. + */ - (void)verifyTimestampsInSnapshot:(FIRDocumentSnapshot *)snapshot fromPreviousSnapshot:(nullable FIRDocumentSnapshot *)previousSnapshot { if (previousSnapshot == nil) { diff --git a/Firestore/Source/API/FIRDocumentSnapshot.m b/Firestore/Source/API/FIRDocumentSnapshot.m index 7722927..c4e3040 100644 --- a/Firestore/Source/API/FIRDocumentSnapshot.m +++ b/Firestore/Source/API/FIRDocumentSnapshot.m @@ -135,7 +135,8 @@ NS_ASSUME_NONNULL_BEGIN } FSTFieldValue *fieldValue = [[self.internalDocument data] valueForPath:fieldPath.internalValue]; - return [self convertedValue:fieldValue options:[FSTFieldValueOptions optionsForSnapshotOptions:options]]; + return [self convertedValue:fieldValue + options:[FSTFieldValueOptions optionsForSnapshotOptions:options]]; } - (nullable id)objectForKeyedSubscript:(id)key { diff --git a/Firestore/Source/API/FIRSnapshotOptions.m b/Firestore/Source/API/FIRSnapshotOptions.m index 71d9d51..72ea3cc 100644 --- a/Firestore/Source/API/FIRSnapshotOptions.m +++ b/Firestore/Source/API/FIRSnapshotOptions.m @@ -29,7 +29,8 @@ NS_ASSUME_NONNULL_BEGIN @implementation FIRSnapshotOptions -- (instancetype)initWithServerTimestampBehavior:(FSTServerTimestampBehavior)serverTimestampBehavior { +- (instancetype)initWithServerTimestampBehavior: + (FSTServerTimestampBehavior)serverTimestampBehavior { self = [super init]; if (self) { @@ -44,8 +45,8 @@ NS_ASSUME_NONNULL_BEGIN static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedInstance = [[FIRSnapshotOptions alloc] - initWithServerTimestampBehavior:FSTServerTimestampBehaviorDefault]; + sharedInstance = + [[FIRSnapshotOptions alloc] initWithServerTimestampBehavior:FSTServerTimestampBehaviorNone]; }); return sharedInstance; @@ -59,6 +60,8 @@ NS_ASSUME_NONNULL_BEGIN case FIRServerTimestampBehaviorPrevious: return [[FIRSnapshotOptions alloc] initWithServerTimestampBehavior:FSTServerTimestampBehaviorPrevious]; + case FIRServerTimestampBehaviorNone: + return [FIRSnapshotOptions defaultOptions]; default: FSTFail(@"Encountered unknown server timestamp behavior: %d", (int)serverTimestampBehavior); } diff --git a/Firestore/Source/Model/FSTFieldValue.h b/Firestore/Source/Model/FSTFieldValue.h index 991fb67..fe15865 100644 --- a/Firestore/Source/Model/FSTFieldValue.h +++ b/Firestore/Source/Model/FSTFieldValue.h @@ -44,9 +44,9 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { /** Defines the return value for pending server timestamps. */ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { - FSTServerTimestampBehaviorDefault, FSTServerTimestampBehaviorEstimate, FSTServerTimestampBehaviorPrevious, + FSTServerTimestampBehaviorNone }; /** Holds properties that define field value deserialization options. */ diff --git a/Firestore/Source/Model/FSTFieldValue.m b/Firestore/Source/Model/FSTFieldValue.m index e26bc28..a6326a7 100644 --- a/Firestore/Source/Model/FSTFieldValue.m +++ b/Firestore/Source/Model/FSTFieldValue.m @@ -33,13 +33,13 @@ NS_ASSUME_NONNULL_BEGIN @implementation FSTFieldValueOptions + (instancetype)optionsForSnapshotOptions:(FIRSnapshotOptions *)options { - if (options.serverTimestampBehavior == FSTServerTimestampBehaviorDefault) { + if (options.serverTimestampBehavior == FSTServerTimestampBehaviorNone) { static FSTFieldValueOptions *defaultInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ defaultInstance = [[FSTFieldValueOptions alloc] - initWithServerTimestampBehavior:FSTServerTimestampBehaviorDefault]; + initWithServerTimestampBehavior:FSTServerTimestampBehaviorNone]; }); return defaultInstance; } else { @@ -74,7 +74,7 @@ NS_ASSUME_NONNULL_BEGIN - (id)value { return [self valueWithOptions:[FSTFieldValueOptions - optionsForSnapshotOptions:[FIRSnapshotOptions defaultOptions]]]; + optionsForSnapshotOptions:[FIRSnapshotOptions defaultOptions]]]; } - (id)valueWithOptions:(FSTFieldValueOptions *)options { @@ -470,7 +470,7 @@ NS_ASSUME_NONNULL_BEGIN - (id)valueWithOptions:(FSTFieldValueOptions *)options { switch (options.serverTimestampBehavior) { - case FSTServerTimestampBehaviorDefault: + case FSTServerTimestampBehaviorNone: return [NSNull null]; case FSTServerTimestampBehaviorEstimate: return [self.localWriteTime approximateDateValue]; diff --git a/Firestore/Source/Public/FIRDocumentSnapshot.h b/Firestore/Source/Public/FIRDocumentSnapshot.h index 0b72fe4..e8e73c9 100644 --- a/Firestore/Source/Public/FIRDocumentSnapshot.h +++ b/Firestore/Source/Public/FIRDocumentSnapshot.h @@ -39,6 +39,12 @@ typedef NS_ENUM(NSInteger, FIRServerTimestampBehavior) { * have not yet been set to their final value. */ FIRServerTimestampBehaviorPrevious, + + /** + * Return `NSNull` for `FieldValue.serverTimestamp()` fields that have not yet + * been set to their final value. + */ + FIRServerTimestampBehaviorNone }; /** @@ -102,8 +108,8 @@ NS_SWIFT_NAME(DocumentSnapshot) /** * Retrieves all fields in the document as a `Dictionary`. * - * @param options `SnapshotOptions` to configure how data is returned from - * the snapshot (e.g. the desired behavior for server timestamps that have not + * @param options `SnapshotOptions` to configure how data is returned from the + * snapshot (e.g. the desired behavior for server timestamps that have not * yet been set to their final value). * @return A `Dictionary` containing all fields in the document. */ @@ -129,8 +135,8 @@ NS_SWIFT_NAME(DocumentSnapshot) * configure this behavior. * * @param field The field to retrieve. - * @param options `SnapshotOptions` to configure how data is returned from - * the snapshot (e.g. the desired behavior for server timestamps that have not + * @param options `SnapshotOptions` to configure how data is returned from the + * snapshot (e.g. the desired behavior for server timestamps that have not * yet been set to their final value). * @return The value contained in the field or `nil` if the field doesn't exist. */ -- cgit v1.2.3 From a2f9591f60b12dde2a3b21eb5688c208b98c11f2 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 8 Dec 2017 14:10:03 -0800 Subject: Fixing comment --- Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m index 94dcee0..f2ae687 100644 --- a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m +++ b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m @@ -108,7 +108,7 @@ return snapshot; } -/** Waits for a snapshot with that has no pending writes */ +/** Waits for a snapshot that has no pending writes */ - (FIRDocumentSnapshot *)waitForRemoteEvent { FIRDocumentSnapshot *snapshot = [_accumulator awaitEventWithName:@"Remote event."]; XCTAssertFalse(snapshot.metadata.hasPendingWrites); @@ -141,7 +141,7 @@ } } -/** Waits for a snapshot containing _setData but with resolved server timestamps. */ +/** Verifies a snapshot containing _setData but with resolved server timestamps. */ - (id)verifySnapshotWithResolvedTimestamps:(FIRDocumentSnapshot *)snapshot { XCTAssertTrue(snapshot.exists); NSDate *when = snapshot[@"when"]; -- cgit v1.2.3 From cd17b85587d1a827389ea1e18d0ce5622606cd5f Mon Sep 17 00:00:00 2001 From: Marek Gilbert Date: Sat, 9 Dec 2017 17:06:10 -0800 Subject: Import absl/base/internal/endian.h --- .../abseil-cpp/absl/base/internal/endian.h | 267 ++++++++++++++++++++ .../abseil-cpp/absl/base/internal/endian_test.cc | 279 +++++++++++++++++++++ .../absl/base/internal/unaligned_access.h | 256 +++++++++++++++++++ 3 files changed, 802 insertions(+) create mode 100644 Firestore/third_party/abseil-cpp/absl/base/internal/endian.h create mode 100644 Firestore/third_party/abseil-cpp/absl/base/internal/endian_test.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/base/internal/unaligned_access.h diff --git a/Firestore/third_party/abseil-cpp/absl/base/internal/endian.h b/Firestore/third_party/abseil-cpp/absl/base/internal/endian.h new file mode 100644 index 0000000..602129e --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/base/internal/endian.h @@ -0,0 +1,267 @@ +// Copyright 2017 The Abseil Authors. +// +// 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 ABSL_BASE_INTERNAL_ENDIAN_H_ +#define ABSL_BASE_INTERNAL_ENDIAN_H_ + +// The following guarantees declaration of the byte swap functions +#ifdef _MSC_VER +#include // NOLINT(build/include) +#elif defined(__APPLE__) +// Mac OS X / Darwin features +#include +#elif defined(__GLIBC__) +#include // IWYU pragma: export +#endif + +#include +#include "absl/base/config.h" +#include "absl/base/internal/unaligned_access.h" +#include "absl/base/port.h" + +namespace absl { + +// Use compiler byte-swapping intrinsics if they are available. 32-bit +// and 64-bit versions are available in Clang and GCC as of GCC 4.3.0. +// The 16-bit version is available in Clang and GCC only as of GCC 4.8.0. +// For simplicity, we enable them all only for GCC 4.8.0 or later. +#if defined(__clang__) || \ + (defined(__GNUC__) && \ + ((__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || __GNUC__ >= 5)) +inline uint64_t gbswap_64(uint64_t host_int) { + return __builtin_bswap64(host_int); +} +inline uint32_t gbswap_32(uint32_t host_int) { + return __builtin_bswap32(host_int); +} +inline uint16_t gbswap_16(uint16_t host_int) { + return __builtin_bswap16(host_int); +} + +#elif defined(_MSC_VER) +inline uint64_t gbswap_64(uint64_t host_int) { + return _byteswap_uint64(host_int); +} +inline uint32_t gbswap_32(uint32_t host_int) { + return _byteswap_ulong(host_int); +} +inline uint16_t gbswap_16(uint16_t host_int) { + return _byteswap_ushort(host_int); +} + +#elif defined(__APPLE__) +inline uint64_t gbswap_64(uint64_t host_int) { return OSSwapInt16(host_int); } +inline uint32_t gbswap_32(uint32_t host_int) { return OSSwapInt32(host_int); } +inline uint16_t gbswap_16(uint16_t host_int) { return OSSwapInt64(host_int); } + +#else +inline uint64_t gbswap_64(uint64_t host_int) { +#if defined(__GNUC__) && defined(__x86_64__) && !defined(__APPLE__) + // Adapted from /usr/include/byteswap.h. Not available on Mac. + if (__builtin_constant_p(host_int)) { + return __bswap_constant_64(host_int); + } else { + register uint64_t result; + __asm__("bswap %0" : "=r"(result) : "0"(host_int)); + return result; + } +#elif defined(__GLIBC__) + return bswap_64(host_int); +#else + return (((x & uint64_t{(0xFF}) << 56) | + ((x & uint64_t{(0xFF00}) << 40) | + ((x & uint64_t{(0xFF0000}) << 24) | + ((x & uint64_t{(0xFF000000}) << 8) | + ((x & uint64_t{(0xFF00000000}) >> 8) | + ((x & uint64_t{(0xFF0000000000}) >> 24) | + ((x & uint64_t{(0xFF000000000000}) >> 40) | + ((x & uint64_t{(0xFF00000000000000}) >> 56)); +#endif // bswap_64 +} + +inline uint32_t gbswap_32(uint32_t host_int) { +#if defined(__GLIBC__) + return bswap_32(host_int); +#else + return (((x & 0xFF) << 24) | ((x & 0xFF00) << 8) | ((x & 0xFF0000) >> 8) | + ((x & 0xFF000000) >> 24)); +#endif +} + +inline uint16_t gbswap_16(uint16_t host_int) { +#if defined(__GLIBC__) + return bswap_16(host_int); +#else + return uint16_t{((x & 0xFF) << 8) | ((x & 0xFF00) >> 8)}; +#endif +} + +#endif // intrinics available + +#ifdef ABSL_IS_LITTLE_ENDIAN + +// Definitions for ntohl etc. that don't require us to include +// netinet/in.h. We wrap gbswap_32 and gbswap_16 in functions rather +// than just #defining them because in debug mode, gcc doesn't +// correctly handle the (rather involved) definitions of bswap_32. +// gcc guarantees that inline functions are as fast as macros, so +// this isn't a performance hit. +inline uint16_t ghtons(uint16_t x) { return gbswap_16(x); } +inline uint32_t ghtonl(uint32_t x) { return gbswap_32(x); } +inline uint64_t ghtonll(uint64_t x) { return gbswap_64(x); } + +#elif defined ABSL_IS_BIG_ENDIAN + +// These definitions are simpler on big-endian machines +// These are functions instead of macros to avoid self-assignment warnings +// on calls such as "i = ghtnol(i);". This also provides type checking. +inline uint16_t ghtons(uint16_t x) { return x; } +inline uint32_t ghtonl(uint32_t x) { return x; } +inline uint64_t ghtonll(uint64_t x) { return x; } + +#else +#error \ + "Unsupported byte order: Either ABSL_IS_BIG_ENDIAN or " \ + "ABSL_IS_LITTLE_ENDIAN must be defined" +#endif // byte order + +inline uint16_t gntohs(uint16_t x) { return ghtons(x); } +inline uint32_t gntohl(uint32_t x) { return ghtonl(x); } +inline uint64_t gntohll(uint64_t x) { return ghtonll(x); } + +// Utilities to convert numbers between the current hosts's native byte +// order and little-endian byte order +// +// Load/Store methods are alignment safe +namespace little_endian { +// Conversion functions. +#ifdef ABSL_IS_LITTLE_ENDIAN + +inline uint16_t FromHost16(uint16_t x) { return x; } +inline uint16_t ToHost16(uint16_t x) { return x; } + +inline uint32_t FromHost32(uint32_t x) { return x; } +inline uint32_t ToHost32(uint32_t x) { return x; } + +inline uint64_t FromHost64(uint64_t x) { return x; } +inline uint64_t ToHost64(uint64_t x) { return x; } + +inline constexpr bool IsLittleEndian() { return true; } + +#elif defined ABSL_IS_BIG_ENDIAN + +inline uint16_t FromHost16(uint16_t x) { return gbswap_16(x); } +inline uint16_t ToHost16(uint16_t x) { return gbswap_16(x); } + +inline uint32_t FromHost32(uint32_t x) { return gbswap_32(x); } +inline uint32_t ToHost32(uint32_t x) { return gbswap_32(x); } + +inline uint64_t FromHost64(uint64_t x) { return gbswap_64(x); } +inline uint64_t ToHost64(uint64_t x) { return gbswap_64(x); } + +inline constexpr bool IsLittleEndian() { return false; } + +#endif /* ENDIAN */ + +// Functions to do unaligned loads and stores in little-endian order. +inline uint16_t Load16(const void *p) { + return ToHost16(ABSL_INTERNAL_UNALIGNED_LOAD16(p)); +} + +inline void Store16(void *p, uint16_t v) { + ABSL_INTERNAL_UNALIGNED_STORE16(p, FromHost16(v)); +} + +inline uint32_t Load32(const void *p) { + return ToHost32(ABSL_INTERNAL_UNALIGNED_LOAD32(p)); +} + +inline void Store32(void *p, uint32_t v) { + ABSL_INTERNAL_UNALIGNED_STORE32(p, FromHost32(v)); +} + +inline uint64_t Load64(const void *p) { + return ToHost64(ABSL_INTERNAL_UNALIGNED_LOAD64(p)); +} + +inline void Store64(void *p, uint64_t v) { + ABSL_INTERNAL_UNALIGNED_STORE64(p, FromHost64(v)); +} + +} // namespace little_endian + +// Utilities to convert numbers between the current hosts's native byte +// order and big-endian byte order (same as network byte order) +// +// Load/Store methods are alignment safe +namespace big_endian { +#ifdef ABSL_IS_LITTLE_ENDIAN + +inline uint16_t FromHost16(uint16_t x) { return gbswap_16(x); } +inline uint16_t ToHost16(uint16_t x) { return gbswap_16(x); } + +inline uint32_t FromHost32(uint32_t x) { return gbswap_32(x); } +inline uint32_t ToHost32(uint32_t x) { return gbswap_32(x); } + +inline uint64_t FromHost64(uint64_t x) { return gbswap_64(x); } +inline uint64_t ToHost64(uint64_t x) { return gbswap_64(x); } + +inline constexpr bool IsLittleEndian() { return true; } + +#elif defined ABSL_IS_BIG_ENDIAN + +inline uint16_t FromHost16(uint16_t x) { return x; } +inline uint16_t ToHost16(uint16_t x) { return x; } + +inline uint32_t FromHost32(uint32_t x) { return x; } +inline uint32_t ToHost32(uint32_t x) { return x; } + +inline uint64_t FromHost64(uint64_t x) { return x; } +inline uint64_t ToHost64(uint64_t x) { return x; } + +inline constexpr bool IsLittleEndian() { return false; } + +#endif /* ENDIAN */ + +// Functions to do unaligned loads and stores in big-endian order. +inline uint16_t Load16(const void *p) { + return ToHost16(ABSL_INTERNAL_UNALIGNED_LOAD16(p)); +} + +inline void Store16(void *p, uint16_t v) { + ABSL_INTERNAL_UNALIGNED_STORE16(p, FromHost16(v)); +} + +inline uint32_t Load32(const void *p) { + return ToHost32(ABSL_INTERNAL_UNALIGNED_LOAD32(p)); +} + +inline void Store32(void *p, uint32_t v) { + ABSL_INTERNAL_UNALIGNED_STORE32(p, FromHost32(v)); +} + +inline uint64_t Load64(const void *p) { + return ToHost64(ABSL_INTERNAL_UNALIGNED_LOAD64(p)); +} + +inline void Store64(void *p, uint64_t v) { + ABSL_INTERNAL_UNALIGNED_STORE64(p, FromHost64(v)); +} + +} // namespace big_endian + +} // namespace absl + +#endif // ABSL_BASE_INTERNAL_ENDIAN_H_ diff --git a/Firestore/third_party/abseil-cpp/absl/base/internal/endian_test.cc b/Firestore/third_party/abseil-cpp/absl/base/internal/endian_test.cc new file mode 100644 index 0000000..f3ff4b3 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/base/internal/endian_test.cc @@ -0,0 +1,279 @@ +// Copyright 2017 The Abseil Authors. +// +// 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 "absl/base/internal/endian.h" + +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "absl/base/config.h" + +namespace absl { +namespace { + +const uint64_t kInitialNumber{0x0123456789abcdef}; +const uint64_t k64Value{kInitialNumber}; +const uint32_t k32Value{0x01234567}; +const uint16_t k16Value{0x0123}; +const int kNumValuesToTest = 1000000; +const int kRandomSeed = 12345; + +#ifdef ABSL_IS_BIG_ENDIAN +const uint64_t kInitialInNetworkOrder{kInitialNumber}; +const uint64_t k64ValueLE{0xefcdab8967452301}; +const uint32_t k32ValueLE{0x67452301}; +const uint16_t k16ValueLE{0x2301}; +const uint8_t k8ValueLE{k8Value}; +const uint64_t k64IValueLE{0xefcdab89674523a1}; +const uint32_t k32IValueLE{0x67452391}; +const uint16_t k16IValueLE{0x85ff}; +const uint8_t k8IValueLE{0xff}; +const uint64_t kDoubleValueLE{0x6e861bf0f9210940}; +const uint32_t kFloatValueLE{0xd00f4940}; +const uint8_t kBoolValueLE{0x1}; + +const uint64_t k64ValueBE{kInitialNumber}; +const uint32_t k32ValueBE{k32Value}; +const uint16_t k16ValueBE{k16Value}; +const uint8_t k8ValueBE{k8Value}; +const uint64_t k64IValueBE{0xa123456789abcdef}; +const uint32_t k32IValueBE{0x91234567}; +const uint16_t k16IValueBE{0xff85}; +const uint8_t k8IValueBE{0xff}; +const uint64_t kDoubleValueBE{0x400921f9f01b866e}; +const uint32_t kFloatValueBE{0x40490fd0}; +const uint8_t kBoolValueBE{0x1}; +#elif defined ABSL_IS_LITTLE_ENDIAN +const uint64_t kInitialInNetworkOrder{0xefcdab8967452301}; +const uint64_t k64ValueLE{kInitialNumber}; +const uint32_t k32ValueLE{k32Value}; +const uint16_t k16ValueLE{k16Value}; + +const uint64_t k64ValueBE{0xefcdab8967452301}; +const uint32_t k32ValueBE{0x67452301}; +const uint16_t k16ValueBE{0x2301}; +#endif + +template +std::vector GenerateAllValuesForType() { + std::vector result; + T next = std::numeric_limits::min(); + while (true) { + result.push_back(next); + if (next == std::numeric_limits::max()) { + return result; + } + ++next; + } +} + +template +std::vector GenerateRandomIntegers(size_t numValuesToTest) { + std::vector result; + std::mt19937_64 rng(kRandomSeed); + for (size_t i = 0; i < numValuesToTest; ++i) { + result.push_back(rng()); + } + return result; +} + +void ManualByteSwap(char* bytes, int length) { + if (length == 1) + return; + + EXPECT_EQ(0, length % 2); + for (int i = 0; i < length / 2; ++i) { + int j = (length - 1) - i; + using std::swap; + swap(bytes[i], bytes[j]); + } +} + +template +inline T UnalignedLoad(const char* p) { + static_assert( + sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8, + "Unexpected type size"); + + switch (sizeof(T)) { + case 1: return *reinterpret_cast(p); + case 2: + return ABSL_INTERNAL_UNALIGNED_LOAD16(p); + case 4: + return ABSL_INTERNAL_UNALIGNED_LOAD32(p); + case 8: + return ABSL_INTERNAL_UNALIGNED_LOAD64(p); + default: + // Suppresses invalid "not all control paths return a value" on MSVC + return {}; + } +} + +template +static void GBSwapHelper(const std::vector& host_values_to_test, + const ByteSwapper& byte_swapper) { + // Test byte_swapper against a manual byte swap. + for (typename std::vector::const_iterator it = host_values_to_test.begin(); + it != host_values_to_test.end(); ++it) { + T host_value = *it; + + char actual_value[sizeof(host_value)]; + memcpy(actual_value, &host_value, sizeof(host_value)); + byte_swapper(actual_value); + + char expected_value[sizeof(host_value)]; + memcpy(expected_value, &host_value, sizeof(host_value)); + ManualByteSwap(expected_value, sizeof(host_value)); + + ASSERT_EQ(0, memcmp(actual_value, expected_value, sizeof(host_value))) + << "Swap output for 0x" << std::hex << host_value << " does not match. " + << "Expected: 0x" << UnalignedLoad(expected_value) << "; " + << "actual: 0x" << UnalignedLoad(actual_value); + } +} + +void Swap16(char* bytes) { + ABSL_INTERNAL_UNALIGNED_STORE16( + bytes, gbswap_16(ABSL_INTERNAL_UNALIGNED_LOAD16(bytes))); +} + +void Swap32(char* bytes) { + ABSL_INTERNAL_UNALIGNED_STORE32( + bytes, gbswap_32(ABSL_INTERNAL_UNALIGNED_LOAD32(bytes))); +} + +void Swap64(char* bytes) { + ABSL_INTERNAL_UNALIGNED_STORE64( + bytes, gbswap_64(ABSL_INTERNAL_UNALIGNED_LOAD64(bytes))); +} + +TEST(EndianessTest, Uint16) { + GBSwapHelper(GenerateAllValuesForType(), &Swap16); +} + +TEST(EndianessTest, Uint32) { + GBSwapHelper(GenerateRandomIntegers(kNumValuesToTest), &Swap32); +} + +TEST(EndianessTest, Uint64) { + GBSwapHelper(GenerateRandomIntegers(kNumValuesToTest), &Swap64); +} + +TEST(EndianessTest, ghtonll_gntohll) { + // Test that absl::ghtonl compiles correctly + uint32_t test = 0x01234567; + EXPECT_EQ(absl::gntohl(absl::ghtonl(test)), test); + + uint64_t comp = absl::ghtonll(kInitialNumber); + EXPECT_EQ(comp, kInitialInNetworkOrder); + comp = absl::gntohll(kInitialInNetworkOrder); + EXPECT_EQ(comp, kInitialNumber); + + // Test that htonll and ntohll are each others' inverse functions on a + // somewhat assorted batch of numbers. 37 is chosen to not be anything + // particularly nice base 2. + uint64_t value = 1; + for (int i = 0; i < 100; ++i) { + comp = absl::ghtonll(absl::gntohll(value)); + EXPECT_EQ(value, comp); + comp = absl::gntohll(absl::ghtonll(value)); + EXPECT_EQ(value, comp); + value *= 37; + } +} + +TEST(EndianessTest, little_endian) { + // Check little_endian uint16_t. + uint64_t comp = little_endian::FromHost16(k16Value); + EXPECT_EQ(comp, k16ValueLE); + comp = little_endian::ToHost16(k16ValueLE); + EXPECT_EQ(comp, k16Value); + + // Check little_endian uint32_t. + comp = little_endian::FromHost32(k32Value); + EXPECT_EQ(comp, k32ValueLE); + comp = little_endian::ToHost32(k32ValueLE); + EXPECT_EQ(comp, k32Value); + + // Check little_endian uint64_t. + comp = little_endian::FromHost64(k64Value); + EXPECT_EQ(comp, k64ValueLE); + comp = little_endian::ToHost64(k64ValueLE); + EXPECT_EQ(comp, k64Value); + + // Check little-endian Load and store functions. + uint16_t u16Buf; + uint32_t u32Buf; + uint64_t u64Buf; + + little_endian::Store16(&u16Buf, k16Value); + EXPECT_EQ(u16Buf, k16ValueLE); + comp = little_endian::Load16(&u16Buf); + EXPECT_EQ(comp, k16Value); + + little_endian::Store32(&u32Buf, k32Value); + EXPECT_EQ(u32Buf, k32ValueLE); + comp = little_endian::Load32(&u32Buf); + EXPECT_EQ(comp, k32Value); + + little_endian::Store64(&u64Buf, k64Value); + EXPECT_EQ(u64Buf, k64ValueLE); + comp = little_endian::Load64(&u64Buf); + EXPECT_EQ(comp, k64Value); +} + +TEST(EndianessTest, big_endian) { + // Check big-endian Load and store functions. + uint16_t u16Buf; + uint32_t u32Buf; + uint64_t u64Buf; + + unsigned char buffer[10]; + big_endian::Store16(&u16Buf, k16Value); + EXPECT_EQ(u16Buf, k16ValueBE); + uint64_t comp = big_endian::Load16(&u16Buf); + EXPECT_EQ(comp, k16Value); + + big_endian::Store32(&u32Buf, k32Value); + EXPECT_EQ(u32Buf, k32ValueBE); + comp = big_endian::Load32(&u32Buf); + EXPECT_EQ(comp, k32Value); + + big_endian::Store64(&u64Buf, k64Value); + EXPECT_EQ(u64Buf, k64ValueBE); + comp = big_endian::Load64(&u64Buf); + EXPECT_EQ(comp, k64Value); + + big_endian::Store16(buffer + 1, k16Value); + EXPECT_EQ(u16Buf, k16ValueBE); + comp = big_endian::Load16(buffer + 1); + EXPECT_EQ(comp, k16Value); + + big_endian::Store32(buffer + 1, k32Value); + EXPECT_EQ(u32Buf, k32ValueBE); + comp = big_endian::Load32(buffer + 1); + EXPECT_EQ(comp, k32Value); + + big_endian::Store64(buffer + 1, k64Value); + EXPECT_EQ(u64Buf, k64ValueBE); + comp = big_endian::Load64(buffer + 1); + EXPECT_EQ(comp, k64Value); +} + +} // namespace +} // namespace absl diff --git a/Firestore/third_party/abseil-cpp/absl/base/internal/unaligned_access.h b/Firestore/third_party/abseil-cpp/absl/base/internal/unaligned_access.h new file mode 100644 index 0000000..ea30829 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/base/internal/unaligned_access.h @@ -0,0 +1,256 @@ +// +// Copyright 2017 The Abseil Authors. +// +// 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 ABSL_BASE_INTERNAL_UNALIGNED_ACCESS_H_ +#define ABSL_BASE_INTERNAL_UNALIGNED_ACCESS_H_ + +#include +#include + +#include "absl/base/attributes.h" + +// unaligned APIs + +// Portable handling of unaligned loads, stores, and copies. +// On some platforms, like ARM, the copy functions can be more efficient +// then a load and a store. +// +// It is possible to implement all of these these using constant-length memcpy +// calls, which is portable and will usually be inlined into simple loads and +// stores if the architecture supports it. However, such inlining usually +// happens in a pass that's quite late in compilation, which means the resulting +// loads and stores cannot participate in many other optimizations, leading to +// overall worse code. + +// The unaligned API is C++ only. The declarations use C++ features +// (namespaces, inline) which are absent or incompatible in C. +#if defined(__cplusplus) + +#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) ||\ + defined(MEMORY_SANITIZER) +// Consider we have an unaligned load/store of 4 bytes from address 0x...05. +// AddressSanitizer will treat it as a 3-byte access to the range 05:07 and +// will miss a bug if 08 is the first unaddressable byte. +// ThreadSanitizer will also treat this as a 3-byte access to 05:07 and will +// miss a race between this access and some other accesses to 08. +// MemorySanitizer will correctly propagate the shadow on unaligned stores +// and correctly report bugs on unaligned loads, but it may not properly +// update and report the origin of the uninitialized memory. +// For all three tools, replacing an unaligned access with a tool-specific +// callback solves the problem. + +// Make sure uint16_t/uint32_t/uint64_t are defined. +#include + +extern "C" { +uint16_t __sanitizer_unaligned_load16(const void *p); +uint32_t __sanitizer_unaligned_load32(const void *p); +uint64_t __sanitizer_unaligned_load64(const void *p); +void __sanitizer_unaligned_store16(void *p, uint16_t v); +void __sanitizer_unaligned_store32(void *p, uint32_t v); +void __sanitizer_unaligned_store64(void *p, uint64_t v); +} // extern "C" + +namespace absl { + +inline uint16_t UnalignedLoad16(const void *p) { + return __sanitizer_unaligned_load16(p); +} + +inline uint32_t UnalignedLoad32(const void *p) { + return __sanitizer_unaligned_load32(p); +} + +inline uint64_t UnalignedLoad64(const void *p) { + return __sanitizer_unaligned_load64(p); +} + +inline void UnalignedStore16(void *p, uint16_t v) { + __sanitizer_unaligned_store16(p, v); +} + +inline void UnalignedStore32(void *p, uint32_t v) { + __sanitizer_unaligned_store32(p, v); +} + +inline void UnalignedStore64(void *p, uint64_t v) { + __sanitizer_unaligned_store64(p, v); +} + +} // namespace absl + +#define ABSL_INTERNAL_UNALIGNED_LOAD16(_p) (absl::UnalignedLoad16(_p)) +#define ABSL_INTERNAL_UNALIGNED_LOAD32(_p) (absl::UnalignedLoad32(_p)) +#define ABSL_INTERNAL_UNALIGNED_LOAD64(_p) (absl::UnalignedLoad64(_p)) + +#define ABSL_INTERNAL_UNALIGNED_STORE16(_p, _val) \ + (absl::UnalignedStore16(_p, _val)) +#define ABSL_INTERNAL_UNALIGNED_STORE32(_p, _val) \ + (absl::UnalignedStore32(_p, _val)) +#define ABSL_INTERNAL_UNALIGNED_STORE64(_p, _val) \ + (absl::UnalignedStore64(_p, _val)) + +#elif defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ + defined(_M_IX86) || defined(__ppc__) || defined(__PPC__) || \ + defined(__ppc64__) || defined(__PPC64__) + +// x86 and x86-64 can perform unaligned loads/stores directly; +// modern PowerPC hardware can also do unaligned integer loads and stores; +// but note: the FPU still sends unaligned loads and stores to a trap handler! + +#define ABSL_INTERNAL_UNALIGNED_LOAD16(_p) \ + (*reinterpret_cast(_p)) +#define ABSL_INTERNAL_UNALIGNED_LOAD32(_p) \ + (*reinterpret_cast(_p)) +#define ABSL_INTERNAL_UNALIGNED_LOAD64(_p) \ + (*reinterpret_cast(_p)) + +#define ABSL_INTERNAL_UNALIGNED_STORE16(_p, _val) \ + (*reinterpret_cast(_p) = (_val)) +#define ABSL_INTERNAL_UNALIGNED_STORE32(_p, _val) \ + (*reinterpret_cast(_p) = (_val)) +#define ABSL_INTERNAL_UNALIGNED_STORE64(_p, _val) \ + (*reinterpret_cast(_p) = (_val)) + +#elif defined(__arm__) && \ + !defined(__ARM_ARCH_5__) && \ + !defined(__ARM_ARCH_5T__) && \ + !defined(__ARM_ARCH_5TE__) && \ + !defined(__ARM_ARCH_5TEJ__) && \ + !defined(__ARM_ARCH_6__) && \ + !defined(__ARM_ARCH_6J__) && \ + !defined(__ARM_ARCH_6K__) && \ + !defined(__ARM_ARCH_6Z__) && \ + !defined(__ARM_ARCH_6ZK__) && \ + !defined(__ARM_ARCH_6T2__) + + +// ARMv7 and newer support native unaligned accesses, but only of 16-bit +// and 32-bit values (not 64-bit); older versions either raise a fatal signal, +// do an unaligned read and rotate the words around a bit, or do the reads very +// slowly (trip through kernel mode). There's no simple #define that says just +// “ARMv7 or higher”, so we have to filter away all ARMv5 and ARMv6 +// sub-architectures. Newer gcc (>= 4.6) set an __ARM_FEATURE_ALIGNED #define, +// so in time, maybe we can move on to that. +// +// This is a mess, but there's not much we can do about it. +// +// To further complicate matters, only LDR instructions (single reads) are +// allowed to be unaligned, not LDRD (two reads) or LDM (many reads). Unless we +// explicitly tell the compiler that these accesses can be unaligned, it can and +// will combine accesses. On armcc, the way to signal this is done by accessing +// through the type (uint32_t __packed *), but GCC has no such attribute +// (it ignores __attribute__((packed)) on individual variables). However, +// we can tell it that a _struct_ is unaligned, which has the same effect, +// so we do that. + +namespace absl { +namespace internal { + +struct Unaligned16Struct { + uint16_t value; + uint8_t dummy; // To make the size non-power-of-two. +} ABSL_ATTRIBUTE_PACKED; + +struct Unaligned32Struct { + uint32_t value; + uint8_t dummy; // To make the size non-power-of-two. +} ABSL_ATTRIBUTE_PACKED; + +} // namespace internal +} // namespace absl + +#define ABSL_INTERNAL_UNALIGNED_LOAD16(_p) \ + ((reinterpret_cast(_p))->value) +#define ABSL_INTERNAL_UNALIGNED_LOAD32(_p) \ + ((reinterpret_cast(_p))->value) + +#define ABSL_INTERNAL_UNALIGNED_STORE16(_p, _val) \ + ((reinterpret_cast< ::absl::internal::Unaligned16Struct *>(_p))->value = \ + (_val)) +#define ABSL_INTERNAL_UNALIGNED_STORE32(_p, _val) \ + ((reinterpret_cast< ::absl::internal::Unaligned32Struct *>(_p))->value = \ + (_val)) + +namespace absl { + +inline uint64_t UnalignedLoad64(const void *p) { + uint64_t t; + memcpy(&t, p, sizeof t); + return t; +} + +inline void UnalignedStore64(void *p, uint64_t v) { memcpy(p, &v, sizeof v); } + +} // namespace absl + +#define ABSL_INTERNAL_UNALIGNED_LOAD64(_p) (absl::UnalignedLoad64(_p)) +#define ABSL_INTERNAL_UNALIGNED_STORE64(_p, _val) \ + (absl::UnalignedStore64(_p, _val)) + +#else + +// ABSL_INTERNAL_NEED_ALIGNED_LOADS is defined when the underlying platform +// doesn't support unaligned access. +#define ABSL_INTERNAL_NEED_ALIGNED_LOADS + +// These functions are provided for architectures that don't support +// unaligned loads and stores. + +namespace absl { + +inline uint16_t UnalignedLoad16(const void *p) { + uint16_t t; + memcpy(&t, p, sizeof t); + return t; +} + +inline uint32_t UnalignedLoad32(const void *p) { + uint32_t t; + memcpy(&t, p, sizeof t); + return t; +} + +inline uint64_t UnalignedLoad64(const void *p) { + uint64_t t; + memcpy(&t, p, sizeof t); + return t; +} + +inline void UnalignedStore16(void *p, uint16_t v) { memcpy(p, &v, sizeof v); } + +inline void UnalignedStore32(void *p, uint32_t v) { memcpy(p, &v, sizeof v); } + +inline void UnalignedStore64(void *p, uint64_t v) { memcpy(p, &v, sizeof v); } + +} // namespace absl + +#define ABSL_INTERNAL_UNALIGNED_LOAD16(_p) (absl::UnalignedLoad16(_p)) +#define ABSL_INTERNAL_UNALIGNED_LOAD32(_p) (absl::UnalignedLoad32(_p)) +#define ABSL_INTERNAL_UNALIGNED_LOAD64(_p) (absl::UnalignedLoad64(_p)) + +#define ABSL_INTERNAL_UNALIGNED_STORE16(_p, _val) \ + (absl::UnalignedStore16(_p, _val)) +#define ABSL_INTERNAL_UNALIGNED_STORE32(_p, _val) \ + (absl::UnalignedStore32(_p, _val)) +#define ABSL_INTERNAL_UNALIGNED_STORE64(_p, _val) \ + (absl::UnalignedStore64(_p, _val)) + +#endif + +#endif // defined(__cplusplus), end of unaligned API + +#endif // ABSL_BASE_INTERNAL_UNALIGNED_ACCESS_H_ -- cgit v1.2.3 From 3bc5488644b8a5042079ffcf91e852bedc24d572 Mon Sep 17 00:00:00 2001 From: Marek Gilbert Date: Sat, 9 Dec 2017 17:35:52 -0800 Subject: Add absl/base/internal/endian.h back into the build --- Firestore/third_party/abseil-cpp/absl/base/CMakeLists.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Firestore/third_party/abseil-cpp/absl/base/CMakeLists.txt b/Firestore/third_party/abseil-cpp/absl/base/CMakeLists.txt index a6cd1ff..08659c4 100644 --- a/Firestore/third_party/abseil-cpp/absl/base/CMakeLists.txt +++ b/Firestore/third_party/abseil-cpp/absl/base/CMakeLists.txt @@ -27,9 +27,11 @@ list(APPEND BASE_PUBLIC_HEADERS list(APPEND BASE_INTERNAL_HEADERS "internal/atomic_hook.h" + "internal/endian.h" "internal/log_severity.h" "internal/raw_logging.h" "internal/throw_delegate.h" + "internal/unaligned_access.h" ) @@ -84,6 +86,17 @@ absl_library( ## TESTS # +# test endian_test +set(ENDIAN_TEST_SRC "internal/endian_test.cc") + +absl_test( + TARGET + endian_test + SOURCES + ${ENDIAN_TEST_SRC} +) + + # test config_test set(CONFIG_TEST_SRC "config_test.cc") set(CONFIG_TEST_PUBLIC_LIBRARIES absl::base) -- cgit v1.2.3 From 6a8d436f0943f10220fddb81b89e27c23ac72e2f Mon Sep 17 00:00:00 2001 From: Ryan Wilson Date: Mon, 11 Dec 2017 12:25:49 -0500 Subject: Modified FirebaseAppStoreURLCheckEnabled Key. (#554) --- Firebase/Core/third_party/FIRAppEnvironmentUtil.m | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Firebase/Core/third_party/FIRAppEnvironmentUtil.m b/Firebase/Core/third_party/FIRAppEnvironmentUtil.m index 337f082..fe4e23d 100644 --- a/Firebase/Core/third_party/FIRAppEnvironmentUtil.m +++ b/Firebase/Core/third_party/FIRAppEnvironmentUtil.m @@ -36,9 +36,11 @@ struct encryption_info_command { @implementation FIRAppEnvironmentUtil -/// A key for the Info.plist to enable or disable checking if the App Store is running in a sandbox, -/// which would be true while running on TestFlight. -static NSString *const kFIRAppStoreSandboxCheckEnabledKey = @"FirebaseAppStoreSandboxCheckEnabled"; +/// A key for the Info.plist to enable or disable checking if the App Store is running in a sandbox. +/// This will affect your data integrity when using Firebase Analytics, as it will disable some +/// necessary checks. +static NSString *const kFIRAppStoreReceiptURLCheckEnabledKey = + @"FirebaseAppStoreReceiptURLCheckEnabled"; /// The file name of the sandbox receipt. This is available on iOS >= 8.0 static NSString *const kFIRAIdentitySandboxReceiptFileName = @"sandboxReceipt"; @@ -159,7 +161,7 @@ static BOOL isAppEncrypted() { // Since checking the App Store's receipt URL can be memory intensive, check the option in the // Info.plist if developers opted out of this check. id enableSandboxCheck = - [[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRAppStoreSandboxCheckEnabledKey]; + [[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRAppStoreReceiptURLCheckEnabledKey]; if (enableSandboxCheck && [enableSandboxCheck isKindOfClass:[NSNumber class]] && ![enableSandboxCheck boolValue]) { -- cgit v1.2.3 From 54ba423c87d6e0a409e4fa3dc95cd81ca16b97f7 Mon Sep 17 00:00:00 2001 From: zxu Date: Mon, 11 Dec 2017 15:37:01 -0500 Subject: Revisit commit method in FIRWriteBatch (#541) * revisit FIRWriteBatch commit * make commitWithCompletion completion nullable; * add commit; * add unit test; * add swift build test for commit; * update CHANGELOG. --- Firestore/CHANGELOG.md | 1 + Firestore/Example/SwiftBuildTest/main.swift | 26 ++++++++++++++++++++++ .../Tests/Integration/API/FIRWriteBatchTests.m | 17 ++++++++++++++ Firestore/Source/API/FIRWriteBatch.m | 6 ++++- Firestore/Source/Public/FIRWriteBatch.h | 7 +++++- 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 32cbbfc..1a8eb07 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -2,6 +2,7 @@ - [changed] Firestore no longer has a direct dependency on FirebaseAuth. - [changed] Removed the includeMetadataChanges property in FIRDocumentListenOptions to avoid confusion with the factory method of the same name. +- [changed] Added a commit method that takes no completion handler to FIRWriteBatch. - [fixed] Fixed a crash when using path names with international characters with persistence enabled. diff --git a/Firestore/Example/SwiftBuildTest/main.swift b/Firestore/Example/SwiftBuildTest/main.swift index bea8b56..475c1b2 100644 --- a/Firestore/Example/SwiftBuildTest/main.swift +++ b/Firestore/Example/SwiftBuildTest/main.swift @@ -27,6 +27,8 @@ func main() { writeDocument(at: documentRef); + writeDocuments(at: documentRef, database: db); + addDocument(to: collectionRef); readDocument(at: documentRef); @@ -129,6 +131,30 @@ func writeDocument(at docRef: DocumentReference) { } } +func writeDocuments(at docRef: DocumentReference, database db: Firestore) { + var batch: WriteBatch; + + batch = db.batch(); + batch.setData(["a" : "b"], forDocument:docRef); + batch.setData(["c" : "d"], forDocument:docRef); + // commit without completion callback. + batch.commit(); + print("Batch write without completion complete!"); + + batch = db.batch(); + batch.setData(["a" : "b"], forDocument:docRef); + batch.setData(["c" : "d"], forDocument:docRef); + // commit with completion callback via trailing closure syntax. + batch.commit() { error in + if let error = error { + print("Uh oh! \(error)"); + return; + } + print("Batch write callback complete!"); + } + print("Batch write with completion complete!"); +} + func addDocument(to collectionRef: CollectionReference) { collectionRef.addDocument(data: ["foo": 42]); diff --git a/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m b/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m index 562c29f..23ae7bc 100644 --- a/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m +++ b/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m @@ -35,6 +35,23 @@ [self awaitExpectations]; } +- (void)testCommitWithoutCompletionHandler { + FIRDocumentReference *doc = [self documentRef]; + FIRWriteBatch *batch1 = [doc.firestore batch]; + [batch1 setData:@{@"aa" : @"bb"} forDocument:doc]; + [batch1 commitWithCompletion:nil]; + FIRDocumentSnapshot *snapshot1 = [self readDocumentForRef:doc]; + XCTAssertTrue(snapshot1.exists); + XCTAssertEqualObjects(snapshot1.data, @{@"aa" : @"bb"}); + + FIRWriteBatch *batch2 = [doc.firestore batch]; + [batch2 setData:@{@"cc" : @"dd"} forDocument:doc]; + [batch2 commit]; + FIRDocumentSnapshot *snapshot2 = [self readDocumentForRef:doc]; + XCTAssertTrue(snapshot2.exists); + XCTAssertEqualObjects(snapshot2.data, @{@"cc" : @"dd"}); +} + - (void)testSetDocuments { FIRDocumentReference *doc = [self documentRef]; XCTestExpectation *batchExpectation = [self expectationWithDescription:@"batch written"]; diff --git a/Firestore/Source/API/FIRWriteBatch.m b/Firestore/Source/API/FIRWriteBatch.m index b918a9a..b1cfa09 100644 --- a/Firestore/Source/API/FIRWriteBatch.m +++ b/Firestore/Source/API/FIRWriteBatch.m @@ -93,7 +93,11 @@ NS_ASSUME_NONNULL_BEGIN return self; } -- (void)commitWithCompletion:(void (^)(NSError *_Nullable error))completion { +- (void)commit { + [self commitWithCompletion:nil]; +} + +- (void)commitWithCompletion:(nullable void (^)(NSError *_Nullable error))completion { [self verifyNotCommitted]; self.committed = TRUE; [self.firestore.client writeMutations:self.mutations completion:completion]; diff --git a/Firestore/Source/Public/FIRWriteBatch.h b/Firestore/Source/Public/FIRWriteBatch.h index 5f0034c..8ff1bec 100644 --- a/Firestore/Source/Public/FIRWriteBatch.h +++ b/Firestore/Source/Public/FIRWriteBatch.h @@ -92,6 +92,11 @@ NS_SWIFT_NAME(WriteBatch) - (FIRWriteBatch *)deleteDocument:(FIRDocumentReference *)document NS_SWIFT_NAME(deleteDocument(_:)); +/** + * Commits all of the writes in this write batch as a single atomic unit. + */ +- (void)commit; + /** * Commits all of the writes in this write batch as a single atomic unit. * @@ -101,7 +106,7 @@ NS_SWIFT_NAME(WriteBatch) * completion handler will not be called when the device is offline, though local * changes will be visible immediately. */ -- (void)commitWithCompletion:(void (^)(NSError *_Nullable error))completion; +- (void)commitWithCompletion:(nullable void (^)(NSError *_Nullable error))completion; @end -- cgit v1.2.3 From 43774fec33adda895609ccd61d7c00e71d2396bb Mon Sep 17 00:00:00 2001 From: Gil Date: Mon, 11 Dec 2017 14:14:05 -0800 Subject: Update CHANGELOG for Firestore v0.9.4 (#556) --- Firestore/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 0c5bcdc..ae9f994 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,8 +1,9 @@ # Unreleased + +# v0.9.4 - [changed] Firestore no longer has a direct dependency on FirebaseAuth. - [fixed] Fixed a crash when using path names with international characters with persistence enabled. - - [fixed] Addressed race condition during the teardown of idle streams (#490). # v0.9.3 -- cgit v1.2.3 From 64066c643d4e1fff7d00d656201d056f10fb2c36 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Tue, 12 Dec 2017 07:02:26 +0800 Subject: Adding Generics --- Firestore/Source/Model/FSTFieldValue.h | 50 ++++++++++++++++------------------ 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/Firestore/Source/Model/FSTFieldValue.h b/Firestore/Source/Model/FSTFieldValue.h index fe15865..9b715a2 100644 --- a/Firestore/Source/Model/FSTFieldValue.h +++ b/Firestore/Source/Model/FSTFieldValue.h @@ -86,7 +86,7 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { * - Array * - Object */ -@interface FSTFieldValue : NSObject +@interface FSTFieldValue <__covariant T> : NSObject /** Returns the FSTTypeOrder for this value. */ - (FSTTypeOrder)typeOrder; @@ -97,7 +97,7 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { * TODO(mikelehen): This conversion should probably happen at the API level and right now `value` is * used inappropriately in the serializer implementation, etc. We need to do some reworking. */ -- (id)value; +- (T)value; /** * Converts an FSTFieldValue into the value that users will see in document snapshots. @@ -105,7 +105,7 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { * Options can be provided to configure the deserialization of some field values (such as server * timestamps). */ -- (id)valueWithOptions:(FSTFieldValueOptions *)options; +- (T)valueWithOptions:(FSTFieldValueOptions *)options; /** Compares against another FSTFieldValue. */ - (NSComparisonResult)compare:(FSTFieldValue *)other; @@ -115,26 +115,24 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { /** * A null value stored in Firestore. The |value| of a FSTNullValue is [NSNull null]. */ -@interface FSTNullValue : FSTFieldValue +@interface FSTNullValue : FSTFieldValue + (instancetype)nullValue; -- (NSNull *)valueWithOptions:(FSTFieldValueOptions *)options; @end /** * A boolean value stored in Firestore. */ -@interface FSTBooleanValue : FSTFieldValue +@interface FSTBooleanValue : FSTFieldValue + (instancetype)trueValue; + (instancetype)falseValue; + (instancetype)booleanValue:(BOOL)value; -- (NSNumber *)valueWithOptions:(FSTFieldValueOptions *)options; @end /** * Base class inherited from by FSTIntegerValue and FSTDoubleValue. It implements proper number * comparisons between the two types. */ -@interface FSTNumberValue : FSTFieldValue +@interface FSTNumberValue : FSTFieldValue @end /** @@ -143,7 +141,6 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { @interface FSTIntegerValue : FSTNumberValue + (instancetype)integerValue:(int64_t)value; - (int64_t)internalValue; -- (NSNumber *)valueWithOptions:(FSTFieldValueOptions *)options; @end /** @@ -153,23 +150,20 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { + (instancetype)doubleValue:(double)value; + (instancetype)nanValue; - (double)internalValue; -- (NSNumber *)valueWithOptions:(FSTFieldValueOptions *)options; @end /** * A string stored in Firestore. */ -@interface FSTStringValue : FSTFieldValue +@interface FSTStringValue : FSTFieldValue + (instancetype)stringValue:(NSString *)value; -- (NSString *)valueWithOptions:(FSTFieldValueOptions *)options; @end /** * A timestamp value stored in Firestore. */ -@interface FSTTimestampValue : FSTFieldValue +@interface FSTTimestampValue : FSTFieldValue + (instancetype)timestampValue:(FSTTimestamp *)value; -- (NSDate *)valueWithOptions:(FSTFieldValueOptions *)options; - (FSTTimestamp *)internalValue; @end @@ -185,7 +179,7 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { * - They sort after all FSTTimestampValues. With respect to other FSTServerTimestampValues, they * sort by their localWriteTime. */ -@interface FSTServerTimestampValue : FSTFieldValue +@interface FSTServerTimestampValue : FSTFieldValue + (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime previousValue:(nullable FSTFieldValue *)previousValue; @@ -197,7 +191,7 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { /** * A geo point value stored in Firestore. */ -@interface FSTGeoPointValue : FSTFieldValue +@interface FSTGeoPointValue : FSTFieldValue + (instancetype)geoPointValue:(FIRGeoPoint *)value; - (FIRGeoPoint *)valueWithOptions:(FSTFieldValueOptions *)options; @end @@ -205,7 +199,7 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { /** * A blob value stored in Firestore. */ -@interface FSTBlobValue : FSTFieldValue +@interface FSTBlobValue : FSTFieldValue + (instancetype)blobValue:(NSData *)value; - (NSData *)valueWithOptions:(FSTFieldValueOptions *)options; @end @@ -213,7 +207,7 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { /** * A reference value stored in Firestore. */ -@interface FSTReferenceValue : FSTFieldValue +@interface FSTReferenceValue : FSTFieldValue + (instancetype)referenceValue:(FSTDocumentKey *)value databaseID:(FSTDatabaseID *)databaseID; - (FSTDocumentKey *)valueWithOptions:(FSTFieldValueOptions *)options; @property(nonatomic, strong, readonly) FSTDatabaseID *databaseID; @@ -222,7 +216,12 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { /** * A structured object value stored in Firestore. */ -@interface FSTObjectValue : FSTFieldValue +// clang-format off +@interface FSTObjectValue : FSTFieldValue < NSDictionary * > + +- (instancetype)init NS_UNAVAILABLE; +// clang-format on + /** Returns an empty FSTObjectValue. */ + (instancetype)objectValue; @@ -237,8 +236,6 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { - (instancetype)initWithImmutableDictionary: (FSTImmutableSortedDictionary *)value NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - - (NSDictionary *)valueWithOptions:(FSTFieldValueOptions *)options; - (FSTImmutableSortedDictionary *)internalValue; @@ -261,19 +258,20 @@ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { /** * An array value stored in Firestore. */ -@interface FSTArrayValue : FSTFieldValue +// clang-format off +@interface FSTArrayValue : FSTFieldValue < NSArray * > + +- (instancetype)init NS_UNAVAILABLE; +// clang-format on /** * Initializes this instance with the given array of wrapped values. * * @param value An immutable array of FSTFieldValue objects. Caller is responsible for copying the - * value or releasing all references. + * value or releasing all references. */ - (instancetype)initWithValueNoCopy:(NSArray *)value NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - -- (NSArray *)valueWithOptions:(FSTFieldValueOptions *)options; - (NSArray *)internalValue; @end -- cgit v1.2.3 From 5b1ca51e7264b5ac581c7a0382beb44ed65bdf14 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Tue, 12 Dec 2017 07:11:39 +0800 Subject: Addressing comments --- Firestore/CHANGELOG.md | 4 ++++ .../Tests/Integration/API/FIRServerTimestampTests.m | 6 +++--- Firestore/Example/Tests/Util/FSTEventAccumulator.h | 4 ++-- Firestore/Source/Model/FSTFieldValue.h | 4 ++-- Firestore/Source/Public/FIRDocumentSnapshot.h | 14 +++++++------- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 0c5bcdc..fa2010b 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,3 +1,7 @@ +# Firestore API Branch +- [added] Added SnapshotOptions API to control how DocumentSnapshots return unresolved + server timestamps. + # Unreleased - [changed] Firestore no longer has a direct dependency on FirebaseAuth. - [fixed] Fixed a crash when using path names with international characters diff --git a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m index f2ae687..da3c03c 100644 --- a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m +++ b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m @@ -16,8 +16,6 @@ @import FirebaseFirestore; -#import -#import #import #import "Firestore/Example/Tests/Util/FSTEventAccumulator.h" @@ -128,7 +126,9 @@ [self expectedDataWithTimestamp:timestamp]); } -/** Verifies a snapshot containing _setData but using the previous field value for the timestamps. +/** + * Verifies a snapshot containing _setData but using the previous field value for server + * timestamps. */ - (void)verifyTimestampsInSnapshot:(FIRDocumentSnapshot *)snapshot fromPreviousSnapshot:(nullable FIRDocumentSnapshot *)previousSnapshot { diff --git a/Firestore/Example/Tests/Util/FSTEventAccumulator.h b/Firestore/Example/Tests/Util/FSTEventAccumulator.h index e32f08b..424f4c8 100644 --- a/Firestore/Example/Tests/Util/FSTEventAccumulator.h +++ b/Firestore/Example/Tests/Util/FSTEventAccumulator.h @@ -23,8 +23,8 @@ NS_ASSUME_NONNULL_BEGIN -typedef void (^FSTValueEventHandler)(id _Nullable, NSError *error); -typedef void (^FSTErrorEventHandler)(NSError *error); +typedef void (^FSTValueEventHandler)(id _Nullable, NSError *_Nullable error); +typedef void (^FSTErrorEventHandler)(NSError *_Nullable error); @interface FSTEventAccumulator : NSObject diff --git a/Firestore/Source/Model/FSTFieldValue.h b/Firestore/Source/Model/FSTFieldValue.h index 9b715a2..93fd5c4 100644 --- a/Firestore/Source/Model/FSTFieldValue.h +++ b/Firestore/Source/Model/FSTFieldValue.h @@ -44,9 +44,9 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) { /** Defines the return value for pending server timestamps. */ typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) { + FSTServerTimestampBehaviorNone, FSTServerTimestampBehaviorEstimate, - FSTServerTimestampBehaviorPrevious, - FSTServerTimestampBehaviorNone + FSTServerTimestampBehaviorPrevious }; /** Holds properties that define field value deserialization options. */ diff --git a/Firestore/Source/Public/FIRDocumentSnapshot.h b/Firestore/Source/Public/FIRDocumentSnapshot.h index e8e73c9..994cabf 100644 --- a/Firestore/Source/Public/FIRDocumentSnapshot.h +++ b/Firestore/Source/Public/FIRDocumentSnapshot.h @@ -26,6 +26,12 @@ NS_ASSUME_NONNULL_BEGIN * their final value. */ typedef NS_ENUM(NSInteger, FIRServerTimestampBehavior) { + /** + * Return `NSNull` for `FieldValue.serverTimestamp()` fields that have not yet + * been set to their final value. + */ + FIRServerTimestampBehaviorNone, + /** * Return a local estimates for `FieldValue.serverTimestamp()` * fields that have not yet been set to their final value. This estimate will @@ -38,13 +44,7 @@ typedef NS_ENUM(NSInteger, FIRServerTimestampBehavior) { * Return the previous value for `FieldValue.serverTimestamp()` fields that * have not yet been set to their final value. */ - FIRServerTimestampBehaviorPrevious, - - /** - * Return `NSNull` for `FieldValue.serverTimestamp()` fields that have not yet - * been set to their final value. - */ - FIRServerTimestampBehaviorNone + FIRServerTimestampBehaviorPrevious }; /** -- cgit v1.2.3 From b8714e3b8977567c7bd7b83163045d145be9ebb5 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Tue, 12 Dec 2017 08:04:50 +0800 Subject: Moving changelog --- Firestore/CHANGELOG.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index e6e3610..b8de1e9 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,12 +1,10 @@ -# Firestore API Branch -- [added] Added SnapshotOptions API to control how DocumentSnapshots return unresolved - server timestamps. - # Unreleased - [changed] Removed the includeMetadataChanges property in FIRDocumentListenOptions to avoid confusion with the factory method of the same name. - [changed] Added a commit method that takes no completion handler to FIRWriteBatch. - [feature] Queries can now be created from an NSPredicate. +- [added] Added SnapshotOptions API to control how DocumentSnapshots return unresolved + server timestamps. # v0.9.4 - [changed] Firestore no longer has a direct dependency on FirebaseAuth. -- cgit v1.2.3